Java泛型读书笔记 (一)

Java泛型

在Java SE7和之后的版本中,在new一个泛型类实例的时候,可以不传入类型参数,因为Java编译器可以通过赋给的变量类型声明推断出来,如下代码:

ArrayList<String> files = new ArrayList<>();

编译器自动将String传入到ArrayList的泛型类参数中。

泛型类表现为普通类的一个工厂,泛型类通过传入不同的类型参数,可以生产出不同的类来。

泛型类型的边界

可以使用如下代码定义一个泛型T的边界:

<T extends BoundingType>

这里定义了T扩展自BoundingType,这个BoundingType既可以是一个接口,也可以是一个类。

一个类型参数或者一个通配符?可以有多个边界,例如下面的代码:

T extends Comparable & Serializable

不同的边界使用&符号分开,而且这些边界中,只能有一个是类,其他的都要是接口,也就是与Java的继承机制相同。并且如果用一个类作为边界,那么必须写在extends后面的第一个位置上,后面再写接口。

泛型参数的擦除

当定义一个泛型类型时,Java会自动提供一个原始类型,这个原始类型的名字就是不带泛型的类名,并且Java编译器会将类型参数擦除掉,变为普通类型。

擦除的原则是,如果泛型类型 T 没有定义边界,那么就将其替换为Object。如果有边界,就替换为边界。

如下代码是Pair< T> 的定义:

public class Pair<T>
{
    private T second;
    private T first;

    public Pair()
    {
        first = null;
        second = null;
    }

    public Pair(T first, T second)
    {
        this.first = first;
        this.second =second;
    }

    public T getFirst()
    {
        return first;
    }

    public T getSecond()
    {
        return second;
    }

    public void setFirst(T newValue)
    {
        first = newValue;
    }

    public void setSecond(T newValue)
    {
        second = newValue;
    }
}

编译器会进行类型擦除,将其变为下面的普通类:

public class Pair
{
    private Object second;
    private Object first;

    public Pair()
    {
        first = null;
        second = null;
    }

    public Pair(Object first, Object second)
    {
        this.first = first;
        this.second =second;
    }

    public Object getFirst()
    {
        return first;
    }

    public Object getSecond()
    {
        return second;
    }

    public void setFirst(Object newValue)
    {
        first = newValue;
    }

    public void setSecond(Object newValue)
    {
        second = newValue;
    }
}

对于有边界的泛型类,如果有多个边界,如下面的代码,那么Java编译器会自动擦除到第一个边界:

public class Interval<T extends Comparable & Serializable> implements Serializable
{
    private T lower;
    private T upper;

    ...

    public Interval(T first, T second)
    {
        if (first.compareTo(second) <= 0)
        {
            lower = first;
            upper = second;
        }
        else
        {
            lower = second;
            upper = first;
        }
    }
}

在进行类型擦除后,上面的代码,将擦除到第一个边界Comparable,实际在java虚拟机中代码是这个样子的:

public class Interval implements Serializable
{
    private Comparable lower;
    private Comparable upper;

    ...

    public Interval(Comparable first, Comparable second)
    {
        ...
    }
}

如果上面的代码将边界的顺序换一下:

<T extends Serializable & Comparable>

那么编译器会擦除到Serializable, 并且编译器会在必要的地方自动加上强制类型转换语句,将Serializable转为Comparable:

public class Interval implements Serializable
{
    private Serializable lower;
    private Serializable upper;

    ...

    public Interval(Serializable first, Serializable second)
    {
        if (((Comparable)first).compareTo((Comparable)second) <= 0)
        {
            lower = first;
            upper = second;
        }
        else
        {
            lower = second;
            upper = first;
        }
        ...
    }
}

同样,对于泛型方法也是需要擦除的,如下代码:

public static <T extends Comparable> T min(T[] a)

将被编译器擦除为:

public static Comparable min(Comparable[] a)

注意:

当一个方法的返回类型是泛型时,由于擦除机制,实际返回的是Object或者边界类型,那么编译器会自动加入类型转换语句将擦除的对象转换为正常的对象类型。

如下面的代码:

Pair<Employee> buddies = ...
Employee buddy = buddies.getFirst();

实际上,在类型擦除后,buddies.getFirst()返回的不是Employee类型的实例,而是Obecject类型的,而编译器会自动加入类型装换将Object转换为Employee

总结:

在调用一个泛型方法时,编译器会将一个调用语句分为两步执行

  1. 调用原始类型Pair.getFirst方法
  2. 将Object类型转换为Employee类型

你可能感兴趣的:(Java泛型读书笔记 (一))