Java泛型系统学习

1,什么是Java中的泛型

Java泛型是 JDK5 中引入的一个新特性,允许在定义类和接口的时候使用类型参数(type parameter)。声明的类型参数在使用时用具体的类型来替换。泛型中最主要的应用是在jdk5中的新集合类框架中。
泛型最大的好处是可以提高代码的复用性。以 List 接口为例,我们可以将 String、Integer等类型放入List中,如不用泛型,存放 String 类型要写一个List接口,存放Integer要写另外List 一个接口,泛型可以很好的解决这个问题。

2,什么是类型擦除?

Java的泛型是在编译器这个层次来实现的,在生成的Java字节代码中不包含泛型中的类型信息。使用泛型的时候加上类型参数,会被编译器编译的时候擦掉。其实编译器通过codeshareing方式为每个泛型类型创建唯一的字节码表示,并且该泛型类型的实例都映射到这个唯一的字节码表示上。将多种泛型类型实例映射到唯一的字节码表示是通过类型擦除(type erasue)实现的。
codesharing : 对每个泛型类只生成唯一的一份目标代码,该泛型类的所有实例都映射到这份目标代码上,在需要的时候执行类型检查和类型转换。

类型擦除:

类型擦除是指通过类型参数合并,将泛型类型实例关联到同一份字节码上。编译器只为泛型类型生成一份字节码,编译器只为类型类型生成一份字节码,并将其实例关联搭配这份字节码上。类型擦除的关键在于从类型中清除类型参数相关的信息,并且在必要的时候添加类型检查和类型转换的方法。类型擦除可以简单的理解为将泛型的java代码转换成普通的java代码,只不过编译器更直接点,将泛型java代码直接转换为普通java字节码。类型擦除的主要过程如下:1,将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。2,移除所有的类型参数。

总结:
1. 虚拟机中没有泛型,只有普通类和普通方法,所有泛型类的类型参数在编译时都会被擦除,泛型类并没有自己独有的Class类对象。比如并不存在List;String>.class或是List Integer.class,而只有List.class。
2. 创建泛型对象时请指明类型,让编译器尽早的做参数检查(Effective Java,第23条:请不要在新代码中使用原生态类型不要忽略编译器的警告信息,那意味着潜在的ClassCastException等着你。
3. 不要忽略编译器的警告信息,那意味着潜在的ClassCastException等着你。
4. 静态变量是被泛型类的所有实例所共享的。对于声明为MyClass的类,访问其中的静态变量的方法仍然是 MyClass.myStaticVar。不管是通过new MyClass还是new MyClass创建的对象,都是共享一个静态变量。
5. 泛型的类型参数不能用在Java异常处理的catch语句中。因为异常处理是由JVM在运行时刻来进行的。由于类型信息被擦除,JVM是无法区分两个异常类型MyException和MyException的。对于JVM来说,它们都是 MyException类型的。也就无法执行与异常对应的catch语句。

泛型中的限定通配符和非限定通配符

限定通配符对类型进行限制,泛型中有两种限定通配符:
– 一种是 来保证泛型类型必须是T的子类来设定泛型类型的上边界;
– 一种是来保证泛型类型必须是 T 的父类来设定类型的下边界;

泛型类型必须用限定的类型来进行初始化,否则,会导致编译错误。非限定通配符表示可以用任意泛型类型来替代,可以在某种意义上来说是泛型向上转型的格式,因为List 与List不存在继承关系。

Java泛型中 K T V E ? object 的含义

E – Element :在集合中使用,因为集合存放的是元素
T – Type : Java 类
K – Key : 键
V – Valeu : 值
N – Number : 数值类型
? – 表示不确定的java类型(无限制通配符类型)
S、U、V – 2nd、3rd、4th types
Object – 是所有类的根类,任何类的对象都可以设置给Object引用变量,使用的时候可能需要类型强制转换,但是用使用了泛型T、E等这些标识后,在实际用之前类型就已经确定了,不需要再进行类型强制转换。

上下界限定符 extends 和 super

在Java中,这两个经常被用于通配符以此来表示类型的上界(extends)和下界(super)。为了更好的理解上下界限定符,看一下下面的两个使用场景。
PECS原则(下文给出详细解释):

  • 场景一: 如果要从集合中读取类型 T 的数据,并且不能写入,可以使用 ? extends 通配符(Producer Extends)

  • 场景二: 如果要从集合中写入类型 T 的数据,并且不需要读,可以使用 ? super 通配符(Consumer Super)。
    如果既要存又要取,那么就不要使用任何通配符

错误案例

  1. 可以把 List 传递给一个接受 List参数的方法吗?
    不可以。

    public void inspect(List<Object> list) {
    for (Object obj : list) {
        System.out.println(obj);
    }
    list.add(1); //这个操作在当前方法的上下文是合法的。 
    }
    public void test() {
        List<String> strs = new ArrayList<String>();
        inspect(strs); //编译错误
    }
    

    这段代码中,inspect方法接受List作为参数,当在test方法中试图传入List的时候,会出现编译错误。
    假设这样的做法是允许的,那么在inspect方法就可以通过list.add(1)来向集合中添加一个数字。这样在test方法看来,其声明为List的集合中却被添加了一个Integer类型的对象。这显然是违反类型安全的原则的,在某个时候肯定会抛出ClassCastException。
    因此,编译器禁止这样的行为。编译器会尽可能的检查可能存在的类型安全问题。对于确定是违反相关原则的地方,会给出编译错误。当编译器无法判断类型的使用是否正确的时候,会给出警告信息。

  2. Java中List和原始类型List之间的区别
    原始类型List和带参数类型之间的主要区别是,在编译时编译器不会对原始类型进行类型安全检查,却会对带参数的类型进行检查。

    通过使用Object作为类型,可以告知编译器该方法可以接受任何类型的对象,比如String或Integer。

    它们之间的第二点区别是,你可以把任何带参数的类型传递给原始类型List,但却不能把List传递给接受 List的方法,因为会产生编译错误。如 1 的题目。

  3. Java中List和List之间的区别

    List 是一个未知类型的List,而List 其实是任意类型的List。你可以把List, List赋值给List<?>,却不能把List赋值给 List

    说点什么

    avatar
      Subscribe  
    提醒

    相关文章

    开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

    返回顶部