知识大全 JDK 5.0中的泛型类型学习

Posted 类型

篇首语:为了生活中努力发挥自己的作用,热爱人生吧。本文由小常识网(cha138.com)小编为大家整理,主要介绍了知识大全 JDK 5.0中的泛型类型学习相关的知识,希望对你有一定的参考价值。

JDK 5.0中的泛型类型学习  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!

  JDK 中增加的泛型类型 是 Java 语言中类型安全的一次重要改进 但是 对于初次使用泛型类型的用户来说 泛型的某些方面看起来可能不容易明白 甚至非常奇怪 在本月的 Java 理论和实践 中 Brian Goetz 分析了束缚第一次使用泛型的用户的常见陷阱 您可以通过讨论论坛与作者和其他读者分享您对本文的看法 (也可以单击本文顶端或底端的讨论来访问这个论坛 )

  表面上看起来 无论语法还是应用的环境(比如容器类) 泛型类型(或者泛型)都类似于 C++ 中的模板 但是这种相似性仅限于表面 Java 语言中的泛型基本上完全在编译器中实现 由编译器执行类型检查和类型推断 然后生成普通的非泛型的字节码 这种实现技术称为擦除(erasure)(编译器使用泛型类型信息保证类型安全 然后在生成字节码之前将其清除) 这项技术有一些奇怪 并且有时会带来一些令人迷惑的后果 虽然范型是 Java 类走向类型安全的一大步 但是在学习使用泛型的过程中几乎肯定会遇到头痛(有时候让人无法忍受)的问题

  注意 本文假设您对 JDK 中的范型有基本的了解

  泛型不是协变的

  虽然将集合看作是数组的抽象会有所帮助 但是数组还有一些集合不具备的特殊性质 Java 语言中的数组是协变的(covariant) 也就是说 如果 Integer 扩展了 Number(事实也是如此) 那么不仅 Integer 是 Number 而且 Integer[] 也是 Number[] 在要求 Number[] 的地方完全可以传递或者赋予 Integer[] (更正式地说 如果 Number 是 Integer 的超类型 那么 Number[] 也是 Integer[] 的超类型) 您也许认为这一原理同样适用于泛型类型 —— List<Number> 是 List<Integer> 的超类型 那么可以在需要 List<Number> 的地方传递 List<Integer> 不幸的是 情况并非如此

  不允许这样做有一个很充分的理由 这样做将破坏要提供的类型安全泛型 如果能够将 List<Integer> 赋给 List<Number> 那么下面的代码就允许将非 Integer 的内容放入 List<Integer>

  List<Integer> li = new ArrayList<Integer>();

  List<Number> ln = li; // illegal

  ln add(new Float( ));

  因为 ln 是 List<Number> 所以向其添加 Float 似乎是完全合法的 但是如果 ln 是 li 的别名 那么这就破坏了蕴含在 li 定义中的类型安全承诺 —— 它是一个整数列表 这就是泛型类型不能协变的原因

  其他的协变问题

  数组能够协变而泛型不能协变的另一个后果是 不能实例化泛型类型的数组(new List<String>[ ] 是不合法的) 除非类型参数是一个未绑定的通配符(new List<?>[ ] 是合法的) 让我们看看如果允许声明泛型类型数组会造成什么后果

  List<String>[] lsa = new List<String>[ ]; // illegal

  Object[] oa = lsa;  // OK because List<String> is a subtype of Object

  List<Integer> li = new ArrayList<Integer>();

  li add(new Integer( ));

  oa[ ] = li;

  String s = lsa[ ] get( );

  最后一行将抛出 ClassCastException 因为这样将把 List<Integer> 填入本应是 List<String> 的位置 因为数组协变会破坏泛型的类型安全 所以不允许实例化泛型类型的数组(除非类型参数是未绑定的通配符 比如 List<?>)

  构造延迟

  因为可以擦除功能 所以 List<Integer> 和 List<String> 是同一个类 编译器在编译 List<V> 时只生成一个类(和 C++ 不同) 因此 在编译 List<V> 类时 编译器不知道 V 所表示的类型 所以它就不能像知道类所表示的具体类型那样处理 List<V> 类定义中的类型参数(List<V> 中的 V)

  因为运行时不能区分 List<String> 和 List<Integer>(运行时都是 List) 用泛型类型参数标识类型的变量的构造就成了问题 运行时缺乏类型信息 这给泛型容器类和希望创建保护性副本的泛型类提出了难题

  比如泛型类 Foo

  class Foo<T>

  public void doSomething(T param)

  

  在这里可以看到一种模式 —— 与泛型有关的很多问题或者折衷并非来自泛型本身 而是保持和已有代码兼容的要求带来的副作用

  泛化已有的类

  在转化现有的库类来使用泛型方面没有多少技巧 但与平常的情况相同 向后兼容性不会凭空而来 我已经讨论了两个例子 其中向后兼容性限制了类库的泛化

  另一种不同的泛化方法可能不存在向后兼容问题 这就是 Collections toArray(Object[]) 传入 toArray() 的数组有两个目的 —— 如果集合足够小 那么可以将其内容直接放在提供的数组中 否则 利用反射(reflection)创建相同类型的新数组来接受结果 如果从头开始重写 Collections 框架 那么很可能传递给 Collections toArray() 的参数不是一个数组 而是一个类文字

  interface Collection<E>

  public T[] toArray(Class<T super E> elementClass);

  

  因为 Collections 框架作为良好类设计的例子被广泛效仿 但是它的设计受到向后兼容性约束 所以这些地方值得您注意 不要盲目效仿

  首先 常常被混淆的泛型 Collections API 的一个重要方面是 containsAll() removeAll() 和 retainAll() 的签名 您可能认为 remove() 和 removeAll() 的签名应该是

  interface Collection<E>

  public boolean remove(E e);  // not really

  public void removeAll(Collection<? extends E> c);  // not really

  

  但实际上却是

  interface Collection<E>

  public boolean remove(Object o);

  public void removeAll(Collection<?> c);

  

  为什么呢?答案同样是因为向后兼容性 x remove(o) 的接口表明 如果 o 包含在 x 中 则删除它 否则什么也不做 如果 x 是一个泛型集合 那么 o 不一定与 x 的类型参数兼容 如果 removeAll() 被泛化为只有类型兼容时才能调用(Collection<? extends E>) 那么在泛化之前 合法的代码序列就会变得不合法 比如

  // a collection of Integers

  Collection c = new HashSet();

  // a collection of Objects

  Collection r = new HashSet();

  c removeAll(r);

  如果上述片段用直观的方法泛化(将 c 设为 Collection<Integer> r 设为 Collection<Object>) 如果 removeAll() 的签名要求其参数为 Collection<? extends E> 而不是 no op 那么就无法编译上面的代码 泛型类库的一个主要目标就是不打破或者改变已有代码的语义 因此 必须用比从头重新设计泛型所使用类型约束更弱的类型约束来定义 remove() removeAll() retainAll() 和 containsAll()

  在泛型之前设计的类可能阻碍了 显然的 泛型化方法 这种情况下就要像上例这样进行折衷 但是如果从头设计新的泛型类 理解 Java 类库中的哪些东西是向后兼容的结果很有意义 这样可以避免不适当的模仿

  擦除的实现

  因为泛型基本上都是在 Java 编译器中而不是运行库中实现的 所以在生成字节码的时候 差不多所有关于泛型类型的类型信息都被 擦掉 了 换句话说 编译器生成的代码与您手工编写的不用泛型 检查程序的类型安全后进行强制类型转换所得到的代码基本相同 与 C++ 不同 List<Integer> 和 List<String> 是同一个类(虽然是不同的类型但都是 List<?> 的子类型 与以前的版本相比 在 JDK 中这是一个更重要的区别)

  擦除意味着一个类不能同时实现 Comparable<String> 和 Comparable<Number> 因为事实上两者都在同一个接口中 指定同一个 pareTo() 方法 声明 DecimalString 类以便与 String 与 Number 比较似乎是明智的 但对于 Java 编译器来说 这相当于对同一个方法进行了两次声明

  public class DecimalString implements Comparable<Number> Comparable<String> // nope

  擦除的另一个后果是 对泛型类型参数是用强制类型转换或者 instanceof 毫无意义 下面的代码完全不会改善代码的类型安全性

  public <T> T naiveCast(T t Object o) return (T) o;

  编译器仅仅发出一个类型未检查转换警告 因为它不知道这种转换是否安全 naiveCast() 方法实际上根本不作任何转换 T 直接被替换为 Object 与期望的相反 传入的对象被强制转换为 Object

  擦除也是造成上述构造问题的原因 即不能创建泛型类型的对象 因为编译器不知道要调用什么构造函数 如果泛型类需要构造用泛型类型参数来指定类型的对象 那么构造函数应该接受类文字(Foo class)并将它们保存起来 以便通过反射创建实例

  结束语

