`
semi_sleep
  • 浏览: 99089 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

对Java Generic相关知识的总结

    博客分类:
  • Java
阅读更多

对于如 List<E> List< String > List ,其中 List<E> 称为 parameterized type E 称为 (formal) type parameter String 称为 actual type argument List 称为 raw type

Generic 的逻辑意义

原有 java 的类型系统

Generic java 5 带来了新的类型,这使得 java 中的类型关系变得更加复杂,要弄清楚加入了 generic 后的类型关系就需要先弄清楚原先 java 中的类型系统。

首先,在类型的定义上,类之间不允许多重继承,类可以实现多个接口。

其次,在类型的使用上,每个变量都必须有明确的类型,变量只能指向相应类型(或相应类型的子类型)的对象,为了实现这一规则, compile time 会对所有的赋值操作做检测,而 runtime 则对所有的显示类型转换做检测。

最后,数组作为 java 中一个特殊的类型,如果 B extends A ,那么 B[] extends A[] ,当对数组 element 赋值时, runtime 会做检测,类型不符则抛 ArrayStoreException ,如下。由于有多重数组的出现,意味着 java 的类型系统种有无限种类型。

// B extends A

A a = new A();

A[] array = new B[1];

Array[0] = a; // ArrayStoreException

我认为理想状态下的 generic

首先,假设有 B<T> extends A ,那么 B<Object> extends A B<String> extends B<Object> ,并且 runtime 对使用到 parameter type 的输入参数做类型检测。这跟原先 java 类型系统中的 array 是一致的。与数组相同的还有,因为有如 B<B<String>> B< B<B<String>>> 等等类型的存在, generic 也可以无限增加可用类型。

其次,当 generic 跟继承连用时,(在不考虑接口的情况下)有三种新的形式: B<T> extends A B extends A<String> B<T> extends A<T> ,其中第三种情况意味着有 B<String> extend A<String>

现实中的 generic

       事实上,在 java 5 中,对于 B<T> extends A B<Object> B<String> 之间并不存在继承关系( invariant subtyping ),这跟数组( covariant subtyping )不同。之所以使用这种做法,我想有以下原因:

首先, java 5 compiler 使用 erasure 来支持 generic ,所有与 generic 相关的信息都不存在于 runtime (见下文中“ generic 的实现”),这就意味着 runtime 无法做如下的类型检测,而即便 runtime 有条件做类型检测,也势必影响代码的执行效率。

ArrayList<String> strList = new ArrayList<String>();

ArrayList<Object> objList = strList;

objList.add(new Object()); // runtime could not throw exception

其次,考虑下面的例子, B<T> extends A<T> ,有 B<String> extends A<String> ,如果使用 covariant subtyping ,又有 B<String> extends B<Object> ,这意味着存在多重继承,而多重继承在 java 里面是不被允许的。值得注意的是,尽管数组使用 covariant subtyping ,但却不会导致多重继承,因为数组属于系统类型, java 并不允许数组被继承。

采用了 invariant subtyping 之后,假如有 A<T> ,由于 A<Object> 不再是其他类型 A<String> A<Integer> 等类型的父类, 则无法声明可以指向所有 A<T> 类型对象的变量。为了解决这一问题, java 1.5 引入了 wildcard ,声明为 A<?> 类型的变量可以指向所有 A<T> 类型的对象。需要注意的是, wildcard 跟继承是两种不同的关系,继承使类型间呈现树状的关系,类型为 B 的变量可以指向的对象类型必须在以 B 为根节点的子树中,而类型为 A<?> 的变量可以指向的对象类型必须为类型树中 A<Object> 或与 A<Object> 平行的节点。最后, wildcard 跟继承结合使得 A<?> 类型变量能够指向的对象类型必须在以 A<Object> A<Object> 平行的节点为根的所有子树中。

// A<T> extends Object, B extends Object, C extends B, D extends B

A<?> a; // instances of A<Object>, A<String>, A<Integer> can be assigned to this variable

B b; // instance of B, C, D can be assigned to this variable

Generic 的实现

加入了 generic java type safe

       保证 type safe ,其实就关键在于确保所有变量所指向的对象的类型必须是正确的。我认为在理想状态下,应该实现以下几点:首先,类型为 A 的变量所能指向的对象类型必须在以 A 为根节点的子树中;其次,类型为 wildcard 的变量,如 A<?> ,所能指向的对象类型必须在以 A<Object> A<Object> 平行的节点为根的所有子树中;最后,所有的显式转换在 runtime 必须做类型判定。其中,前两点由 compiler 实现,最后一点由 jvm 实现,然而事实上, java 5 仅实现了前两点,而决定不在 runtime 做检测。

       Compile time generic type safe 主要包括 generic class generic method type safe ,以下分开讨论。

Generic class type safe

       假设有以下的类:

public class A {};

public class B<T> extends A {

public T obj;

}

public class C<T> extends B<T> {

public void set(T obj) { this. obj = obj; }

public T get() { return obj; }

}

对于类型为 C<String> 的对象,能够指向它的变量的类型有: A B<String> C<String> B<?> C<?> 。对于类型为 A 的变量,通过该变量无法访问到任何与 T 相关的方法或对象变量,很显然在原有 java type safe 机制仍然有效;对于类型为 B<String> C<String> 的变量, compiler 对所有通过该变量所访问的方法( set get )或对象变量 (obj) 进行检测,所有涉及到 T 的赋值都必须满足 T=String ,则 type safe 得以保证。对于类型为 B<?> C<?> 的变量,通过该变量所访问的方法或对象变量,所有的输出值中 T 类型被替换成 T bound (见下文中“ type parameter 的限制”),所有输入值中由于 T 类型未知,所以不能接受任何变量赋值( null 除外)。在理想状态下,输入值中 T 类型应该也被替换成 T bound ,然后由 runtime 去做类型判定,但是由于 runtime 没有 generic 相关的任何信息

C<String> strC = new C<String>();

C<?> c = strC;

// even if the following code pass compile time check, runtime could not throw exception

c.obj = new Object();

c.set(new Object());

// here’s a unexpected exception

String str = strC.obj;

str = strC.get();

generic class 的所有方法中, T 的类型被认为是其 bound 或者 bound 的某个子类。也就是说,首先, T 的变量只能指向类型为 T T 的子类的对象;其次,通过 T 的变量只能访问到其 bound 的方法和对象变量。假设以下代码存在于 C set 方法中:

public void set(T obj;) {

Object temp;

temp = obj; // ok

obj = temp; // ompile error

obj.toString(); // can access Object’s methods

}

Generic method type safe

Generic class 不同的是,在 generic method 中, actual type argument 并非指定的,而是由 compiler 推断出的( Inference )。 Compiler 通过对 generic method 中的输入变量的类型推断 type parameter 的类型,如果不能够得到一个 unique smallest type ,则被视为 compile error ,参考以下代码:

public <A> void doublet(A a, A b) {};

// compile error, because String and Integer have both Comparable and Serializable as common supertypes

doublet(“abc”, 123);

wildcard generic method 同时使用时,有以下的特例:

public <T> List<T> test(List<T> list) { return list; }

List<?> wildcardList = new ArrayList<String>();

wildcardList = test(wildcardList);

最后, generic method 中对 type parameter 的使用所必须遵循的规则跟上面所提到的 generic class 的方法中的规则是一样的。

Erasure 的实现方式

Java 5 compiler 中采用 erasure 来实现 generic ,经过 erasure 的处理,所有与 generic 相关的信息将被抹掉( erase ),同时在适当的位置插入显式类型转换,最终形成的 byte code java1.4 byte code 没有什么不一样。

首先, parameterized type ,被还原成其 non-parameterized type ,如 List<String> 将变成 List

其次, type parameter 被替换成它的 bound ,如 T 将变成 Object (假如它的 upper bound Object )。

接着,对于方法类成员的返回值,如果其类型为 parameter type erasure 则会插入显式转换。如:

public class A<T> {

public T get() { return null; }

}

A<String> a = new A<String>();

String temp = a.get();

// translate to

public class A {

public Object get() { return null; }

}

A a = new A();

String temp = (String) a.get();

最后 erasure 将在必要的时候插入 bridge method 。对于以下的代码

public class A<T> {

private T obj;

public void set(T obj) { this.obj = obj; }

public T get() { return obj; }

}

public class B extends A<String> {

public void set(String obj) {};

public String get() { return null;}

}

A<String> a = new B();

a.set(“abc”);

String temp = a.get();

在没有 bridge method 存在的情况下,对于 a 的方法的调用将无法获得多态性的支持,原因是 B 中的方法的 signature A 的不同,所以不被 jvm 视为重载。这时候 erasure 必须在 B 中插入如下的 bridge method

public void set(Object obj) { set((String) obj);}

public Object get() { return get(); }

需要注意的是 get bridge method 在是编译不过的,因为 java 不允许这种形式的 overload ,事实上, bridge method 是直接在 byte code 中插入的。

最后值得注意的是, bridge method 只有在需要的时候被插入,如果 B 不重载 get set 方法,将不会有 bridge method 存在。

由于 runtime 缺乏 generic 相关的信息而导致的各种限制

1.       通过 wildcard 类型的变量访问方法及对象变量受到限制(如上文所述)。

2.       type parameter 相关的显式转化无法保证 type safe ,同时 compiler 会有 warning

List<?> list = new ArrayList<String>();

List<String> strList = (List<String>) list; // warning

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics