NoPerfectName Engineer

Java 泛型

2017-03-18
NoPerfectName

Java泛型介绍

泛型是 Java 1.5 的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。

Java 泛型被引入的好处是安全简单。

泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。

泛型在使用中还有一些规则和限制:
1)泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。
2)泛型的参数类型可以使用 extends 语句和 super 语句,例如。习惯上成为“有界类型”。
3)泛型的参数类型还可以是通配符类型。通配符类型的集合不能往里面存放任何数据( null 除外)。
4) 泛型不是协变的,数组是协变的。也就是说A是B的超类,则A[]也是B[]的超类型,但对于泛型来说List<A>不是List<B>的超类型。

数组能够协变而泛型不能协变的另一个后果是,不能实例化泛型类型的数组(new List<String>[3]是不合法的),除非类型参数是一个未绑定的通配符(new List<?>[3]是合法的)。


泛型实现原理

  • Java的泛型是伪泛型。在编译期间,所有的泛型信息都会被擦除掉。正确理解泛型概念的首要前提是理解类型擦除(type erasure)。

  • Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为类型擦除。
     如在代码中定义的List<object>和List<String>等类型,在编译后都会变成List。JVM 看到的只是 List,而由泛型附加的类型信息对 JVM 来说是不可见的。Java 编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。

  • 不要在新代码中使用原生态类型,这条规则有两个例外,两者都源于“泛型信息可以在运行时被擦除”。
    1)在类文字(class literal)中必须使用原生态类型,规范不允许使用参数化类型(虽然允许数组类型和基本类型)。换句话说,List.class,String[].class和int.class都合法,但是List<String>.class则不合法。
    2)第二个例外与instanceof有关。由于泛型信息可以在运行时被擦除,因此在参数划类型而非无限通配符类型上使用instanceof操作符是非法的。下面利用泛型来使用instanceof操作符的首选方法:

    if(o instanceof Set){
      Set<?> m = (Set<?>) o;
    }
    

extends 和 super

关键字说明

● ? 通配符类型

● <? extends T> 表示类型的上界,表示参数化类型的可能是T 或是 T的子类

● <? super T> 表示类型下界(Java Core中叫超类型限定),表示参数化类型是此类型的超类型(父类型),直至Object

extends 示例

static class Food{}
static class Fruit extends Food{}
static class Apple extends Fruit{}
static class RedApple extends Apple{}

List<? extends Fruit> flist = new ArrayList<Apple>();
// compile error:
// flist.add(new Apple());
// flist.add(new Fruit());
// flist.add(new Object());
flist.add(null); // only work for null

List<? extends Frut> 表示 “具有任何从Fruit继承类型的列表”,编译器无法确定List所持有的类型,所以无法安全的向其中添加对象。可以添加null,因为null 可以表示任何类型。所以List 的add 方法不能添加任何有意义的元素,但是可以接受现有的子类型List<Apple> 赋值。

Fruit fruit = flist.get(0);

Apple apple = (Apple)flist.get(0);

由于,其中放置是从Fruit中继承的类型,所以可以安全地取出Fruit类型。

flist.contains(new Fruit());

flist.contains(new Apple());

在使用Collection中的contains 方法时,接受Object 参数类型,可以不涉及任何通配符,编译器也允许这么调用。

super 示例

List<? super Fruit> flist = new ArrayList<Fruit>();

flist.add(new Fruit());
flist.add(new Apple());

flist.add(new RedApple());

// compile error:

List<? super Fruit> flist = new ArrayList<Apple>();

List<? super Fruit> 表示“具有任何Fruit超类型的列表”,列表的类型至少是一个 Fruit 类型,因此可以安全的向其中添加Fruit 及其子类型。由于List<? super Fruit>中的类型可能是任何Fruit 的超类型,无法赋值为Fruit的子类型Apple的List<Apple>.

// compile error:

Fruit item = flist.get(0);

因为,List<? super Fruit>中的类型可能是任何Fruit 的超类型,所以编译器无法确定get返回的对象类型是Fruit,还是Fruit的父类Food 或 Object.

小结

extends 可用于的返回类型限定,不能用于参数类型限定。

super 可用于参数类型限定,不能用于返回类型限定。

带有super超类型限定的通配符可以向泛型对易用写入,带有extends子类型限定的通配符可以向泛型对象读取。——《Core Java》


Similar Posts

上一篇 Java异常机制

Comments