泛型 一、引入泛型的意义 二、Java的泛型是伪泛型(语法糖) 三、类型擦除 四、通配符 五、泛型和数组 六、泛型和构造函数 七、泛型和多态 八、泛型方法 参考资料

泛型,即泛化类型。本质是将数据类型指定为参数——参数化类型。泛型程序设计(Generic Programming)意味着编写的代码可以被很多不同类型的对象所重用。

【类型参数的理解】

类似于函数中的形参和实参一样,当一个泛型声明被调用,实际类型参数(actual type arguments)取代形式类型参数。

与c++中的Template的重要区别是,java的泛型是通过擦除实现的!所以,并不是直接把所有的类型参数简单地替换成实际类型。在java里,一个泛型类型的声明只被编译一次,并且得到一个class文件,就像普通的class或者interface的声明一样。而类型参数就是这个class的一个参数。譬如ArrayList会是一个class文件,ArrayList<String>和ArrayList<Integer>的对象都共用ArrayList.class。

值得注意的是,泛型的限制之一是:泛型与继承没有关系!!!!尽管String是Object的子类,但ArrayList<Object>和ArrayList<String>之间没有继承关系!!所以不能把ArrayList<String>的变量赋给ArrayList<Object>。

** 引入泛型之后,继承这个概念存在两个维度:一个是类型参数本身的继承体系,譬如String和Object。另一个是泛型类或接口自身的继承体系,譬如List和ArrayList。

在Java1.5之前,只能通过Object是所有类型的父类和类型强制转换来共同实现类型泛化。但这要求程序员清晰地知道具体类型,编译器无法检查类型强制转换是否正确。只有运行期的JVM才知道是否正确,大量ClassCastException的风险被转嫁到程序运行期。而有了泛型,编译器就能通过类型参数来保证类型转换的正确性,增强了程序的可读性和稳定性

泛型
一、引入泛型的意义
二、Java的泛型是伪泛型(语法糖)
三、类型擦除
四、通配符
五、泛型和数组
六、泛型和构造函数
七、泛型和多态
八、泛型方法
参考资料

二、Java的泛型是伪泛型(语法糖)

Java 语言中的泛型基本上完全在编译器中实现,由编译器执行类型检查和类型推断,然后生成普通的非泛型的字节码。——擦除技术erasure(编译器使用泛型类型信息保证类型安全,然后在生成字节码之前将其清除)。

Java的泛型只在程序源码中存在,在编译后的字节码文件中,就已经被替换为原来的原始类型(Raw Type,也称为裸类型)了,并且在相应的地方插入了强制转型代码。——不需要改动原来的JVM就支持泛型,因此泛型其实是Java的一颗语法糖。对于JVM来说,ArrayList<Integer>和ArrayList<String>是同一个类型,String和Integer已在编译期被擦除,只剩下ArrayList。

如果通过反射来调用list的add方法,可以绕过编译器的类型检查!!!

泛型
一、引入泛型的意义
二、Java的泛型是伪泛型(语法糖)
三、类型擦除
四、通配符
五、泛型和数组
六、泛型和构造函数
七、泛型和多态
八、泛型方法
参考资料

泛型
一、引入泛型的意义
二、Java的泛型是伪泛型(语法糖)
三、类型擦除
四、通配符
五、泛型和数组
六、泛型和构造函数
七、泛型和多态
八、泛型方法
参考资料

三、类型擦除

3.1 类型擦除的过程

首先是找到用来替换类型参数的具体类。这个具体类一般是Object。如果指定了类型参数的上界的话,则使用这个上界。把代码中的类型参数都替换成具体的类。同时去掉出现的类型声明,即去掉<>的内容。比如T get()方法声明就变成了Object get();List<String>就变成了List。接下来就可能需要生成一些桥接方法(bridge method)。这是由于擦除了类型之后的类可能缺少某些必须的方法。比如考虑下面的代码:

class MyString implements Comparable<String> {
    public int compareTo(String str) {        
        return 0;    
    }
} 

当类型信息被擦除之后,上述类的声明变成了class MyString implements Comparable。但是这样的话,类MyString就会有编译错误,因为没有实现接口Comparable声明的int compareTo(Object)方法。这个时候就由编译器来动态生成这个方法。

3.2 类型擦除引起的一些问题