cha138/Article/program/Java/JSP/201405/30952

相关参考

知识大全 Java的泛型原来这样让人不舒服

Java的泛型原来这样让人不舒服  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  Java泛型中的

知识大全 构建可反转排序的泛型字典类(1)--雏形

  前言  前段时间为了查找泛型资料我翻译了OReilly出版的《C#Cookbook》这本书的几个关于泛型的章节其中反转SortedList里的内容(见)这一节中有一个接近行代码的例子当时看到这个例

知识大全 Java编程思想里的泛型实现一个堆栈类 分享

Java编程思想里的泛型实现一个堆栈类分享  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  觉得作

知识大全 C#使用相应的泛型版本替换Stack和Queue

C#使用相应的泛型版本替换Stack和Queue  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  

知识大全 J2SE5.0新特性示例---泛型

J2SE5.0新特性示例---泛型  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  简介  泛型其

知识大全 Eclipse3.1中体验J2SE5.0之泛型(组图)

Eclipse3.1中体验J2SE5.0之泛型(组图)  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧

知识大全 使用泛型实现单例提供者

介绍  很多有着不同开发背景得人都会比较熟悉单例模式他们会发现每次他们要创建一个不同的单例类的时候都不得不写同样得代码使用新的C#的泛型可以实现只写一次同样得代码背景  已经有很多文章介绍过单例模式也

知识大全 java中使用泛型

  泛型是在jdk之后才出现的一种机制说简单点就是一个特定的对象可以接收任何的对象例如杯子可以装水也可以装饮料说简单点就是来者不拒什么都要下面我就来写个小例子看一下  写一个接口Info里面什么方法都

知识大全 泛型类型的子类及通配符的使用

  本文讲述以下几个方面的内容试图说明泛型类型的子类及通配符的使用  ()   子类及替换原则  ()   使用extends关键字的通配符

知识大全 Java高级编程——泛型类型 第一部分

Java高级编程——泛型类型第一部分  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  编辑按《Ja