JAVA-泛型篇

最近又开始学习了,仔细看来,发现这篇在现在看来毫无营养的文章居然发布快4年了。个人工作也快3年了,感觉自己越来越像个CRUD程序猿,是时候回来翻翻了。

一般的类和方法,只能使用具体的类型:要么是基本类型,要么是自定义的类。如果要编写可以应用多种类型的代码,这种刻板的限制对代码的束缚就会很大。 《JAVA编程思想》

泛型最主要的作用:告诉你自己、你的同事以及IDE,别在我需要一个Integer的时候给我一个String,谢谢! 沃知基硕德。

为什么要有泛型

泛型在JAVA语言里面出现的时间不算早,1.5之后才出现,而且JAVA的泛型一直有人说其是伪泛型,那么为什么JAVA的语言开发者要引入泛型呢,而且不想其他语言一样支持真实的泛型,而是选择使用擦除呢?

为什么要引入泛型

想知道这个问题的答案就需要知道如果没有引入泛型会有什么问题。
答案是不会有任何问题。
你的程序加不加泛型丝毫不影响其能不能在JVM里面跑,也不影响其跑的快不快,因为泛型是到不了Class的,其在编译时就会被擦除,替换了。这就是为什么很多人说JAVA里面的泛型是伪泛型。

// 对于jvm来说,下面两行代码毫无区别,他们都等于第三行代码
List<String> allStrList = new ArrayList<>();
List allStrList = new ArrayList();

List<Object> allStrList = new ArrayList<>();

那为什么JAVA语言开发者还要引入泛型,并且还要弄个伪泛型呢?他们都是大神级别的人物,精通C++,JAVA,C#等语言,对于他们来说实现真正的泛型并非难事。
对于第一个问题,其实上面引述自《JAVA编程思想》的话就已经说明白了,就是为了代码的复用,C++里面的泛型甚至直接叫“模板”。此外其实还有个作用,个人甚至觉得这个作用更大,就是用来提高阅读性和帮助IDE进行类型检查,避免潜在的错误。事实上,当你按上面的第二行代码写上去,编译器是会给Raw Types警告的,需要使用@SuppressWarnings("rawtypes")进行压制。

// 如果不使用此注解进行压制,IDE将给出raw types警告
@SuppressWarnings("rawtypes")	
List allStrList = new ArrayList();

但对于第二个问题,事实上对于JVM从技术上来说,实现起来并不难,但这群大佬还是选择了放弃真泛型。其原因大致有两点:

  1. 兼容性考虑:当时是1.5,如果使用了真泛型,就会出现Python2 和 Python3的问题,无法相互兼容。但当时的JAVA已经运行在了无数设备上,还有无数的第三方库,不兼容的后果将会是灾难性的。我们可以从前不久的log4j的事情以及ie的事情来设想下此情景。
  2. 效率考虑:JAVA在很长的一段时间里,其运行效率都是捉襟见肘的,特别是在电子产业早期,相信不少同学应该都在手机上玩过JAVA游戏,以诺基亚为例,当时的内存是几百M。而如果使用真泛型,JVM就需要针对运行的程序进行记录,保存其运行类型。这一点可以从C#的身上看到影子,由于 .NET 泛型的类型参数之实际类型在运行时均不会被消除,运行速度会因为类型转换的次数增加而减慢。

怎么用泛型

泛型其实用法很简单,就下面几项:

  1. 泛型类:可以约束其非静态属性,非静态方法。比如下面代码里面的T,R。
  2. 泛型方法:
    • 静态方法:静态方法其泛型是不受类泛型约束的,比如下面这个例子里面的T,其和类上面的T是完全无关的,同样的还有静态属性是不合法的,同学可以想想是为什么?
    • 普通方法:其可以自定义泛型,也可以使用类上面的泛型
  3. 泛型接口:这个其实和泛型类大差不差,唯一的点在于其工厂模式的设计。但这属于设计模式的应用,不属于语法层面,
  4. 上边界:写法和类的继承一样也是使用extends关键字,以此约束泛型必须是某个类的之类
  5. 下边界:关键词为super,而且只能和下面的通配符?配合使用,用于约束泛型的下边界
  6. 通配符:关键词?,这是一个非常特殊的泛型类型,可以代表任何类型,你甚至可以List<? super R>这种骚操作,而这某种程度来说也是唯一应该使用的用法。
public abstract class Generic<T, R extends Number> {

    T[] genericTs;

    T t;

    List<T> list;

    {
        System.out.println(genericTs.length);
        System.out.println(t);
        System.out.println(list);
    }
    
    public abstract <E extends Number> T[] genericArrays(
            T t, R r,
            List<T> tList,
            Class<? extends Number> numberClazz, Class<? super Integer> superIntegerClazz,
            List<E> list
    );

	// 注意此处的R继承自String,和类上面的继承自Number的R没有半毛钱关系
    public static <R extends String> R staticFun(
            R r,
            List<? extends R> listExtends,
            List<? super R> listSuper
    ) {
        return r;
    }

}

// 泛型接口的工厂模式
interface GenericInterface<T>{
    T getInstance();
}

泛型使用的注意点

  1. 泛型是会被擦除的,其擦除后的实际对象会依据泛型的上下界而定,有上界的擦除为上界,有下界的擦除为下界,通配或者无界的擦除为Object。
  2. 普通的泛型变量只能有上界,不能有下界。
  3. 通配符可以有上界,也能有下界,但不能同时有上下界。
  4. 泛型最大的作用是告诉IDE里面的类型是什么,在编译阶段帮忙审查代码是否存在错误,尽量避免运行时发生类型错误

泛型源码和顶层设计

下图便是泛型的最顶层设计。

  • 其中的Type是1.5版本新设计的,是所有JAVA类型的祖先类,类似于Object在实例里面的地位,
  • 对于所有的JAVA文件,无论是系统自带的jar包,或者是个人开发者写的类,接口,注解等等都可以编译为Class文件,所以对于这些一概类型定义为Class
  • ?是泛型通配符(WildcardType),当进行编译的时候,其是最特殊的一个,需要考虑其上下界
  • 泛型类型(TypeVariable)就是我们常写的T, E, K, V, R这些。
  • GenericArrayType这个根据字面意思就能知道是啥,泛型数组,比如T[]这样的。
  • 被泛型化类型(ParameterizedType),这个是指List<T>之类,本来是普通类型,但添加了泛型类型的普通类型。
    类型uml图

例子:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值