java.lang.Class是泛型化的。Class有一个类型参数T,代表Class对象代表的类型。但是一个具体的泛型类是没有独享的Class类对象的。List<String>和List<Integer>的Class对象都是List.class。

因此,静态成员和方法、静态代码块都是由泛型类的所有实例共享的,无论是new MyClass<String>还是new MyClass<Integer>创建的对象,都共享静态成员和方法、静态代码块,所以不允许在静态成员和方法、静态代码块中使用类型参数!——容易产生冲突。

类型参数不能用于异常类型,因为异常处理是由JVM在运行时刻进行的,由于类型擦除,JVM无法区分catch语句中的两个异常类型MyException<String>和MyException<Integer>。

由于大多数情况下,擦除后的类型就会是Object,而Object是不能存储基本类型的,所以类型参数不允许是基本类型

四、通配符

前面提到,泛型的限制之一是不能维持继承关系。通配符用于支持灵活的子类化。

4.1 无限定通配符(?)

Collection<?>表示一个集合,它的元素类型可以匹配任意类型。

注意区分Collection<Object>,该集合的元素类型只能是Object,不含子类。尽管写入时,可以接受子类向上转型为Object再写入,但读出时一律是Object类型的,需要根据实际类型进行强制转换。

泛型
一、引入泛型的意义
二、Java的泛型是伪泛型(语法糖)
三、类型擦除
四、通配符
五、泛型和数组
六、泛型和构造函数
七、泛型和多态
八、泛型方法
参考资料

泛型
一、引入泛型的意义
二、Java的泛型是伪泛型(语法糖)
三、类型擦除
四、通配符
五、泛型和数组
六、泛型和构造函数
七、泛型和多态
八、泛型方法
参考资料

在上例中,List使用通配符后无法写入null以外的对象,原因在于,Collection接口的定义中,add()方法的入参使用的是在类上声明的类型参数,而该List对象的类型参数是通配符,add方法无法判断传入的具体类型,这是不安全的操作,所以禁止。但读出元素时,由于一定会是个Object,这是安全的。

泛型
一、引入泛型的意义
二、Java的泛型是伪泛型(语法糖)
三、类型擦除
四、通配符
五、泛型和数组
六、泛型和构造函数
七、泛型和多态
八、泛型方法
参考资料

4.2 上限通配符(? extends Parent)

List<? extends Parent>表示元素类型是Parent类及其子类。同样存在只读限制,因为传入类型虽然有界,但具体类型仍是未知的。改进是读出元素时,可以把边界缩小到Parent。

泛型
一、引入泛型的意义
二、Java的泛型是伪泛型(语法糖)
三、类型擦除
四、通配符
五、泛型和数组
六、泛型和构造函数
七、泛型和多态
八、泛型方法
参考资料

4.3 下限通配符(? super Child)

List<? super Child>表示元素类型是Child及其父类。只允许写入下界对应的类型,因为其祖先类型是未知的,有可能接口不一致,如果允许写入会存在安全隐患。上界未知,所以上界实际上还是Object,读出时只能用Object来接收。

泛型
一、引入泛型的意义
二、Java的泛型是伪泛型(语法糖)
三、类型擦除
四、通配符
五、泛型和数组
六、泛型和构造函数
七、泛型和多态
八、泛型方法
参考资料

4.4 原始类型(raw type)

原始类型就是擦除了泛型信息,最终在字节码中的真实类型。

用于与泛型之前的老代码兼容。类似于通配符,但类型检查更宽松,使用时会产生未检查警告(unchecked warning, rawtypes)。

对于无界通配符和下限通配符,擦除后类型为Object,对于上限通配符,擦除后类型为上界。所谓的界定,其实只在编译器进行类型检查时有效,在运行时全部无效。所以在运行期使用类型参数,就会产生错误。

五、泛型和数组

java中的数组是协变的,即:如果Integer扩展了Number,那么不仅Integer是Number,Integer[]也是Number[]。在要求Number[]的地方,可以传入一个Integer[]型的引用。也就是说,当Number是Integer的超类型时,Number[]是Integer[]的超类型。

java的泛型不是协变的。因为List<Number>并不是List<Integer>的超类型。原因在于,这是不安全的。Number可能有多个子类,譬如Integer和Float。如果允许,就会允许把List<Integer>的引用赋给List<Number>,同时允许将Float类型的元素写入该list中。但实际上该List中应存放Integer,编译器完全无法检查这种情况,问题抛给了运行期。

