Fork me on GitHub
返回顶部
跳到底部

泛型

所谓泛型,就是允许在定义类、接口、方法时使用类型形参,这个类型形参将在声明变量、创建对象、调用方法时动态地指定。

集合中的泛型

泛型最常见在使用集合时,如:

List<String> list=new ArrayList<String>();

可以简写为:

List<String> list=new ArrayList<>();

泛型集合与泛型数组的不同

如果B是A的子类,对于数组而言,B[]A[]的子类型,但是对于泛型,G<B>不是G<A>的子类型,下面第二行就会编译出错。

Object[] a=new String[10];
List<Object> a1=new ArrayList<String>();//编译错误

下面我们来探讨一下原因。
按照上面的逻辑,我们可以做下面实验:

Integer[] ia=new Integer[5];
Number[] na=ia;
na[0]=0.5;//编译正常,运行错误,有风险

上面的代码编译正常,但运行到na[0]=0.5;会抛出一个ArrayStoreException异常,这是一种潜在的风险。
在Java的早期设计中,允许Integer[]数组赋值给Number[]变量存在缺陷,因此Java在泛型设计是进行了改进,它不再允许把List对象赋值给List变量。例如,下面的代码会编译错误:

List<Integer> iList=new ArrayList<>();
List<Number> nList=iList;

Java泛型设计的原则是,只要代码在编译时没有出现警告,就不会遇到运行时异常。

定义泛型接口、类

可以在任意类、接口的定义是增加泛型声明:

public class Apple <T>{
    private T info;
    public Apple(T info){
        this.info=info;
    }
}

当从泛型类中派生子类时,必须要指明参数类型。

public class Apple extends Fruit<String>

可以用如下方式声明一个泛型引用:

Fruit<String> fruit;

泛型方法

可以用如下方式定义泛型方法:

static <T> void fromArrayToCollection(T[] a, Collection<T> c){
    for (T o:a){
        c.add(o);
    }
}

泛型构造器

和定义泛型方法一样:

class Foo{
    <T> Foo(T t){
        System.out.println(t);
    }
}

使用如下:

Foo foo=new <String> Foo("hello");

通配符

类型通配符?

List<?>,这种带通配符的List表示它是各种泛型的父类,并不能把元素加入其中。例如,下面代码将会引起编译错误。

List<?> list=new ArrayList<>();
list.add(new Object());//编译错误
Object o=list1.get(0);

使用通配符的集合只能向其中取元素,不能往里面添加元素

设定类型通配符的上限:? extends T

用法如下:

List<? extends Number> list3=new ArrayList<>();
list3.add(new Integer());//编译错误
Number str=list3.get(0);

这种方式声明的集合只能取元素,不能往里放元素,应为声明该集合时,只知道元素类型继承自Number,但不知道该集合中元素的具体类型,所以不能往里放元素;因为知道里面的元素一定是Number的子类,所以可以取出来

设定类型通配符的下限:? super T

用法如下:

List<? super Number> list4=new ArrayList<>();
list4.add(new Integer(1));
Object o1=list4.get(0);

这种方式声明集合时,可以往里面放元素,也可以从里面取元素。因为对于List<? super Number>,可以知道集合元素声明的类型一定是Number的父类,所以往里面放Number及其子类的元素,而且取出来的元素一定是Object类型。

擦除与转换

在严格的泛型代码里,带泛型声明的类应该带着类型参数。但为了与老的Java代码保持一致,也允许在使用带泛型声明的类时不指定实际的类型参数。如果没有为这个泛型类指定实际的类型参数,则该类型参数被称作raw type(原始类型)。
当把一个具有泛型信息的对象赋值给另一个没有泛型信息的变量时,所有尖括号之间的类型信息都将被扔掉。比如一个List类型被转换为List,则该List对集合元素的类型检查变成了类型参数的上限(Object)。

class Orange<T extends Number>{
    T size;
    public Orange(){
        
    }
    public Orange(T size){
        this.size=size;
    }
    public void setSize(T size){
        this.size=size;
    }
    public T getSize(){
        return this.size;
    }
}
public class ErasureTest {
    public static void main(String[] args){
        Orange<Integer> a=new Orange<>(5);
        Integer as=a.getSize();
        //将a对象赋给b变量,将丢失尖括号里的类型信息
        Orange b=a;
        //b只知道size的类型为Number
        Number size1=b.getSize();
        //下面代码引起编译错误
        Integer size2=b.getSize();
    }
}

对泛型而言,可以直接将一个List对象赋值给一个List<String>对象,如下程序:

List<Integer> li=new ArrayList<>();
li.add(5);
li.add(9);
List list=li;
//下面代码引编译通过
List<String> ls=list;
//但从集合里取元素时会引起运行时异常
System.out.println(ls.get(0));

上面代码编译正常,但运行时会抛出ClassCastException异常,因为li变量声明的是List<String>,实际引用的是List<Integer>集合,所以当试图把该集合里的元素当成String类型的对象取出时,将引发运行时异常。

posted @ 2018-05-15 08:58  sqmax  阅读(152)  评论(0编辑  收藏  举报