java 泛型

2016-03-08 15:44:07   最后更新: 2016-03-08 15:44:07   访问数量:465




泛型是 java SE5 开始支持的特性,他可以指定一个“适用于很多类的类型”,也就是参数化类型,使程序可以应用于多种类型,这样泛化的思想为程序设计增添了更高的灵活性

泛型最有用的地方就是创造容器类

 

下面的例子展示了泛型的基本用法:

package com.techlog.test; class Temp { } public class Test<T> { private T a; public Test(T a) { this.a = a; } public T getA() { return a; } public static void main(String[] argv) { Test<Integer> test = new Test<>(5); Test<Temp> tempTest = new Test<>(new Temp()); System.out.println(test.getA()); System.out.println(test.getClass() == tempTest.getClass()); } }

 

 

在创建 Test 对象时,指定了他持有的对象 a 的类型,这个类型是作为一个参数传入的,这就是泛型最基本的用法

 

程序打印了:

5

true

这意味着,虽然传入的是不同的泛型实参,但他们创建都是 Test 类型,因此 test 和 tempTest 两个对象调用 getClass 方法后返回的结果是相同的,泛型类型只是在逻辑上的不同类型,实际上都是相同的类类型

与泛型类的用法一样,接口也可以是使用泛型

 

  • 需要注意的是,类 static 方法是无法访问泛型类的类型参数的

 

上面展示了类使用泛型的示例,泛型不仅可以应用于整个类和接口上,也可以使用在类中的参数化方法上,这也是泛型接口常用的使用方式

使用泛型方法的类可以不是泛型类,然而泛型方法比泛型类更加明确与清晰,所以相较之泛型类,最好的选择是使用泛型方法

上面提到了泛型类中 static 方法无法访问类型参数的限制,但可以通过将 static 方法设置为泛型方法让他具备泛型的能力

 

泛型方法由:[方法访问限制关键字] <T> 返回类型 方法名(参数列表) { 方法体 } 构成,当然也可以使用多个泛型参数,通常使用 E、T、K、V 作为泛型类型参数

 

下面是一个泛型方法的示例:

package com.techlog.test; public class Test { public <T> void printParamClassName(T x) { System.out.println(x.getClass().getName()); } public static void main(String[] argv) { Test test = new Test(); test.printParamClassName(""); test.printParamClassName(1); test.printParamClassName(1.0); test.printParamClassName(1.0F); test.printParamClassName(test); } }

 

 

上面的例子通过上一篇日志中介绍了 Class 类和他的方法,这里使用了 getName 方法获取了类名,打印出了:

java.lang.String

java.lang.Integer

java.lang.Double

java.lang.Float

com.techlog.test.Test

 

C++ 的泛型具有十分巨大的灵活性,但在 java 中,泛型则受到一些限制,因为 java 是通过“擦除”实现泛型的

在泛型代码内部,所有有关泛型参数类型的信息都已经被擦除,因此你既不能创建一个泛型类型的实例,也不能假定泛型类型具有任何特定方法而调用它,虽然 C++ 中你可以这样做

这正是第一个例子中 Test<Integer> 与 Test<Temp> 是相同类型的原因,因为作为参数的两个类型的实际类型信息都已经被擦除,在泛型代码内部,你无法对泛型类型使用 new 表达式和 instanceof 操作

 

package com.techlog.test; import java.lang.reflect.Array; import java.util.Arrays; public class Test<T> { private Class<T> kind; public Test(Class<T> kind) { this.kind = kind; } @SuppressWarnings("uncheck") T[] create(int size) { return (T[]) Array.newInstance(kind, size); } public static void main(String[] argv) { Test<String> stringTest = new Test<>(String.class); String[] stringArray = stringTest.create(10); System.out.println(Arrays.toString(stringArray)); } }

 

 

上面的代码企图在泛型类中返回泛型类数组,由于 java 泛型的擦除实现,打印出了:

[null, null, null, null, null, null, null, null, null, null]

你可以通过返回泛型 List 实现上面代码所需要的功能

 

java 允许你为泛型的类型参数设置边界限制条件

package com.techlog.test; interface Fly { void turnRight(); void turnLeft(); } class Temp<T extends Fly> { T item; Temp(T item) { this.item = item; } void turnCycle() { for (int i=0; i<10; i++) item.turnLeft(); for (int i=0; i<10; i++) item.turnRight(); } } class Bird implements Fly { @Override public void turnRight() { System.out.println("bird turn right"); } @Override public void turnLeft() { System.out.println("bird turn left"); } } public class Test { public static void main (String[] argv) { Temp<Bird> temp = new Temp<>(new Bird()); temp.turnCycle(); } }

 

 

上面的例子中,使用 extends 关键字指定了泛型类型必须继承自 Fly 接口,因此接口中的信息并没有被擦除,从而我们可以在泛型代码中调用接口中的方法

 

有时我们使用通配符指定泛型参数:

package com.techlog.test; import java.util.*; class Fruit { } class Apple extends Fruit { } class Jonathan extends Apple { } class Orange extends Fruit { } public class Test { public static void main(String[] argv) { List<? extends Fruit> flist = new ArrayList<Apple>(); // Error // flist.add(new Apple()); // flist.add(new Fruit()); } }

 

 

在 flist 对象创建中,传递给了他一个 ArrayList<Apple> 类型对象,这个类型可以自动转型为 List<? extends Fruit> 类型,但是你却不能为这个 List<? extends Fruit> 类型对象添加任何数据

这是为什么呢?根据 RTTI,运行时 flist 的类型实际上是 ArrayList<Apple>,他只能放入 Apple 类及其子类对象,而这里的语法是编译器检查的,编译期并不能知道运行时 flist 的实际对象类型,因此编译时无法通过,不仅仅是添加,就是任何容器的 set 方法也是不行的,只能初始化一次然后做只读容器使用

 

package com.techlog.test; import java.util.*; class Fruit { } class Apple extends Fruit { } class Jonathan extends Apple { } class Orange extends Fruit { } public class Test { public static void main(String[] argv) { List<? extends Fruit> flist = new ArrayList<Apple>( Arrays.asList(new Apple[]{new Apple(), new Jonathan()})); System.out.println(flist); } }

 

 

根据这个特性,<? extends T> 常常用作方法参数,这样可以保证方法内不会对容器中的元素做任何修改

 

List<? extends Fruit> 容器只能用 Fruit 及他的子类对象容器实例化,反过来,如果我们要求必须用某个类的容器或其父类容器实例化的话,就需要逆变通配符 <? super T> 了

package com.techlog.test; import java.util.ArrayList; import java.util.List; class Fruit { } class Apple extends Fruit { } class Jonathan extends Apple { } class Orange extends Fruit { } public class Test { public static void main(String[] argv) { List<? super Apple> alist = new ArrayList<Fruit>(); // Error // List<? super Apple> alist = new ArrayList<Jonathan>(); alist.add(new Apple()); alist.add(new Jonathan()); // Error // alist.add(new Fruit()); } }

 

 

通常他也用在方法形参中,用来限定传入实参的下限,然而在形参使用中,他依然只能加入声明的泛型类型及其子类

 






技术帖      龙潭书斋      容器      java      thinking in java      java编程思想      collection      rtti      extends      泛型      运行时      runtime     


京ICP备15018585号