对于如 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
分享到:
相关推荐
### 黑马程序员入学Java知识(精华总结) #### 一、Java概述与基础知识 ##### 1、何为编程? 编程是指使用计算机语言来编写指令,这些指令被计算机执行以完成特定任务的过程。通过编程,我们可以控制计算机的行为...
### 黑马程序员入学Java精华总结 #### 一、Java概述与基础知识 1. **何为编程?** - 编程是指通过编写计算机能够理解的指令来解决问题或完成特定任务的过程。这些指令通常被组织成算法,并使用某种编程语言实现。...
### Java知识点总结 #### 第1章 Java基础 ##### 1.1 SUN,JAVA,ECLIPSE相关常识 - **James Gosling** 被誉为“Java之父”,他是Java语言的主要设计者之一。 - **Oracle** 收购了Sun Microsystems之后,获得了...
### Java基础知识笔记总结 #### 一、Java概述与基础知识 1. **何为编程?** - 编程是通过特定的计算机语言来编写指令,让计算机能够执行一系列任务的过程。 2. **Java语言概述,历史、特点** - Java是由Sun ...
### Java精华总结 #### 一、Java概述与基础知识 ##### 1. 何为编程? 编程是一种通过编写计算机可以理解的指令来解决问题的过程。这些指令是按照特定的语法规则组织起来的,用来指导计算机执行特定任务。 ##### ...
总结来说,“61850的Java端”涉及了以下几个核心知识点: 1. Java编程语言的基本概念和特性。 2. IEC 61850协议的结构、数据模型和服务。 3. MMS协议的报文解析和生成。 4. GOOSE和SV实时数据传输机制。 5. Java ...
以上只是Java集合框架中Collection接口及其相关子接口和实现类的基础知识。在实际开发中,我们需要根据具体需求选择合适的集合类型,合理利用其特性,优化代码性能。通过实例练习,可以更好地理解和掌握这些概念。...
本文将围绕“p33172858_1036_Generic.zip”这个压缩包文件,详细介绍WebLogic Server 10.3.6.0版本的补丁更新及其相关知识点。 首先,我们看到标题提及的是“10.3.6.0版本”,这代表WebLogic Server的一个特定发行...
### Java Collection Framework 相关知识点 #### 一、引言 在 Java 领域,《Java Collection Framework》这本书被广泛认为是一本优秀的教程,尤其适合初学者了解集合框架的前世今生。通过本书的学习,读者不仅能...
其中,Web Dynpro Table提供了一个强大的TableSorter Java类,使得开发者能够以极简的代码量实现对任何表格数据的排序功能。这不仅简化了开发过程,也提高了应用程序的性能和用户体验。 2. **Web Dynpro表格的过滤...
总结来说,这份"JAVA高级编程资料"涵盖了JAVA开发中的关键知识点,包括多线程的管理与同步、网络编程的基础与优化、文件与流的使用技巧以及集合类的深入理解。这些内容对于任何想在JAVA开发领域深入学习或提升技能的...
Generic [java] 泛型 [dʒi'nerik] goto (保留字) 跳转 heap n.堆 [hi:p] implements (关键字) 实现 ['implimәnt] import (关键字) 引入(进口,输入) Info n.信息 (information [,infә'meiʃәn] ) ...
### Java初级知识要点详解 #### 一、Java概述与基础知识 **1. 何为编程?** 编程是指通过编写计算机程序来实现特定功能的过程。它涉及使用特定的编程语言(如Java),按照一定的规则和逻辑组织代码,以解决具体...
总结一下,Virtuoso OpenSource是一个高性能的数据库系统,尤其适合处理知识图谱和RDF数据,如Freebase。通过其强大的SPARQL支持和丰富的API,开发人员可以在Linux环境中搭建起强大的知识存储和检索平台。在实际应用...