Java核心技术-泛型

泛型对于集合类尤为有用

1 为什么要使用泛型程序设计

泛型程序设计意味着编写的代码可以被很多不同类型的对象所重用。

1.1 类型参数的好处

在Java中增加泛型类之前,泛型程序设计是用继承实现的。ArrayList类只维护一个Object引用的数组:

public class ArrayList{

    private Object[] elementData;

    ...

    public Object get(int i){...}

    public void add(Object o){...}

}

上述方法存在两个问题:1当获取一个值时必须进行强制类型转换

    2没有错误检查,可以向数组列表中添加任何类的对象

泛型提供了一个更好的解决方案:类型参数,ArrayList类有一个类型参数用来指示元素的类型:

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

这样做是的代码具有更好的可读性

Java SE 7及以后的版本中可以在构造函数中省略泛型类型:

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

省略的类型可以从变量的类型推断得出。

*编译器也可以很好的利用这个信息,当获取一个值时不需要进行强制类型转换,编译器就知道返回值类型为String。

*编译器还知道ArrayList<String>中add方法有一个类型为String的参数,并且编译器会进行检查,避免插入错误类型的对象

类型参数的魅力在于:使得程序具有更好的可读性和安全性。

1.2 谁想称为泛型程序员

使用一个像ArrayList的泛型类很容易,但是实现一个泛型类并没有那么容易。

例如,ArrayList类有一个addAll方法用来添加另一个集合的全部元素。可以将ArrayList<Manage>中的所有元素添加到ArrayList<Employee>中去,但是反过来就不行。如果只能允许前一个调用,而不能允许后一个调用呢?(通配符类型)

2 定义简单泛型类

一个泛型类是具有一个或多个类型变量的类,泛型类可以有多个类型变量

类定义中的类型变量指定方法的返回类型以及局部变量的类型

public class Paris<T> {

    private T second;

        first = null;

    }

        this.first = first;

    }

        return first;

    public T getSecond() {

    }

        first = newValue;

    public void setSecond(T newValue) {

    }

    private T first;

    public Paris() {

        second = null;

    public Paris(T first,T second) {

        this.second = second;

    public T getFirst() {

    }

        return second;

    public void setFirst(T newValue) {

    }

        second = newValue;

}

用具体的类型替换类型变量就可以实例化泛型类型,可以将结果想象成带有构造器的普通类,换句话说,泛型类可看作普通类的工厂

3 泛型方法

泛型方法既可以定义在普通类中,也可以定义在泛型类中

类型变量放在修饰符的后面,返回类型的前面。

4 类型变量(T)的限定

class ArrayAlg

{

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

    {

        if(a==null || a.length==0) return null;

        T smallest=a[0];

        for(int i=1;i<a.length;i++)

            if(smallest.compareTo(a[i])>0) smallest=a[i];

        return smallest;

    }

}

为了确保T所属的类有compareTo方法,将T限制为实现了Comparable接口的类

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

**为什么用extends而不是implements?

T应该是绑定类型的子类型,T和绑定类型可以是类,也可以是接口,选用extends是因为更加接近子类的概念

同时一个类型或通配符可以有多个限定(可以拥有多个接口类型,但至多只能有一个类),例:

T extends Comparable & Serializable

限定类型用“&”分隔,用逗号来分隔类型变量,如果用一个类作为限定,它必须是限定列表中的第一个

5 泛型代码和虚拟机

虚拟机中没有泛型类型对象,所有对象都属于普通类

5.1 类型擦除

无论何时定义一个泛型类型,都自动提供了一个相应的原始类型。原始类型的名字就是删去类型参数后的泛型类型名。

擦除类型变量,并替换为限定类型(无限定的变量用Object)

Paris<T>的原始类型为:

public class Pair

{

    private Object first;

    private Object second;

    

    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;}

}

因为T是一个无限定的变量,所以直接用Object替换。

假定有一个泛型类Interval:

public class Interval<T extends Compareble & 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;}

     }

}

原始类型Interval如下:

public class Interval implements Serializable

{

    private Comparable lower;

    private Comparable upper;

    ...

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

}

注:如果切换限定:class Interval<T extends Serializable & Comparable>,则原始类型用Serializable替换T,而编译器在必要时要插入Comparable强制类型转换。

为了提高效率,应该将标签接口(没有方法的接口)放在边界列表的末尾。

5.2 翻译泛型表达式(就是泛型类型被擦除,编译器要对其进行翻译,到底返回一个什么样类型的值)

当程序调用泛型方法时,如果擦除返回类型,编译器插入强制类型转换,即:

1.对原始方法Pair.getFirst的调用

2.将返回的Object类型强制转换为Employee类型

当存取一个泛型域时也要插入强制类型转换

5.3 翻译泛型方法

方法擦除带来了两个复杂的问题:

有一个日期区间是一对LocalDate对象,并且需要覆盖Pair中的setSecond这个方法来确保第二个值永远不小于第一个值。

class DateInterval extends Pair<LocalDate>

{

    public void setSecond(LocalDate second)

    {

        if(second.compareTo(getFirst())>=0)

            super.setSecond(second);

    }

}

但是类型擦除后变成:

class DateInterval extends Pair

{

    public void setSecond(LocalDate second){...}

    ...

}

这里,希望对setSecond的调用具有多态性,并调用最合适的方法。但由于类型擦除与多态发生了冲突。要解决这个问题需要在DateInterval中生成一个桥方法(再覆盖超类的setSecond(Object second)方法)

public void setSecond(Object second){ setSecond((Date) second); }

java泛型转换的事实:

a虚拟机中没有泛型,只有普通的类和方法

b所有的类型参数都用他们的限定类型替换

c桥方法被合成来保持多态

d为保持类型安全性,必要时插入强制类型转换

 5.4 调用遗留代码

设计Java泛型类型时,主要目标是允许泛型代码和遗留代码之间能够相互操作。

可以使用@SuppressWarnings("unchecked")消除警告

6 约束与局限性

大多数限制是由类型擦除引起的

6.1 不能用基本类型实例化类型参数

可以使用基本类型的包装类,如Pair<Double>,擦除之后Pair类可以含Object类型的域,而Object不能存储double。

6.2 运行时类型查询只适用于原始类型

所有的类型查询只产生原始类型

if(a instanceof Pair<String>)//error

如果仅仅测试a是否是任意类型的一个Pair,也会出现错误

if(a instanceof Pair<T>)//error

总结:试图查询一个对象是否属于某个泛型类型时,倘若使用instanceof会得到一个编译错误,如果使用强制类型转换会得到一个警告

猜你喜欢

转载自blog.csdn.net/wo8vqj68/article/details/81169247