list 继承2的类型就决定了它继承3的动态数组所能存储的元素类型,即后者的类型参数可以从变量中推断出。
例2:
public class TypeInference {
public static Set union(Set extends E> s1, Set extends E> s2) {
Set result = new HashSet(s1);
result.addAll(s2); return result;
} public static void main(String[] args) {
Set integers = new HashSet();
Set doubles = new HashSet();
Set numbers = null; //编译器的类型推断能力有限
numbers = TypeInference.union(integers, doubles); // Error
numbers = TypeInference.union(integers, doubles); // OK
}
}
public class HasF {
public void f(){...}
}//以下两种实现方式所取得的效果是一样的//泛型实现class Manipulator1{
private T obj;
public Manipulator1(T x){
this.obj = x;
} public void manipulate(){
obj.f();
}
}//多态实现class Manipulator2{
private HasF obj;
public Manipulator2(HasF x){
this.obj = x;
} public void manipulate(){
obj.f();
}
}
泛型类的识别(误区)
先看下面两段代码:
// 第一段代码public class Pair {
private T first;
private T second;
public Pair(T first, T second){
this.first = first;
this.second = second;
} public void setFirst(T first){
this.first = first;
} public T getFirst(){
return first;
} public void setSecond(T second){
this.second = second;
} public T getSecond(){
return second;
}
}
// 第二段代码public class DateInterval extends Pair { // 时间间隔类
public DateInterval(Date first, Date second){ super(first, second);
} @Override
public void setSecond(Date second) { super.setSecond(second);
} @Override
public Date getSecond(){ return super.getSecond();
}
}
由泛型类的定义可知,Pair 是一个泛型类,因为在类名后面有类型参数;类DateInterval 后面没有跟类型参数列表,因此该类就是一个 T 被替换为 Date 的实体类,其从 Pair泛型类型 继承得到的方法列表,与泛型彻底无关。
//代码示例public class Test2{
public static void main(String[] args) {
/**不指定泛型的时候*/
Integer i = Test2.add(1, 2); //这两个参数都是Integer,所以T为Integer类型
Number f = Test2.add(1, 1.2); //这两个参数一个是Integer,一个是Double,所以取同一父类的最小级,为Number
Object o = Test2.add(1, "asd"); //这两个参数一个是Integer,一个是String,所以取同一父类的最小级,为Object
System.out.println(i.getClass().getName()); //输出: java.lang.Integer
System.out.println(f.getClass().getName()); //输出: java.lang.Double
System.out.println(o.getClass().getName()); //输出: java.lang.String
/**指定泛型的时候*/
int a = Test2.add(1, 2); //指定了Integer,所以只能为Integer类型或者其子类
int b = Test2.add(1, 2.2); //编译错误,指定了Integer,不能为Double
Number c = Test2.add(1, 2.2); //指定为Number,所以可以为Integer和Double
}
//这是一个简单的泛型方法
public static T add(T x,T y){
return y;
}
}
/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
RangeCheck(index);
return (E) elementData[index];
// 只会让编译器认为该方法所返回的值是 E 类型,但此处转型不会起到预期效果,因为运行时 T 会被替换为 Object !!!
}
ArrayList> list = new ArrayList();
list = new ArrayList();
list.add(e); // e cannot be resolved to a variable
System.out.println(list1.size()); // OK
list 变量是ArrayList>类型,所以能将指向任意类型的ArrayList对象的引用存储在其中。但由于 list 是通配符类型参数的结果,所以存储引用的实际类型并不知道,因而无法使用这个变量调用任何与类型参数有关的方法。特别地,在 Java 集合接口1中,对于参数值是未知类型的容器类,只能读取其中元素,不能向其中添加元素, 因为,其类型未知,所以编译器无法识别添加元素的类型和容器的类型是否兼容,唯一的例外是 NULL(对 Null 而言,无所谓类型)。
public class Wildcards {
// Raw argument:
static void rawArgs(Holder holder, Object arg) {
holder.set(arg); // Warning:
holder.set(new Wildcards()); // Same warning
// OK, but type information has been lost:
Object obj = holder.get();
} // Similar to rawArgs(), but errors instead of warnings:
static void unboundedArg(Holder> holder, Object arg) { // holder.set(arg); // Error:
// holder.set(new Wildcards()); // Same error
// OK, but type information has been lost:
Object obj = holder.get();
}
}
class Fruit {}
class Apple extends Fruit {}
class Jonathan extends Apple {}
class Orange extends Fruit {}
public class CovariantArrays {
public static void main(String[] args) {
Fruit[] fruit = new Apple[10];
fruit[0] = new Apple(); // 编译期、运行期都 OK
fruit[1] = new Jonathan(); // 编译期、运行期都 OK
fruit[3] = new Fruit(); // 编译期 OK、运行期抛出 java.lang.ArrayStoreException(因为 fruit 的运行时类型是 Apple[], 而不是 Fruit[] 或 Orange[])
// 说明 Fruit[] 是 Apple[] 的父类型
System.out.println(Fruit[].class.isAssignableFrom(Apple[].class)); // true
}
}
public class NonCovariantGenerics {
List flist = new ArrayList(); // Compile Error: Type Mismatch }
由以上代码可以知道,编译期根本不允许我们这么做。试想,如果编译期允许我们这样做,该容器就允许存入任何类型的对象,只要它是一种Fruit,而不像数组那样会抛出运行时异常,违背了泛型的初衷(泛型保证容器的类型安全检查)。所以,在编译期看来,List 和 List 根本就是两种不同的类型,并无任何继承关系。
但是,有时你想要在以上两个类型之间建立某种向上转型关系,这就引出了通配符的上界。例如:
public class GenericsAndCovariance {
public static void main(String[] args) {
// 允许我们向上转型,向数组那样
List extends Fruit> flist = Arrays.asList(new Apple());
// Compile Error: can’t add any type of object:
flist.add(new Apple()); // Compile Error
flist.add(new Fruit()); // Compile Error
flist.add(new Object()); // Compile Error
flist.add(null); // Legal but uninteresting
// We know that it returns at least Fruit:
Fruit f = flist.get(0);
Object o = flist.get(0);
Apple a = flist.get(0); // Compile Error:Type mismatch
flist.contains(new Apple()); // OK
flist.indexOf(new Apple()); // OK
}
}
对于上述例子,flist 的类型就是List extends Fruit>了,但这并不意味着可以向这个 List 可以添加任何类型的 Fruit,甚至于不能添加 Apple。虽然编译器知道这个 List 持有的是 Fruit,但并不知道其具体持有哪种特定类型(可能是List,List接口5可知,参数 apples 是 Apple 或 Apple的某种基类型 (例如:Fruit,Object,…) 的 List,也就是说,该 List 可以是 List接口6 :
public class Holder {
private T value; public Holder() {
} public Holder(T val) {
value = val;
} public void set(T val) {
value = val;
} public T get() { return value;
} public boolean equals(Object obj) { return value.equals(obj);
} public static void main(String[] args) {
Holder Apple = new Holder(new Apple());
Apple d = Apple.get();
Apple.set(d);
Holder extends Fruit> fruit = Apple; // OK
Fruit p = fruit.get();
d = (Apple) fruit.get(); // Returns ‘Fruit’,类型擦除,返回上界
// No warning,运行时异常 java.lang.ClassCastException
Orange c = (Orange) fruit.get();
// fruit.set(new Apple()); // Cannot call set(),参数列表含通配符
// fruit.set(new Fruit()); // Cannot call set(),参数列表含通配符
fruit.equals(d); // OK,参数列表不含通配符
}
}
类型擦除指的是通过类型参数合并,将泛型类型实例关联到同一份字节码(Class 对象)上。编译器只为泛型类型生成一份字节码,并将其实例关联到这份字节码上。类型擦除的ArrayList super Apple> 类型在于从泛型类型中清除类型参数的相关信息,并且在必要的时候添加类型检查和类型转换的方法。
擦除是在ArrayList super Apple> 类型完成的。类型擦除可以简单的理解为将泛型java代码转换为普通java代码,只不过编译器更直接点,将泛型java代码直接转换成普通java字节码。泛型类型只有在ArrayList super Fruit> 类型才会出现,在此之后,程序中的所有泛型类型都将被擦除,并替换为它们的 super>。因此,>
2、编译器是如何配合类型擦除的?
3、类型擦除的主要过程
对于Pair
// Wildcard type for parameter that serves as an E producerpublic void pushAll(Iterable extends E> src) {
for (E e : src)
push(e);
}
Pair的原始类型为:
// popAll method without wildcard type - deficient!public void popAll(Collection dst) { while (!isEmpty())
dst.add(pop());
}
以下类型擦除示例:
// Wildcard type for parameter that serves as an E consumerpublic void popAll(Collection super E> dst) {
while (!isEmpty())
dst.add(pop());
}
// List extends T> 类型的 src 囊括了所有 T类型及其子类型 的列表
// List super T> 类型的 dest 囊括了所有可以将 src中的元素添加进去的 List种类
public static void copy(List super T> dest, List extends T> src) {
// 将 src 复制到 dest 中
int srcSize = src.size(); if (srcSize > dest.size())
throw new IndexOutOfBoundsException("Source does not fit in dest");
if (srcSize < COPY_THRESHOLD ||
(src instanceof RandomAccess && dest instanceof RandomAccess)) {
for (int i=0; i di=dest.listIterator();
ListIterator extends T> si=src.listIterator();
for (int i=0; i
//代码示例 Aclass Pair {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
//代码示例 4final class Test {
public static void main (String[] args) {
LinkedList numberList = new LinkedList ();
numberList.add(new NumericValue((byte)0));
numberList.add(new NumericValue((byte)1));
NumericValue y = Collections.max( numberList );
}
}
//代码示例 2final class NumericValue implements java.lang.Comparable{ //域
private byte value; //构造器
public NumericValue(byte); //方法
public int compareTo(NumericValue);
public volatile int compareTo(java.lang.Object); //桥方法
public byte getValue( );
}
反编译这个类,得到下面代码片段:
//代码示例 3class Collections {
public static Comparable max(Collection xs) {
Iterator xi = xs.iterator();
Comparable w = (Comparable) xi.next();
while (xi.hasNext()) {
Comparable x = (Comparable) xi.next();
if (w.compareTo(x) < 0) w = x;
}
return w;
}
}
现将泛型应用到上述代码,如下:
//代码示例 4final class Test {
public static void main (String[ ] args) {
LinkedList numberList = new LinkedList();
numberList.add(new NumericValue((byte)0)); ,
numberList.add(new NumericValue((byte)1));
NumericValue y = (NumericValue) Collections.max( numberList );
}
}
反编译这个类,得到下面代码片段:
public class Test10 {
public static void main(String[] args) {
ArrayList arrayList1=new ArrayList();
arrayList1.add("1"); //编译通过
arrayList1.add(1); //编译错误
String str1=arrayList1.get(0); //返回类型就是 String
ArrayList arrayList2=new ArrayList();
arrayList2.add("1"); //编译通过
arrayList2.add(1); //编译通过
Object object=arrayList2.get(0); //返回类型就是 Object
new ArrayList().add("11"); //编译通过
new ArrayList().add(22); //编译错误
String string=new ArrayList().get(0); //返回类型就是 String
}
}
在上述应用泛型的代码中,将
// 代码片段1public class SimpleHolder {
private Object obj; public void setObj(Object obj) { this.obj = obj;
} public Object getObj() { return obj;
} public static void main(String[] args) {
SimpleHolder holder = new SimpleHolder();
holder.setObj("Item");
String s = (String)holder.getObj();
}
}
public class GenericArray {
private T[] array; public GenericArray(int sz) {
array = (T[]) new Object[sz];
} public void put(int index, T item) {
array[index] = item;
} public T get(int index) { return array[index];
} public T[] rep() { return array; } public static void main(String[] args) {
GenericArray gai = new GenericArray(10);
gai.put(0, new Integer(4));
gai.get(0);
Integer i = gai.get(0); // This causes a ClassCastException:
Integer[] ia = gai.rep(); // This is OK:
Object[] oa = (Object[])gai.rep();
}
}
我们的本意是想重写父类Pair中的setSecond方法,但是从方法签名上看,这完全是两个不同的方法,type mismatch。而实际情况呢?运行DateInterval的main方法,我们看到 public void setSecond(Date second)的确重写了public void setSecond(Object second)方法。这是如何做到的呢?
使用Java类分析器对其进行分析,结果:
// 第一段代码public class Pair {
private T first;
private T second;
public Pair(T first, T second){
this.first = first;
this.second = second;
} public void setFirst(T first){
this.first = first;
} public T getFirst(){ return first;
} public void setSecond(T second){ this.second = second;
} public T getSecond(){ return second;
}
}
为了实现多态,我们知道方法3也是由编译器生成的桥方法。方法擦除带来的第二个问题就是:由编译器生成的桥方法 public volatile java.lang.Object getSecond() 方法和 public java.util.Date getSecond() 方法,从方法签名的角度看是两个完全相同的方法,它们怎么可以共存呢? 如果是我们自己编写Java代码,这样的代码是无法通过编译器的检查的,但是虚拟机却是允许这样做的,因为虚拟机通过参数类型和返回类型来确定一个方法,所以编译器为了实现泛型的多态允许自己做这个看起来“不合法”的事情。
补充说明: Iterable 例如,
public void setSecond(Date second);
使用Java 类分析器对ASub分析可得:
public class DateInterval extends Pair{
//构造器
public DateInterval(java.util.Date, java.util.Date); //方法
public void setSecond(java.util.Date); public volatile void setSecond(java.lang.Object); //方法 1
public java.util.Date getSecond( ); //方法 2
public volatile java.lang.Object getSecond( ); //方法 3,它难道不会和方法 2 冲突?
public static void main(java.lang.String[]);
}
事实上,在调用 pushAll方法时 src生产了 E实例(produces E instances),在调用 popAll方法时 dst消费了 E实例(consumes E instances)(Exception是Throwable的子类)。例如:下面的定义将不会通过编译
class FixedSizeStack {
private int index = 0;
private Object[] storage;
public FixedSizeStack(int size) {
storage = new Object[size];
} public void push(T item) {
storage[index++] = item;
} public T pop() {
//Warnning: Unchecked cast from Object to T
return (T) storage[--index];
}
}public class GenericCast {
public static final int SIZE = 10;
public static void main(String[] args) {
FixedSizeStack strings = new FixedSizeStack(SIZE);
for (String s : "A B C D E F G H I J".split(" "))
strings.push(s); for (int i = 0; i < SIZE; i++) {
String s = strings.pop();
System.out.print(s + " ");
}
}
}
class Pair {
public boolean equals(T value) {
return null;
}
}
public boolean equals(T value){}
boolean equals(Object)
public class CheckedList { @SuppressWarnings("unchecked")
static void oldStyleMethod(List probablyDogs) { //原生List
probablyDogs.add(new Cat());
}
public static void main(String[] args) {
List dogs1 = new ArrayList();
oldStyleMethod(dogs1); // Quietly accepts a Cat
List dogs2 = Collections.checkedList(
new ArrayList(), Dog.class);
try {
oldStyleMethod(dogs2); // Throws an exception
} catch(Exception e) {
System.out.println(e);
}
// Derived types work fine: List pets = Collections.checkedList(
new ArrayList(), Pet.class);
pets.add(new Dog());
pets.add(new Cat());
}
} /* Output:
java.lang.ClassCastException: Attempt to insert class typeinfo.pets.Cat
element into collection with element type class typeinfo.pets.Dog