泛型

一、泛型基础

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

  /**
	 * 泛型类可以有多个类型变量如Pair<E,T>
 *
	 * @param <T> 类型变量
	 */
public class Pair<T> {
    /** T可以代表任意类型 */
    private T t;

    Pair(T t) {
        this.t=t;
    }

    /**
     * 不带返回值的泛型方法,可以定义在普通类中(不是泛型类的类中)
     *
     * @param e 类型变量
     * @param <E> 声明是一个泛型方法
     */
    public <E> void method0(E e) {

    }

    public <E> String method1(E e) {
        return "";
    }

    public <E> E method3(E e) {
        return e;
    }

    public <E> E[] method4(E[] e) {
        return e;
    }

    /*-------------------------------------*/
   
}

二、类型变量的限定

有时,类或方法需要对类型变量加以约束。如下例子,计算数组中的最小元素:

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

但是,这里有一个问题。请看一下min 方法的代码内部。变量smallest 类型为T, 这意味着它可以是任何一个类的对象。怎么才能确信T 所属的类有compareTo 方法呢?
解决这个问题的方案是将T 限制为实现了Comparable 接口(只含一个方法compareTo 的
标准接口)的类。可以通过对类型变量T 设置限定(bound) 实现这一点:

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

读者或许会感到奇怪—在此为什么使用关键字extends 而不是implements ? 毕竟,Comparable 是一个接口。下面的记法

<T extends BoundingType〉

表示T 应该是绑定类型的子类型(subtype)。T和绑定类型可以是类,也可以是接口。选择关键字extends 的原因是更接近子类的概念,并且Java的设计者也不打算在语言中再添加一个新的关键字(如sub)。一个类型变量或通配符可以有多个限定, 例如:T extends Comparable & Serializable限定类型用“ &” 分隔,而逗号用来分隔类型变量。在Java 的继承中, 可以根据需要拥有多个接口超类型, 但限定中至多有一个类。如果用一个类作为限定,它必须是限定列表中的第一个。

三、泛型代码和虚拟机

虚拟机没有泛型类型对象—所有对象都属于普通类。在泛型实现的早期版本中, 甚至
能够将使用泛型的程序编译为在1.0 虚拟机上运行的类文件! 这个向后兼容性在Java 泛型开
发的后期被放弃了。

3.1类型擦除

无论何时定义一个泛型类型, 都自动提供了一个相应的原始类型( raw type )。原始类型的名字就是删去类型参数后的泛型类型名。擦除( erased) 类型变名, 并替换为限定类型(无限定的变量用Object)。
例如,Pair的原始类型如下:

    public class Pair {
	   
	    private Object t;
	
	    Pair(Object t) {
	        this.t=t;
	    }
		。。。。。
	}

因为T 是一个无限定的变量,所以直接用Object替换。
结果是一个普通的类, 就好像泛型引人Java 语言之前已经实现的那样。
在程序中可以包含不N类型的Pair, 例如, Pair 或Pair。而擦除类
型后就变成原始的Pair 类型了。

原始类型用第一个限定的类型变量来替换, 如果没有给定限定就用Object 替换。例如,类Pair 中的类型变量没有显式的限定,因此,原始类型用Object替换T。假定声明了一个不同的类型。

    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;
	        }
	    }
	}
原始类型Interval 如下所示:
	public class Interval implements Serializable {
	    private Comparable lower;
	    private Comparable upper;
	    public Interval (Comparable first, Comparable second) { . . . }
	}

或许大家可能想要知道切换限定: <T extends Comparable & Serializable> 会发生什么。如果这样做 原始类型用Serializable 替换T, 而编译器在必要时要向Comparable插入强制类型转换。为了提高效率,应该将标签( tagging) 接口(即没有方法的接口)放在边界列表的末尾。

四、约束与局限性

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

不能用类型参数代替基本类型。因此, 没有Pair, 只有Pair。当然,其原因是类型擦除。擦除之后,Pair类含有Object类型的域,而Object不能存储double的值。

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

虚拟机中的对象总有一个特定的非泛型类型。因此, 所有的类型查询只产生原始类型。例如:
if (a instanceof Pair) // Error
实际上仅仅测试a 是否是任意类型的一个Pair。下面的测试同样如此:
if (a instanceof Pair) // Error
或强制类型转换:
Pair<St「ing> p = (Pair) a; // Warning-can only test that a is a Pair
为提醒这一风险, 试图查询一个对象是否属于某个泛型类型时, 倘若使用instanceof 会得到一个编译器错误, 如果使用强制类型转换会得到一个警告。
同样的道理, getClass 方法总是返回原始类型。例如:
Pair stringPair = . .
Pai「< Employee〉employeePair = . .
if (stringPair.getClassO == employeePair .getClassO) // they are equal
其比较的结果是true, 这是因为两次调用getClass 都将返回Pair.class。

4.3不能创建参数化类型的数组

不能实例化参数化类型的数组, 例如:
Pair[] table = new Pair[10] ; // Error
这有什么问题呢? 擦除之后, table 的类型是Pair[]。 可以把它转换为Object[] :
Object[] objarray = table;
数组会记住它的元素类型, 如果试图存储其他类型的元素, 就会抛出一个Array-StoreException 异常:
obj array[0] = “Hello”; // Error component type is Pair
不过对于泛型类型, 擦除会使这种机制无效。以下赋值:
obj array[0] = new Pair< Employee>();
能够通过数组存储检査, 不过仍会导致一个类型错误。出于这个原因, 不允许创建参数
化类型的数组。
需要说明的是, 只是不允许创建这些数组, 而声明类型为Pair[] 的变量仍是合法
的。不过不能用new Pair[10] 初始化这个变量。

五、通配符类型

5.1<? extends BindingClass>

这个通配符限制为BindingClass的所有子类。可以使用返回值,但不能为方法提供参数。

? extends Employee get()
void set(? extends BindingClass)

这样将不可能调用set方法。编译器只知道需要某个BindingClass的子类型,但不知道具体是什么类型。它拒绝传递任何特定的类型。毕竟?不能用来匹配。使用get就不存在这个问题: 将get的返回值赋给一个BindingClass的引用完全合法。
这就是引人有限定的通配符的关键之处。现在已经有办法区分安全的访问器方法和不安全的更改器方法了。

5.2<? super BindingClass>

这个通配符限制为BindingClass的所有超类型。可以为方法提供参数,但不能使用返回值。

void set(? super BindingClass)
? super Manager getFirst()

这不是真正的Java语法,但是可以看出编译器知道什么。编译器无法知道set方法的具体类型,因此调用这个方法时不能接受类型为Object的参数。只能传递BindingClass类型的对象,或者某个子类型对象。另外,如果调用get, 不能保证返回对象的类型。只能把它赋给一个Object。

直观地讲,带有超类型限定的通配符可以向泛型对象写人,带有子类型限定的通配符可
以从泛型对象读取。

? extends xx
//表示上界类型通配符,我虽然不知道这个类型具体是什么,但他是xx和xx的子类
? super xx
//表示下界类型通配符,我虽然不知道这个类型具体是什么,但他是xx和xx的父类

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

书香水墨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值