1. 介绍
JDK1.5中引入了对java语言的多种扩展,泛型(generics)即其中之一。
把一个list中的内容限制为一个特定的数据类型呢?这是generics背后的核心思想。这是上面程序片断的一个泛型版本:
List<Integer> myIntList = new LinkedList<Integer>(); // 1
myIntList.add(new Integer(0)); // 2
Integer x = myIntList.iterator().next(); // 3
2. 定义简单的泛型
下面是从java.util包中的List接口和Iterator接口的定义中摘录的片断:
public interface List<E> {
void add(E x);
Iterator<E> iterator();
}
public interface Iterator<E> {
E next();
boolean hasNext();
}
类型参数在整个类的声明中可用,几乎是所有可是使用其他普通类型的地方(但是有些重要的限制,请参考第7部分)。
一个泛型类型的声明只被编译一次,并且得到一个class文件,就像普通的class或者interface的声明一样。
类型参数就跟在方法或构造函数中普通的参数一样。就像一个方法有形式参数(formal value parameters)来描述它操作的参数的种类一样,一个泛型声明也有形式类型参数(formal type parameters)。当一个方法被调用,实参(actual arguments)替换形参,方法体被执行。当一个泛型声明被调用,实际类型参数(actual type arguments)取代形式类型参数。
3. 泛型和子类继承
让我们测试一下我们对泛型的理解。下面的代码片断合法么?
List<String> ls = new ArrayList<String>(); //1
List<Object> lo = ls; //2
lo.add(new Object()); // 3
String s = ls.get(0); // 4: 试图把Object赋值给String
java编译器当然会阻止这种情况的发生。第2行会导致一个编译错误。
总之,如果Foo是Bar的一个子类型(子类或者子接口),而G是某种泛型声明,那么G<Foo>是G<Bar>的子类型并不成立!!
4. 通配符(Wildcards)
什么是各种collections的父类呢?它写作: Collection<?>(发音为:"collection of unknown"),就是,一个集合,它的元素类型可以匹配任何类型。显然,它被称为通配符。将任意元素加入到其中不是类型安全的:
Collection<?> c = new ArrayList<String>();
c.add(new Object()); // 编译时错误
add方法有类型参数E作为集合的元素类型。我们传给add的任何参数都必须是一个未知类型的子类。因为我们不知道那是什么类型,所以我们无法传任何东西进去。唯一的例外是null,它是所有类型的成员。
4.1. 有限制的通配符(Bounded Wildcards)
方法能够接受一个任意种类的shape:
public void drawAll(List<? extends Shape> shapes) { //..}
List<? extends Shape>是有限制通配符的一个例子。这里?代表一个未知的类型,就像我们前面看到的通配符一样。但是,在这里,我们知道这个未知的类型实际上是Shape的一个子类(它可以是Shape本身或者Shape的子类而不必是extends自Shape)。我们说Shape是这个通配符的上限(upper bound)。
5. 泛型方法
考虑写一个方法,它用一个Object的数组和一个collection作为参数,完成把数组中所有object放入collection中的功能。
使用generic methods。就像类型声明,方法的声明也可以被泛型化——就是说,带有一个或者多个类型参数。
static <T> void fromArrayToCollection(T[] a, Collection<T> c){
for (T o : a) {
c.add(o); // correct
}
}
现在有一个问题:我们应该什么时候使用泛型方法,又什么时候使用通配符类型呢?
泛型函数允许类型参数被用来表示方法的一个或多个参数之间的依赖关系,或者参数与其返回值的依赖关系。如果没有这样的依赖关系,不应该使用泛型方法。
一前一后的同时使用泛型方法和通配符也是可能的。下面是方法 Collections.copy():
class Collections {
public static <T> void copy(List<T> dest, List<? extends T> src){...}
}
6. 与旧代码交互
直到现在,我们的例子中都假定了一个理想的世界,那里所有人使用的都是最新版本的java编程语言,它支持泛型。
唉,现实并非如此。百万行代码都是在早先版本的语言下写作的,他们不可能一晚上就转换过来。
后面,在第10部分,我们会解决把老代码转换为使用泛型的代码的问题。在这里,我们把注意力放在一个更简单的问题:老代码怎么和泛型代码交互?这个问题包括两部分:在泛型中使用老代码和在老代码中使用泛型代码。
6.1. 在泛型代码中使用老代码
怎样才能使用老代码的同时在自己的代码中享受泛型带来的好处?
public static void addAssembly(String name, Collection parts) {...} Collection getParts(); // Returns a collection of Parts
Collection<Part> c = new ArrayList<Part>();
Inventory.addAssembly(”thingee”, c);
Collection<Part> k = Inventory.getAssembly(”thingee”).getParts();
在严格的泛型代码里,Collection应该总是带着类型参数。当一个泛型类型,比如Collection被使用而没有类型参数时,它被称作一个raw type(自然类型??)。类型Collection表示一个未知类型元素的集合,就像Collection<?>,这样说更准确。
但是等一下,那也不正确。考虑getParts()这个调用,它返回一个Collection。然后它被赋值给k,而k是Collection<Part>。如果这个调用的结果是一个Collection<?>,这个赋值应该是一个错误。事实上,这个赋值是合法的,但是它产生一个未检查警告(unchecked warning)。这个警告是必要的,因为事实是编译器无法保证其正确性。我们没有办法检查getAssembly()中的旧代码来保证返回的确实是一个Collection<Part>。
从泛型代码中调用老代码具有先天的危险性,一旦你把泛型编程和非泛型编程混合起来,泛型系统所提供的所有安全保证都失效。然而,你还是比你根本不用泛型要好。至少你知道你这一端的代码是稳定的。
6.2. 擦除和翻译(Erasure and Translation)
List ys = new LinkedList();
List xs = ys;
xs.add(x);
return (String) ys.iterator().next(); // run time error
当我们从list中获取一个元素的时候,并且试图通过转换为String而把它当作一个string,我们得到一个 ClassCastException。完全一样的事情发生在使用泛型的代码上。
这样的原因是,泛型是通过java编译器的称为擦除(erasure)的前端处理来实现的。你可以(基本上就是)把它认为是一个从源码到源码的转换,它把泛型版本的loophole()转换成非泛型版本。
7. 要点(The Fine Print)
7.1. 一个泛型类被其所有调用共享
下面的代码打印的结果是什么?
List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
System.out.println(l1.getClass() == l2.getClass());
或许你会说false,但是那你就错了。它打印出true。因为所有的泛型类型在运行时有同样的类(class),而不管他们的实际类型参数。
事实上,泛型之所以为泛型就是因为它对所有其可能的类型参数,它有同样的行为;同样的类可以被当作许多不同的类型。
7.2. 转型和instanceof
泛型类被所有其实例(instances)共享的另一个暗示是检查一个实例是不是一个特定类型的泛型类是没有意义的。
类型参数在运行时并不存在。这意味着它们不会添加任何的时间或者空间上的负担,这很好。不幸的是,这也意味着你不能依靠他们进行类型转换。
7.3. 数组Arrays
数组对象的组成类型不能是一个类型变量或者类型参数,除非它是无上限的通配符类型。为了避免下面的情况,必须有这样的限制:
如果参数化类型可以是数组,那么意味着上面的例子可以没有任何unchecked warnings的通过编译,但是在运行时失败。我们把类型安全(type-safety)作为泛型首要的设计目标。特别的,java语言被设计为保证:如果你的整个程序没有unchecked warnings的使用javac –source1.5通过编译,那么它是类型安全的(原文: if your entire application has been compiled without unchecked warnings using javac -source 1.5, it is type safe)。
8. Class Literals as Run-time Type Tokens
JDK1.5中一个变化是类 java.lang.Class是泛型化的。这是把泛型作为容器类之外的一个很有意思的例子(using genericity for something other than a container class)。
现在,Class有一个类型参数T, 你很可能会问,T 代表什么?
它代表Class对象代表的类型。比如说,String.class类型代表 Class<String>,Serializable.class代表 Class<Serializable>。着可以被用来提高你的反射代码的类型安全。
特别的,因为 Class的 newInstance() 方法现在返回一个T, 你可以在使用反射创建对象时得到更精确的类型。
public static <T> Collection<T> select(Class<T>c, String sqlStatement) {
Collection<T> result = new ArrayList<T>();
/* run sql query using jdbc */
for ( /* iterate over jdbc results */ ) {
T item = c.newInstance();
/* use reflection and set all of item’s fields from sql results */
result.add(item);
}
return result;
}
9. More fun with *
在这一部分,我们来考虑一些通配符得高级用法。
使用一种我们还没有见过的有限制的通配符:有下限的通配符。语法 ? super T 表示T的一个未知的父类(或者是T自己)。这跟我们用? extends T 表示T的一个未知的子类是对应的。
<T> T writeAll(Collection<T> coll, Sink<? super T> snk) { … }
String str = writeAll(cs, s); // YES!!!
作为使用下限通配符最终的例子,让我们来看看方法 Collections.max(),它返回一个集合中的最大的元素。
T 精确的(exactly)和自己能比较是不需要的。所需要的是 T能够和它的父类中的一个进行比较,这导出:(注:Collections.max()的实际方法签名更复杂,我们在第10部分再讨论。)
public static <T extends Comparable<? super T>> T max(Collection<T> coll)
这个推论对大多数想让 Comparable 对任意类型生效的用法中都有效:你总是应该使用 Comparable<? super T>。
总之,如果你有一个只使用类型参数T作为参数的API,它的使用应该利用下限通配符( ? super T )的好处。相反的,如果API只返回T,你应该使用上限通配符( ? extends T )来给你的客户端更大的灵活性。
10. 泛型化老代码
你还应该保证修订过的API保持与老客户端的二进制兼容。API的erasure必须与老的未泛型化版本一样。在大多数情况下,这是很自然的结果,但是有些精巧的情形(subtle cases)。我们看看我们已经碰到过的精巧的情形中的一个(one of the subtle cases),方法Collections.max()。就像我们在第9部分看到的,一个似是而非的max()的方法签名是:
public static <T extends Comparable<? super T>> T max(Collection<T> coll)
这很好,除了擦除(erasure)后的签名是:
public static Comparable max(Collection coll)
这和老版本的max() 的签名不同:
public static Object max(Collection coll)
当然可以把max()定义为这个签名,但是这没有成为现实,因为所有调用了Collections.max()的老的二进制class文件依赖于返回Object的签名。
我们可以强迫the erasure不同,通过给形式类型参数T显式的定义一个父类。
public static <T extends Object & Comparable<? super T>> T max(Collection<T> coll)
这是一个对一个类型参数给定多个界限(multiple bounds)的例子,是用语法 T1 & T2 … & Tn。一个有多个界限的类型的参数是所有界限中列出来的类型的子类。当多个界限被使用的时候,界限中的第一个类型被用作这个类型参数的erasure。
相关推荐
Java 5 泛型是Java编程语言中一个重要的里程碑,它引入了类型安全的集合,大大增强了代码的可读性和可维护性。泛型在Java 5中首次亮相,为开发者提供了一种方式来限制集合(如List、Set、Map等)中可以存储的数据...
虽然Java泛型在运行时会被擦除,无法直接获取到类型参数的实际类型(即`T.class`),但可以通过反射机制获取到类型信息。下面是一个典型的例子: ```java public abstract class BaseHibernateEntityDao<T> extends...
Java 5 引入了泛型和反射两个重要的特性,极大地增强了编程的灵活性和安全性。泛型主要是为了解决在编程中频繁进行类型转换的问题,而反射则允许程序在运行时动态地获取类的信息和调用方法。 泛型类型: 在 Java 5 ...
Java 实现泛型List的源码,基本实现了List接口的全部所有方法。欢迎大家发表自己的观点和建议。
2.java定义泛型类.zip2.java定义泛型类.zip2.java定义泛型类.zip2.java定义泛型类.zip2.java定义泛型类.zip2.java定义泛型类.zip2.java定义泛型类.zip2.java定义泛型类.zip2.java定义泛型类.zip2.java定义泛型类.zip...
在编程世界中,C#和Java都是广泛应用的高级编程语言,它们都支持泛型这一强大的特性,以提高代码的类型安全性和重用性。本文将深入探讨C#和Java在泛型实现上的异同,帮助开发者更好地理解和利用这两种语言的泛型功能...
这是一个使用JAVA实现的泛型编程,分为两部分,第一部分创建泛型类,并实例化泛型对象,得出相加结果。 第二部分用户自行输入0--4,选择要进行的加减乘除运算或退出,再输入要进行运算的两个数,并返回运算结果及...
综上所述,虽然Java泛型在编译后会进行类型擦除,但通过上述技巧,我们仍然能够在运行时获得关于泛型类实例化类型的一些信息。在实际开发中,这些方法可以帮助我们编写更加灵活和安全的代码。在示例文件`GenericRTTI...
其次,泛型是Java SE 5引入的新特性,用于在编译时提供类型安全,并消除在运行时的类型检查和强制转换。在动态数据库操作中,泛型可以用来创建通用的DAO(数据访问对象)接口和实现,以处理不同类型的实体对象。例如...
感谢所有为Java泛型做出贡献的人们,包括设计者、实现者以及提供反馈和支持的社区成员。泛型是Java语言的一个重要特性,极大地提高了代码的质量和可维护性。 以上就是基于给定文件信息对Java 1.5泛型指南的主要知识...
泛型是Java编程语言中的一个重要特性,它引入于JDK 5.0,极大地提高了代码的类型安全性和可读性。泛型允许我们在类、接口和方法中使用类型参数,这样我们可以在编译时检查类型,避免了运行时类型转换的麻烦和潜在的...
泛型是Java语言的一个重要特性,首次出现在Java SE 1.5版本中。它的引入主要是为了解决在集合操作中类型安全性的问题,并通过引入参数化类型的概念,提高了代码的复用性与可读性。 ### 泛型概念 泛型,即参数化...
Java泛型的用法及T.class的获取过程解析 Java泛型是Java编程语言中的一种重要特性,它允许开发者在编写代码时指定类型参数,从而提高代码的灵活性和可读性。本文将详细介绍Java泛型的用法 及T.class的获取过程解析...
在Java编程语言中,泛型是一种强大的特性,它...总的来说,理解和掌握Java泛型类的继承应用是提高代码质量和可维护性的关键一步。通过这个压缩包中的源码,你将有机会实践和探索这一主题,进一步提升你的Java编程技能。
Java泛型是Java SE 5.0引入的一个重要特性,它极大地增强了代码的类型安全性和可读性。泛型在编程中的应用广泛,特别是在集合框架中,使得我们可以在编译时就检查类型,避免了不必要的类型转换,并且提高了代码的...
1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1.java泛型定义.zip1....
在Java编程语言中,泛型是一种强大的特性,它允许我们在类、接口和方法中使用类型参数,从而提高代码的灵活性和安全性。当我们谈论“继承泛型类”时,这意味着一个类(子类)正在扩展一个已经定义了泛型的类(父类)...
Java 泛型是一种强大的工具,它允许我们在编程时指定变量的类型,提供了编译时的类型安全。然而,Java 的泛型在运行时是被擦除的,这意味着在运行时刻,所有的泛型类型信息都会丢失,无法直接用来创建对象或进行类型...