发布日期:2009-12-15 5:31:46
Java泛型
泛型是Java 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
Java泛型被引入的好处是安全简单。
在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。
泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。
泛型在使用中还有一些规则和限制:
1、泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。
2、同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。
3、泛型的类型参数可以有多个。
4、泛型的参数类型可以使用extends语句,例如。习惯上成为“有界类型”。
5、泛型的参数类型还可以是通配符类型。例如Class classType = Class.forName(java.lang.String);
泛型还有接口、方法等等,内容很多,需要花费一番功夫才能理解掌握并熟练应用。在此给出我曾经了解泛型时候写出的两个例子(根据看的印象写的),实现同样的功能,一个使用了泛型,一个没有使用,通过对比,可以很快学会泛型的应用,学会这个基本上学会了泛型70%的内容。
例子一:使用了Java泛型
public class Gen {
private T ob; //定义泛型成员变量
public Gen(T ob) {
this.ob = ob;
}
public T getOb() {
return ob;
}
public void setOb(T ob) {
this.ob = ob;
}
public void showTyep() {
System.out.println("T的实际类型是: " + ob.getClass().getName());
}
}
public class GenDemo {
public static void main(String[] args){
//定义泛型类Gen的一个Integer版本
Gen intOb=new Gen(88);
intOb.showTyep();
int i= intOb.getOb();
System.out.println("value= " + i);
System.out.println("----------------------------------");
//定义泛型类Gen的一个String版本
Gen strOb=new Gen("Hello Gen!");
strOb.showTyep();
String s=strOb.getOb();
System.out.println("value= " + s);
}
}
例子二:没有使用泛型
public class Gen2 {
private Object ob; //定义一个通用类型成员
public Gen2(Object ob) {
this.ob = ob;
}
public Object getOb() {
return ob;
}
public void setOb(Object ob) {
this.ob = ob;
}
public void showTyep() {
System.out.println("T的实际类型是: " + ob.getClass().getName());
}
}
public class GenDemo2 {
public static void main(String[] args) {
//定义类Gen2的一个Integer版本
Gen2 intOb = new Gen2(new Integer(88));
intOb.showTyep();
int i = (Integer) intOb.getOb();
System.out.println("value= " + i);
System.out.println("----------------------------------");
//定义类Gen2的一个String版本
Gen2 strOb = new Gen2("Hello Gen!");
strOb.showTyep();
String s = (String) strOb.getOb();
System.out.println("value= " + s);
}
}
运行结果:
两个例子运行Demo结果是相同的,控制台输出结果如下:
T的实际类型是:
java.lang.Integer
value= 88
----------------------------------
T的实际类型是: java.lang.String
value= Hello Gen!
Process finished with exit code 0
看明白这个,以后基本的Java泛型应用和代码阅读就不成问题了。
虽然Scala创始人Martin Odersky说当年正是因为Java泛型的丑陋,所以才想到要创建一个新的语言,不过这仍然不妨碍我们学习Java泛型。毕竟即使听说Java泛型不好用,但好不好用还是得会用了才知道。下面是一些有关Java泛型的总结:
普通泛型
class Point< T>{ // 此处可以随便写标识符号,T是type的简称
private T var ; // var的类型由T指定,即:由外部指定
public T getVar(){ // 返回值的类型由外部决定
return var ;
}
public void setVar(T var){ // 设置的类型也由外部决定
this.var = var ;
}
};
public class GenericsDemo06{
public static void main(String args[]){
Point< String> p = new Point< String>() ; // 里面的var类型为String类型
p.setVar("it") ; // 设置字符串
System.out.println(p.getVar().length()) ; // 取得字符串的长度
}
};
----------------------------------------------------------
class Notepad< K,V>{ // 此处指定了两个泛型类型
private K key ; // 此变量的类型由外部决定
private V value ; // 此变量的类型由外部决定
public K getKey(){
return this.key ;
}
public V getValue(){
return this.value ;
}
public void setKey(K key){
this.key = key ;
}
public void setValue(V value){
this.value = value ;
}
};
public class GenericsDemo09{
public static void main(String args[]){
Notepad< String,Integer> t = null ; // 定义两个泛型类型的对象
t = new Notepad< String,Integer>() ; // 里面的key为String,value为Integer
t.setKey("汤姆") ; // 设置第一个内容
t.setValue(20) ; // 设置第二个内容
System.out.print("姓名;" + t.getKey()) ; // 取得信息
System.out.print(",年龄;" + t.getValue()) ; // 取得信息
}
};
通配符
class Info< T>{
private T var ; // 定义泛型变量
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
public String toString(){ // 直接打印
return this.var.toString() ;
}
};
public class GenericsDemo14{
public static void main(String args[]){
Info< String> i = new Info< String>() ; // 使用String为泛型类型
i.setVar("it") ; // 设置内容
fun(i) ;
}
public static void fun(Info< ?> temp){ // 可以接收任意的泛型对象
System.out.println("内容:" + temp) ;
}
};
受限泛型
class Info< T>{
private T var ; // 定义泛型变量
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
public String toString(){ // 直接打印
return this.var.toString() ;
}
};
public class GenericsDemo17{
public static void main(String args[]){
Info< Integer> i1 = new Info< Integer>() ; // 声明Integer的泛型对象
Info< Float> i2 = new Info< Float>() ; // 声明Float的泛型对象
i1.setVar(30) ; // 设置整数,自动装箱
i2.setVar(30.1f) ; // 设置小数,自动装箱
fun(i1) ;
fun(i2) ;
}
public static void fun(Info< ? extends Number> temp){ // 只能接收Number及其Number的子类
System.out.print(temp + "、") ;
}
};
----------------------------------------------------------
class Info< T>{
private T var ; // 定义泛型变量
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
public String toString(){ // 直接打印
return this.var.toString() ;
}
};
public class GenericsDemo21{
public static void main(String args[]){
Info< String> i1 = new Info< String>() ; // 声明String的泛型对象
Info< Object> i2 = new Info< Object>() ; // 声明Object的泛型对象
i1.setVar("hello") ;
i2.setVar(new Object()) ;
fun(i1) ;
fun(i2) ;
}
public static void fun(Info< ? super String> temp){ // 只能接收String或Object类型的泛型
System.out.print(temp + "、") ;
}
};
Java泛型无法向上转型
class Info< T>{
private T var ; // 定义泛型变量
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
public String toString(){ // 直接打印
return this.var.toString() ;
}
};
public class GenericsDemo23{
public static void main(String args[]){
Info< String> i1 = new Info< String>() ; // 泛型类型为String
Info< Object> i2 = null ;
i2 = i1 ; //这句会出错 incompatible types
}
};
Java泛型接口
interface Info< T>{ // 在接口上定义泛型
public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型
}
class InfoImpl< T> implements Info< T>{ // 定义泛型接口的子类
private T var ; // 定义属性
public InfoImpl(T var){ // 通过构造方法设置属性内容
this.setVar(var) ;
}
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
};
public class GenericsDemo24{
public static void main(String arsg[]){
Info< String> i = null; // 声明接口对象
i = new InfoImpl< String>("汤姆") ; // 通过子类实例化对象
System.out.println("内容:" + i.getVar()) ;
}
};
----------------------------------------------------------
interface Info< T>{ // 在接口上定义泛型
public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型
}
class InfoImpl implements Info< String>{ // 定义泛型接口的子类
private String var ; // 定义属性
public InfoImpl(String var){ // 通过构造方法设置属性内容
this.setVar(var) ;
}
public void setVar(String var){
this.var = var ;
}
public String getVar(){
return this.var ;
}
};
public class GenericsDemo25{
public static void main(String arsg[]){
Info i = null; // 声明接口对象
i = new InfoImpl("汤姆") ; // 通过子类实例化对象
System.out.println("内容:" + i.getVar()) ;
}
};
Java泛型方法
class Demo{
public < T> T fun(T t){ // 可以接收任意类型的数据
return t ; // 直接把参数返回
}
};
public class GenericsDemo26{
public static void main(String args[]){
Demo d = new Demo() ; // 实例化Demo对象
String str = d.fun("汤姆") ; // 传递字符串
int i = d.fun(30) ; // 传递数字,自动装箱
System.out.println(str) ; // 输出内容
System.out.println(i) ; // 输出内容
}
};
通过泛型方法返回泛型类型实例
class Info< T extends Number>{ // 指定上限,只能是数字类型
private T var ; // 此类型由外部决定
public T getVar(){
return this.var ;
}
public void setVar(T var){
this.var = var ;
}
public String toString(){ // 覆写Object类中的toString()方法
return this.var.toString() ;
}
};
public class GenericsDemo27{
public static void main(String args[]){
Info< Integer> i = fun(30) ;
System.out.println(i.getVar()) ;
}
public static < T extends Number> Info< T> fun(T param){//方法中传入或返回的泛型类型由调用方法时所设置的参数类型决定
Info< T> temp = new Info< T>() ; // 根据传入的数据类型实例化Info
temp.setVar(param) ; // 将传递的内容设置到Info对象的var属性之中
return temp ; // 返回实例化对象
}
};
使用泛型统一传入的参数类型
class Info< T>{ // 指定上限,只能是数字类型
private T var ; // 此类型由外部决定
public T getVar(){
return this.var ;
}
public void setVar(T var){
this.var = var ;
}
public String toString(){ // 覆写Object类中的toString()方法
return this.var.toString() ;
}
};
public class GenericsDemo28{
public static void main(String args[]){
Info< String> i1 = new Info< String>() ;
Info< String> i2 = new Info< String>() ;
i1.setVar("HELLO") ; // 设置内容
i2.setVar("汤姆") ; // 设置内容
add(i1,i2) ;
}
public static < T> void add(Info< T> i1,Info< T> i2){
System.out.println(i1.getVar() + " " + i2.getVar()) ;
}
};
Java泛型数组
public class GenericsDemo30{
public static void main(String args[]){
Integer i[] = fun1(1,2,3,4,5,6) ; // 返回泛型数组
fun2(i) ;
}
public static < T> T[] fun1(T...arg){ // 接收可变参数
return arg ; // 返回泛型数组
}
public static < T> void fun2(T param[]){ // 输出
System.out.print("接收泛型数组:") ;
for(T t:param){
System.out.print(t + "、") ;
}
}
};
Java泛型的嵌套设置
class Info< T,V>{ // 接收两个泛型类型
private T var ;
private V value ;
public Info(T var,V value){
this.setVar(var) ;
this.setValue(value) ;
}
public void setVar(T var){
this.var = var ;
}
public void setValue(V value){
this.value = value ;
}
public T getVar(){
return this.var ;
}
public V getValue(){
return this.value ;
}
};
class Demo< S>{
private S info ;
public Demo(S info){
this.setInfo(info) ;
}
public void setInfo(S info){
this.info = info ;
}
public S getInfo(){
return this.info ;
}
};
public class GenericsDemo31{
public static void main(String args[]){
Demo< Info< String,Integer>> d = null ; // 将Info作为Demo的泛型类型
Info< String,Integer> i = null ; // Info指定两个泛型类型
i = new Info< String,Integer>("汤姆",30) ; // 实例化Info对象
d = new Demo< Info< String,Integer>>(i) ; // 在Demo类中设置Info类的对象
System.out.println("内容一:" + d.getInfo().getVar()) ;
System.out.println("内容二:" + d.getInfo().getValue()) ;
}
};
学习Java的泛型介绍
通常,缺陷严重影响着大型程序和软件的使用。通过周密的设计、编码和测试,或许可以减少一些缺陷,但是缺陷对程序来讲简直就是无孔不入,特别是在要引入一些新的特性或者程序越来越大越来越复杂的时候。值得我们高兴的是有些缺陷能很容易被发现,这给我的工作带来了极大的方便。例如编译时的缺陷能够立刻告诉我们某个地方有错误,我们也可以通过编译输出的错误信息判断和找出错误所在,并且修改它。运行时的缺陷就没有那么好对付了,因为它们隐藏的很深,喜欢和我们玩捉秘藏,往往很难被发现,有时即使在某些时候我们发现了它们,要找到产生它们原因的道路还很曲折。泛型(Generics)能够帮助我们在编译程序的时候就发现更多的缺陷。
下面代码定义了一个Box类,有两个方法add()和get()。
public class Box {
private Object object;
public void add(Object object) {
this.object = object;
}
public Object get() {
return object;
}
}
因为它的方法的参数是Object,所以我们可以随意传递任何参数到add()方法中,即使我们要想传递的不是原始类型也没有关系。然而事实上,我们应该严格限制传递的参数类型,但是目前我们只能做的就是在文档中加以说明或者在代码中添加相应的注释,编译器却对参数类型所作限制是不得而知的。
public class BoxDemo {
public static void main(String[] args) {
// ONLY place Integer objects into this box!
Box integerBox = new Box();
// Imagine this is one part of a large application
// modified by one programmer.
integerBox.add("10"); // note how the type is now String
// ... and this is another, perhaps written
// by a different programmer
Integer someInteger = (Integer)integerBox.get();
System.out.println(someInteger);
}
}
在BoxDemo中,没有将字符串”10”转换成整数对象,很明显是个缺陷。但是在编译的时候并不能发现这个缺陷,从而造成在运行的时候出现错误。如果在定义Box类的时候能够使用泛型,那么这个错误在编译的时候就会被发现。
我们可以通过将"public class Box" 修改为 "public class Box<T>"而定义一个泛型,在这个定义中,使用了一个类型变量(type variable) T,而且T能够在Box类之内的任何地方被使用。这中定义的方法其实并不复杂,并且在接口(interface)中也被使用。实际上,T可以看作是一种特殊的数据类型,它的值就是我们要传递给它的参数,参数的类型可以是类,也可以是接口,或者其他类型的变量,但是却不能是原始类型(primitive)的数据。
/**
* Generic version of the Box class.
*/
public class Box<T> {
private T t; // T stands for "Type"
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
}
如上所示,我们用T代替了其中所有的Object。为了在我们的代码中能够使用泛型类,我们必须要用具体的类型比如Integer来代替T,这也叫做泛型调用(generic type invocation)。
实际上,泛型调用和普通的方法调用非常相似,所不同的是方法调用时传递的是参数,而泛型调用是传递的是一种具体的类型参数,比如Integer。泛型调用也叫做参数化类型(parametersized type)。
为了实例化使用泛型技术定义的类,和通常一样要使用new关键字,如下所示:
integerBox = new Box<Integer>();
或者如果写的更加全面的话可以用下面的语句:
Box<Integer> integerBox = new Box<Integer>();
一旦integerBox被实例话了,我们就可以使用它的get方法而无需进行参数的转换。而且如果试图想加一个类型不符的参数到box,编译器就会报错。
我们一定要记住,类型变量并不是真正的数据类型本身,上面的例子中,在文件中是找不到T.java或者T.class的,而且T也不是类名Box的一部分。实际上在编译的时候,所有和泛型有关的信息都会被去掉,从而最后只有Box.class文件,这会在后面进一步讨论。
另外还要注意的是泛型可以多个类型参数,但是每个类型参数在所定义的类或者接口中不能重复。例如Box<T, T>则是有问题的,而Box<T, U>则是可以使用的。
按照惯例,类型参数一般使用单个大写的字母表示。这样就可以普通变量的命名形成了明显的对比。如果不按照此惯例,就很难区分类型参数名和普通的类名或者接口名。
通常使用的类型参数如下:
E -元素(Element);
K -关键字(Key);
N -数(Number);
T -类型(Type);
V -值(Value);
S,U,V等-第2个,第3个,第4个类型。
如果在申明方法或者构造器的时候使用类型参数的话,就可以定义泛型方法和泛型构造器。这和定义一个普通的泛型基本上无二样,除了类型参数的作用范围只是在定义它的方法或者构造器之中。
/**
* This version introduces a generic method.
*/
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");
}
}
在上面的例子中,我们定义了一个泛型方法inspect,而该泛型方法定义了一个类型参数U。这个方法接收输入的参数并将其类型输出,为了比较,它也将T的类型输出。这个类中还有一个main方法使得它可以直接运行,而且输出如下:
T: java.lang.Integer
U:java.lang.String
通过输入不同的参数,输出也会跟着相应的变化。
有时候,我们要限制传递给类型参数的具体参数。例如,对数进行操作的方法就只能接受Number或者其子类的对象作为改方法的参数,而不能接受其他类型的参数。这也就是要对参数类型进行限制的原因。
在申明一个类型参数的时候,如果在类型参数名后跟着extends关键字,而extends关键字后面又跟着类型参数的上限(upper bound),例如这个上限可以是个数类Number,那么这个被申明的类型参数就是一个受限的参数类型。需要注意的是,这里的extends关键字可以是普通意义上类“继承”的意思,也可以是接口上“实现”的意思。
/**
* This version introduces a bounded type parameter.
*/
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"); // error: this is still String!
}
}
如上代码所示,U就是一个受限的类型参数,只能向其传递Number类或者Number子类的参数。如果对上面的代码进行编译的时候,就会报错,原因就是调用inspect方法的时候向其传递了一个String的参数"some text"。
在定义受限类型参数的时候,如果还想要实现接口的话,就可以将要实现的接口使用&字符连接在类型参数上限(upper bound)的后面,如下所示:
<U extends Number & MyInterface>
要想实现多个接口的话,就用&依次将要实现的接口跟在后面就可以了,如下:
<U extends Number & MyInterface1 & MyInterface2 … >
只要两种类型能够相符,我们可以把一种类型的对象赋给另外一种类型的对象。例如,可以把一个Integer赋给一个Object,因为Object是Integer的父类之一。
Object someObject = new Object();
Integer someInteger = new Integer(10);
someObject = someInteger; // OK
在面向对象的编程中,Integer和Object之间的这种关系叫做”is a”,也就是说在本质上是相同的,Integer是一种Object,所以才允许上面的这种赋值。由于Integer也是Number的一种,所以下面的赋值也是有效的:
public void someMethod(Number n){
// method body omitted
}
someMethod(new Integer(10)); // OK
someMethod(new Double(10.1)); // OK
对于泛型来讲,上面的讨论的这种关系也是适用的。在一个泛型调用中,我们可以传递Number作为它的参数,但是在后续调用add方法的时候,我们使用和Number相符的类型作为其参数:
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>的子类型。
在泛型中,我们可以用一个通配符”?”来代替一个未知的类型。例如,使用下面的代码为某种animal指定一个cage:
Cage<? extends Animal> someCage = ...;
"? extends Animal"表示一种未知的类型,它可能是animal的一种子类型,也可能是animal自己本身,总的来讲就是某种animal。上面的例子是一种受限通配符,它的上限就是Animal。如果需要装下某种animal的cage,那么就可以被用作是lion cage或者butterfly cage。
如果使用super而不是extends则就可以为未知类型指定一个下限(lower bound)。例如,Cage<? super Animal>表示的也是一种未知类型,其可能是animal的一种超类型(supertype),也可能是animal自己本身。当然,如果我们用<?>来定义一个未知类型,那么这样的未知类型是不受限的。一个不受限的未知类型实质上就是<? extends Object>。
虽然Cage<Lion>和Cage<Butterfly>不是Cage<Animal>的子类型,但是却是Cage<? extends Animal>的子类型。上面已经定义了someCage,那么就可以进行如下赋值:
Cage<Lion> lionCage = ...;
Cage<Butterfly> butterflyCage = ...;
someCage = lionCage; // OK
someCage = butterflyCage; // OK
但是我们还是不能把butterflies和lions直接add到someCage:
interface Lion extends Animal {}
Lion king = ...;
interface Butterfly extends Animal {}
Butterfly monarch = ...;
someCage.add(king); // compiler-time error
someCage.add(monarch); // compiler-time error
如果someCage是一个butterfly cage,那么它装入butterfly是没有问题的,但是却装不了lion。当然,如果someCage是一个lion cage,那么它装入lion是没有问题的,却装不了butterfly。也就是我们不能向someCage种装入任何anmial,那么是不是someCage就没有任何用了呢?其实不然,例如下面的代码就用到了someCage:
void feedAnimals(Cage<? extends Animal> someCage) {
for (Animal a : someCage)
a.feedMe();
}
这样一来,我们就可以把每种animal装入到对应独立的cage中,然后依次调用这个方法,如下:
feedAnimals(lionCage);
feedAnimals(butterflyCage);
或者把所有的animal cage组合起来,然后可以用下面的代码进行代替:
feedAnimals(animalCage);
当我们实例化一个泛型的时候,编译器使用一种叫做类型擦除(type erasure)的技术。在类型擦除的过程中,编译器会去除掉类与接口中所有和类型参数有关的信息。类型擦除使得用泛型的java应用程序能够和该泛型创建之前就存在的java库和应用程序相兼容。
例如Box<String>在编译的时候产生一个叫做原型(raw type)的类型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
}
}
上面黑体代码之所以在编译的时候会报错是因为编译器去除了所有和参数(由类型参数E代表)相关的信息。
有了类型擦除技术之后,就可以让新的代码和遗留的代码共存。但是无论如何,使用原型是一种不好的编程习惯,应该避免在程序中使用。当把遗留代码和泛型代码混合在一起的时候,我们可能会碰到类似于下面的告警:
Note: WarningDemo.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
例如,我们用一个旧的API,但参数却用的是一个原型参数,如下的代码所示:
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