Java中的泛型

来源:互联网 发布:58中国经纪人网络平台 编辑:程序博客网 时间:2024/06/01 08:03

泛型(Generic)在jdk1.5被引入,它加入了一个全新的语法元素,并且好多类和方法都重新实现了泛型的版本,受之影响最大的就是Collections Framework了。本篇博客来讨论一下泛型的语法特征,并简要地讨论一下底层的实现机制。

泛型类

下面先看一个泛型类Printer

public class Printer<T> {    T target;    public Printer(T target){        this.target = target;    }    public void print(){        System.out.println(target);    }}

这个类的构造函数中接受一个泛型的target,在print方法中可以将它打印出来。这个参数可以是任意的类型。下面看一下如何使用这个类。

public static void main(String[] args) {      Printer<String> printer1 = new Printer<String>("hello,generic");      printer1.print();      Printer<Integer> printer2 = new Printer<Integer>(23);      printer2.print();}

上面代码的运行结果是

hello,generic23

可以看得出来,泛型的特性加强了Printer的功能,使它可以打印任意类型的东西。

类型推断

泛型支持类型推断。从jdk1.7开始,java编译器是为我们提供一些便利的。以Printer<String> printer1 = new Printer<String>("hello,generic");为例,可以简化为

Printer<String> printer1 = new Printer<>("hello,generic");

new Printer<>("hello,generic")返回的类型就是Printer<String>,因为"hello,generic"是一个字符串。

<>被成为diamond操作符,类型推断就是通过这个操作符完成的。

在maven中加入以下配置,即可使用jdk1.7的编译器

<build>    <plugins>        <plugin>            <groupId>org.apache.maven.plugins</groupId>            <artifactId>maven-compiler-plugin</artifactId>            <configuration>                <source>1.7</source>                <target>1.7</target>            </configuration>        </plugin>    </plugins></build>

泛型方法

在Printer类中加入一个静态的泛型方法staticPrint

public static <T2> void staticPrint(T2 obj) {    System.out.println(obj);}

由于是静态方法,所以无需实例化Printer类就可以使用这个方法

Printer.<String>staticPrint("hello,generic");Printer.<Integer>staticPrint(23);

当然,这种情况下也可以使用类型推断

Printer.staticPrint("hello,generic");Printer.staticPrint(23);

这种情况下,连diamond操作符都省略了

bound type

bound type用来对泛型参数做一些限制,即泛型参数必须继承自这个父类,或者实现这个接口。
如果规定Printer类必须是数字,则需要将bound type设置为Number类,如下

public class Printer<T extends Number> {    T target;    public Printer(T target) {        this.target = target;    }    public void print() {        System.out.println(target);    }    public static <T2> void staticPrint(T2 obj) {        System.out.println(obj);    }}

这种情况下,使用String作为泛型参数就不能通过编译了,因为Integer继承自Number类,而String则没有。

//      Printer<String>类型不支持//      Printer<String> printer1 = new Printer<>("hello,generic");//      printer1.print();        Printer<Integer> printer2 = new Printer<Integer>(23);        printer2.print();

bound type的机制允许同时声明父类的限制和接口的限制,语法如下

MyClass<T extends MyClass & MyInterface>

泛型类型和通配符

同一个泛型类配上不同的泛型参数,就是不同的类型,即Printer<Integer>Printer<String>在编译时被认为是不同的类型。

看下面的例子

Printer<String> printer1 = new Printer<>("hello,generic");Printer<Integer> printer2 = new Printer<>(23);//printer1 = printer2;

上面代码的最后一行编译时会报错:不兼容的类型:Printer<java.lang.Integer>无法转换为Printer<java.lang.String>

可见,不通的泛型参数之间是不兼容的。但如果我有一个方法需要接受Printer任意泛型参数的对象的话,该如何处理呢?

可以使用通配符来?解决这个问题

Printer<?> printer3 = null;printer3 = printer1;printer3 = printer2;

也就是说,Printer<?>类型兼容任意版本的Printer,包括Printer<String>Printer<Integer>

同时,这个通配符也可以设置bound type。例如,如果将printer3声明为Printer<? extends Integer>类型的话,是无法将Printer<String>类型的对象赋值给它的。

Erasure

泛型的一个好处就是它是类型安全的,但这个类型安全是在编译阶段保证的,就像上面的例子一样(不能通过编译)。泛型的所有类型参数都只存在于编译阶段,jvm完全不知道泛型参数的存在,更不知道泛型参数的类型。

java代码编译后,泛型的类型信息会被清除(erase)掉,替换为它的bound type,如果没有指定bound type的话,则默认替换为Object类型,并在必要时做一些恰当的类型转换,java runtime(jvm)完全不知道这一切的存在。这就是java泛型的Erasure机制。

这也解释了为什么不能在泛型方法中创建一个泛型类型的新实例,因为jvm不知道这个泛型类型到底是什么类型。