数组能协变而泛型不能协变的后果就是,不能实例化泛型类型的数组。如果允许泛型类型的数组,就可能把ArrayList<String>[]的引用赋给Object[],这样就可能把ArrayList<Integer>引用放入该Object数组中,这时再使用ArrayList<String>的引用去读取元素时,就会发现类型转换错误,因为泛型是不协变的,ArrayList<Integer>不能转为ArrayList<String>。

成功创建泛型数组的唯一方式是创建一个类型擦除的数组,然后转型:

泛型
一、引入泛型的意义
二、Java的泛型是伪泛型(语法糖)
三、类型擦除
四、通配符
五、泛型和数组
六、泛型和构造函数
七、泛型和多态
八、泛型方法
参考资料

不能实例化用类型参数表示的类型数组。编译器不知道 V到底表示什么类型,因此不能实例化 V数组。

Collections 类通过一种别扭的方法绕过了这个问题,在 Collections 类编译时会产生类型未检查转换的警告。

class ArrayList<V> { 
  private V[] backingArray; 
  public ArrayList() { 
    backingArray = (V[]) new Object[DEFAULT_SIZE]; 
  } 
 }

因为泛型是通过擦除实现的,backingArray的类型实际上就是 Object[],因为 Object代替了 V。这意味着:实际上这个类期望 backingArray是一个 Object数组,但是编译器要进行额外的类型检查,以确保它包含 V类型的对象。所以这种方法很奏效,但是非常别扭,因此不值得效仿。

六、泛型和构造函数

不能使用类型参数来访问构造函数,譬如:new T(param)。因为在编译时并不知道要构造什么类,因此调用哪个构造函数是不确定的,也许该类中并没有对应的构造函数。

不能用通配符类型的参数调用泛型构造函数,即使知道存在这样的构造函数也不行,譬如:new HashSet<?>(set);  // illegal

七、泛型和多态

7.1 擦除与多态的冲突

假设有以下父类:

class Pair<T> {
    private T value;
    public T getValue() {
        return value;
    }
    public void setValue(T value) {
        this.value = value;
    }
}

子类:

class DateInter extends Pair<Date> {
    @Override
    public void setValue(Date value) {
        super.setValue(value);
    }
    @Override
    public Date getValue() {
        return super.getValue();
    }
}

父类经过擦除后,类型参数用Object替换。这样一来,子类中“覆盖”的方法,实际上是重载,而不是重写,也就是说,子类中应该有4个方法。这与本意相悖。泛型和多态出现了冲突。

7.2 桥方法

事实上,子类中的两个方法确实是重写了。JVM采用了桥方法来完成这一功能。而最终编译的字节码中也确实存在了4个方法,只不过多出来的那两个方法就是桥方法,它们的作用是去调用我们重写的那两个方法。

public Object getValue() {
        return super.getValue();
} //桥方法

public Date getValue() {
        return super.getValue();
} //实际重写的方法

注意到这两个方法的签名完全一样,由JVM来区别哪个是桥方法……

八、泛型方法

在泛型方法中,类型参数的重要作用是用来表示多个参数之间的依赖关系。如果没有依赖关系,而是用于多态,那么应使用通配符。

在很多泛型方法中,类型参数和通配符配合使用。

泛型
一、引入泛型的意义
二、Java的泛型是伪泛型(语法糖)
三、类型擦除
四、通配符
五、泛型和数组
六、泛型和构造函数
七、泛型和多态
八、泛型方法
参考资料

泛型
一、引入泛型的意义
二、Java的泛型是伪泛型(语法糖)
三、类型擦除
四、通配符
五、泛型和数组
六、泛型和构造函数
七、泛型和多态
八、泛型方法
参考资料

泛型
一、引入泛型的意义
二、Java的泛型是伪泛型(语法糖)
三、类型擦除
四、通配符
五、泛型和数组
六、泛型和构造函数
七、泛型和多态
八、泛型方法
参考资料

参考资料

http://www.ibm.com/developerworks/cn/java/j-jtp01255.html

http://www.infoq.com/cn/articles/cf-java-generics

http://blog.****.net/lonelyroamer/article/details/7868820