一、介绍
在软件开发过程中,bugs被发现得越早越好。因此,我们要想办法尽量在编译时发现bugs,而不是运行时。
Java 泛型编程为我们提供了一种在编译时多发现一些bugs的方法。
在Java 集合框架中大量使用了泛型编程。
先看一个不用泛型的类Box,它提供了设置与获取object的方法:
public class Box {
private Object object;
public void add(Object object) {
this.object = object;
}
public Object get() {
return object;
}
}
由于object是Object类型的,你可以在object中存储任何类型。
下面是一个BoxDemo,在这里它使用Box来存取整型数据:
public class BoxDemo1 {
public static void main(String[] args) {
// 只把整型数据放到这个Box中!
Box integerBox = new Box();
integerBox.add(new Integer(10));
Integer someInteger = (Integer)integerBox.get();
System.out.println(someInteger);
}
}
在这个程序中你要用Box来存取整型数据,你要告知其他程序员这一约束的方法之一是写一行注释。
但这样做,编译器不会知道,另外粗心的程序员也不会看到,他很可能写出一下代码:
public class BoxDemo2 {
public static void main(String[] args) {
// 只把整型数据放到这个Box中!
Box integerBox = new Box();
// 想象这是一个大程序的一部分
// 它由另一个程序员来维护。
integerBox.add("10"); // 注意现在传入的类型为String
// ... 这是另外一部分,很可能
// 由另一个程序员来编写
Integer someInteger = (Integer)integerBox.get();
System.out.println(someInteger);
}
}
这段代码可以通过编译,但运行时会出以下异常:
Exception in thread "main"
java.lang.ClassCastException:
java.lang.String cannot be cast to java.lang.Integer
at BoxDemo2.main(BoxDemo2.java:6)
如果Box类用泛型实现,这样的错误就可以在编译时发现,而不是运行时。
二、泛型类型
现在我们用泛型来实现Box类。
首先我们进行泛型类型声明:把"public class Box" 改成 "public class Box<T>"。
(注意:泛型类型声明对于接口同样适用。)
T 叫做类型变量,这样声明之后,T 就可以在Box类中使用了。在这种情况下,我们也可以把 T 叫做Box类的正式类型参数。
代码如下:
/**
* Box 类的泛型版本
*/
public class Box<T> {
private T t; // T 表示 "Type"
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
}
要引用这样一个带泛型的类,必须使用泛型类型调用:
Box<Integer> integerBox;
(为什么要叫“调用”呢? 思考下方法调用,如:add(8)。方法调用有圆括号,而泛型类型调用有尖括号,形式上有点像啦。)
一个泛型类型调用通常也叫做参数化类型。
那如何实例化这个类呢?
integerBox = new Box<Integer>();
实例化后,你就可以使用这个类的实例方法了:
public class BoxDemo3 {
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
integerBox.add(new Integer(10));
Integer someInteger = integerBox.get(); // 没有进行类型转换!
System.out.println(someInteger);
}
}
这样,当你想向add()方法传入一些与 T(本例为Integer)类型不兼容的类型,如String类型时,就会出现编译错误:
BoxDemo3.java:5: add(java.lang.Integer) in Box<java.lang.Integer>
cannot be applied to (java.lang.String)
integerBox.add("10");
^
1 error
泛型类型声明中使用的T并不是实际存在的一种类型,它也不是类名Box的一部分。实际上,在编译过程中,所有的泛型信息都会被去掉,这在后面的类型擦除中会介绍。
在泛型类型声明中可以使用多个符号,但各个符号不能相同。如,你可以声明Box<T,U>,而声明Box<T,T>就会报错。
类型参数的命名规范:
E - Element (在Java集合框架中被大量使用)
K - Key
N - Number
T - Type
V - Value
S,U,V etc. - 2nd, 3rd, 4th types
三、带泛型的方法和构造函数
类型参数也可以被声明在方法和构造函数的签名当中,使它们成为带泛型的方法和构造函数。
这与声明泛型类型没什么不同,只不过类型的作用域不同罢了。
/**
* 此版本引进带泛型的方法。
*/
public class Box<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
public <U> void inspect(U u){
System.out.println("T: " + t.getClass().getName());
System.out.println("U: " + u.getClass().getName());
}
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
integerBox.add(new Integer(10));
integerBox.inspect("some text");
}
}
程序的输出是:
T: java.lang.Integer
U: java.lang.String
传入不同的类型,输出就会相应的改变。
带泛型的方法更为实际的用途可能会像下面这段代码:
public static <U> void fillBoxes(U u, List<Box<U>> boxes) {
for (Box<U> box : boxes) {
box.add(u);
}
}
为了使用这个方法,你的代码可能会是这样:
Crayon red = ...;
List<Box<Crayon>> crayonBoxes = ...;
调用这个方法的完整格式是:
Box.<Crayon>fillBoxes(red, crayonBoxes);
另一种较为简便的调用格式是:
Box.fillBoxes(red, crayonBoxes); // 编译器推断出 U 是 Crayon 类型
这一特性叫做类型推断,它可以使你像调用普通方法那样调用带泛型的方法。
四、限定类型参数
有时候你可能想限制可以被传入的类型。比如一个操作数字的方法,它只想接受Number以及Number的子类的实例。这时候就可以用限定类型参数了。
声明一个限定类型参数的方法:写出类型参数名+extend(对于后面的上界是类用extend,若是接口用implement)+上界。
/**
* 此版本引进一个限定类型参数
*/
public class Box<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
public <U extends Number> void inspect(U u){
System.out.println("T: " + t.getClass().getName());
System.out.println("U: " + u.getClass().getName());
}
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
integerBox.add(new Integer(10));
integerBox.inspect("some text"); // 错误: 这还是一个字符串!
}
}
以上代码会出现编译错误,应为String不是Number类或Number的子类:
Box.java:21: <U>inspect(U) in Box<java.lang.Integer> cannot
be applied to (java.lang.String)
integerBox.inspect("10");
^
1 error
如果还想规定更多的实现接口,用 & 把限定类连起来:
<U extends Number & MyInterface>
五、子类
父类参数可以引用子类实例。这在面向对象领域叫做"is a"关系。
如:
Object someObject = new Object();
Integer someInteger = new Integer(10);
someObject = someInteger; // OK
这在泛型编程中同样适用:
Box<Number> box = new Box<Number>();
box.add(new Integer(10)); // OK
box.add(new Double(10.1)); // OK
现在,思考如下方法:
public void boxTest(Box<Number> n){
// method body omitted
}
这个方法接受什么样的参数呢?从方法签名来看,它接受Box<Number>。
那它能否接受Box<Integer> 或 Box<Double> 呢?也许你认为能。
但结果却出乎意料,答案是:不能!因为 Box<Integer> 或 Box<Double>并不是Box<Number>的子类,它们不兼容。
想象有一个Cage<Animal>类,它可以装Animal类以及Animal类的子类,即它足够大可以装狮子,它的栏杆足够密也可以装蝶。
而Cage<Lion>只是一个能盛下狮子的大笼子,Cage<Butterfly>只是一个栏杆足够密的小笼子。
现在你想要一个可以盛所有动物的万能笼子,有时可以关狮子,有时可以装蝴蝶。
而我却给你一个只能装狮子的大笼子或一个只能装蝴蝶的小笼子,这意味着你会失去狮子或蝴蝶,你会满意吗?
六、通配符
现在假设我就要一个笼子,它不是万能的,它只能装一种动物,但具体装狮子或者蝴蝶我还不确定,这就要使用限定通配了。
怎样声明这样一个笼子呢?
Cage<? extends Animal> someCage = ...;
在这里,? 就是一个限定通配符,Animal是上界。
你也可以声明下界<? super Animal>。
虽然Cage<Lion> 和 Cage<Butterfly>不是Cage<Animal>的子类,可它们是Cage<? extends Animal>的子类。
Cage<Animal> 是一个能装任意动物的笼子,而Cage<? extends Animal>是一个只能装一种动物的笼子,所以狮子笼子或蝴蝶笼子都是这样的笼子。
七、类型擦除
当泛型类型的类被实例化时,编译器使用类型擦除技术,把所有有关类型的信息都去掉,以使编译后的程序能与泛型出现以前的Java类库或程序兼容。
比如Box<String>被编译成Box类,Box类叫做原始类型,原始类型是不带参数类型的泛型类名(或接口名)。
这意味着你无法知晓在运行时某个泛型类使用的是那种具体的类型。下面的操作是不可行的:
public class MyClass<E> {
public static void myMethod(Object item) {
if (item instanceof E) { //Compiler error
...
}
E item2 = new E(); //Compiler error
E[] iArray = new E[10]; //Compiler error
E obj = (E)new Object(); //Unchecked cast warning
}
}
当传统代码与泛型代码混合使用时,编译器可能会给出如下警告信息:
Note: WarningDemo.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
如:
public class WarningDemo {
public static void main(String[] args){
Box<Integer> bi;
bi = createBox();
}
static Box createBox(){
return new Box();
}
}
用 -Xlint:unchecked 选项重新编译代码,可得到详细信息:
WarningDemo.java:4: warning: [unchecked] unchecked conversion
found : Box
required: Box<java.lang.Integer>
bi = createBox();
^
1 warning
------------------------
词汇表:
泛型 Generics
泛型类型 Generic Types
类型变量 Type variables
类型参数 Type Parameters
限定类型参数 Bounded Type Parameters
子类 Subtyping
通配符 Wildcards
类型擦除 Type Erasure
泛型类型调用 generic type invocation
参数化类型 parameterized type
类型推断 type inference
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/ax003d/archive/2008/11/20/3342467.aspx
分享到:
相关推荐
泛型编程 [翻译]Java泛型编程指南(上).htm
SUN公司的Java泛型编程文档,包括英文原版和网络翻译版,为开发者提供了深入理解和熟练运用泛型的重要资源。 首先,泛型的基本概念是允许在定义类、接口和方法时使用类型参数,这样就可以在编译时检查类型安全,...
Java 泛型编程是一种强大的工具,它允许我们在编写代码时引入类型参数,从而提高了代码的灵活性和安全性。泛型在Java中主要应用于类、接口和方法,使得程序员可以在编译时检查类型安全,并且可以消除运行时的类型...
Java泛型是Java编程语言中的一个关键特性,它在2004年随着...总的来说,Java泛型编程是一种强大的工具,能够提升代码的清晰度、安全性和可维护性。通过深入学习和实践,我们可以更高效地利用泛型来设计和实现复杂系统。
标题和描述均聚焦于“基于Java的泛型编程”,这一主题深入探讨了Java语言中泛型编程的概念、优点以及其实现机制。以下是对这一主题的详细解析,旨在全面阐述泛型编程在Java中的应用及其重要性。 ### 泛型编程的意义...
### Java泛型编程详解 #### 一、引言 Java泛型编程是在JDK 1.5版本之后引入的一项重要特性,它允许开发者在编译时检查类型安全,并且所有的强制转换都由编译器自动完成,从而显著减少了运行时出现的`...
这是一个使用JAVA实现的泛型编程,分为两部分,第一部分创建泛型类,并实例化泛型对象,得出相加结果。 第二部分用户自行输入0--4,选择要进行的加减乘除运算或退出,再输入要进行运算的两个数,并返回运算结果及...
C++程序设计与实践:模板和泛型编程 C++ 程序设计与实践中,模板和泛型编程是非常重要的概念。模板是 C++ 的泛型机制,用来实现泛型编程。泛型编程是指不依赖于任何具体类型来编写通用代码,具体类型信息的提供是在...
java泛型详解.pdf
Java泛型是Java编程语言中的一种重要特性,它允许开发者在编写代码时指定类型参数,从而提高代码的灵活性和可读性。本文将详细介绍Java泛型的用法 及T.class的获取过程解析。 一、泛型的基本概念 泛型是Java 5中...
泛型是Java编程中的一项重要特性,它允许我们在定义类、接口、方法时使用类型参数,从而实现更灵活、安全的代码设计。泛型的主要目的是提高类型安全性,减少强制类型转换,以及提供编译时的类型约束。 在传统的Java...
在Java编程语言中,泛型(Generics)是一种强大的特性,它允许我们在编写代码时指定容器(如集合)可以存储的数据类型。这提高了代码的安全性和效率,因为编译器可以在编译时检查类型,避免了运行时...
Java泛型是Java编程语言中的一个强大特性,它允许我们在定义类、接口和方法时指定类型参数,从而实现代码的重用和类型安全。在Java泛型应用实例中,我们可以看到泛型如何帮助我们提高代码的灵活性和效率,减少运行时...
Java泛型是Java编程语言中的一个关键特性,它在2004年随着Java SE 5.0的发布而引入,极大地增强了代码的类型安全性和重用性。本篇文章将深入探讨Java泛型的发展历程、核心概念以及其在实际开发中的应用。 1. **发展...