java核心技术卷I-泛型(一)

泛型程序设计

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

类型参数的好处

使得程序具有更好的可读性和安全性。

泛型类

一个泛型类( generic class) 就是具有一个或多个类型变量的类。

public class Pair<T> {
    private T first;
    private T second;
    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; } 
}

Pair 类引人了一个类型变量 T,用尖括号 ( < >) 括起来,并放在类名的后面。泛型类可
以有多个类型变量。

public class Pair<T, U> { . . . }

泛型方法

class ArrayAlg
{
    public static <T> T getMiddle(T... a)
    {
	    return a[a.length / 2];
    }
}

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

类型变量的限定

class ArrayAlg
{
	public static <T> T min(T[] a) // almost correct
	{
		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;
	}
}

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

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

现在,泛型的 min方法只能被实现了 Comparable 接口的类(如 String、 LocalDate 等)的数组调用。

<T extends BoundingType>

表示 T 应该是绑定类型的子类型 (subtype)。 T 和绑定类型可以是类, 也可以是接口。选择关键字 extends 的原因是更接近子类的概念, 并且 Java 的设计者也不打算在语言中再添加一个新的关键字(如 sub)。
一个类型变量或通配符可以有多个限定, 例如:

T extends Comparable & Serializable

在 Java 的继承中, 可以根据需要拥有多个接口超类型, 但限定中至多一个类。如果用一个类作为限定,它必须是限定列表中的第一个

类型擦除

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

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 替换。
结果是一个普通的类, 就好像泛型引人 Java 语言之前已经实现的那样。
原始类型用第一个限定的类型变量来替换, 如果没有给定限定就用 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) { . . . }
}

翻译泛型表达式

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

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

擦除 getFirst 的返回类型后将返回 Object 类型。编译器自动插人 Employee 的强制类型转换。也就是说,编译器把这个方法调用翻译为两条虚拟机指令:

对原始方法 Pair.getFirst 的调用。
将返回的 Object 类型强制转换为 Employee 类型。

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

关 Java 泛型转换的事实:

虚拟机中没有泛型,只有普通的类和方法。
所有的类型参数都用它们的限定类型替换。
桥方法被合成来保持多态。
为保持类型安全性,必要时插人强制类型转换。

约束与局限性

用 Java 泛型时需要考虑的一些限制。大多数限制都是由类型擦除引起的。
1>不能用基本类型实例化类型参数
不能用类型参数代替基本类型。因此, 没有 Pair<double>, 只 有 Pair<Double>。 当然,其原因是类型擦除。擦除之后, Pair 类含有 Object 类型的域, 而 Object 不能存储 double值。
2>运行时类型查询只适用于原始类型
试图查询一个对象是否属于某个泛型类型时,倘若使用 instanceof 会得到一个编译器错误。同样的道理, getClass 方法总是返回原始类型。例如:

Pair<String> stringPair = . .
Pair<Employee> employeePair = . .
if (stringPair.getClass() == employeePair.getClass()) // they are equal

3>不能创建参数化类型的数组
不能实例化参数化类型的数组, 例如:

Pair<String>[] table = new Pair<String>[10]; // Error

擦除之后, table 的类型是 Pair[。] 可以把它转换为 Object[] :

Object[] objarray = table;

数组会记住它的元素类型, 如果试图存储其他类型的元素, 就会抛出一个 Array-StoreException 异常。
需要说明的是, 只是不允许创建这些数组, 而声明类型为 Pair[] 的变量仍是合法的。不过不能用 new Pair<String>[10] 初始化这个变量。
4>Varargs 警告
向参数个数可变的方法传递一个泛型类型的实例。

public static <T> void addAll(Collections coll, T... ts)
{
	for (t : ts) coll.add(t);
}

应该记得,实际上参数 ts 是一个数组, 包含提供的所有实参。

Col1ection<Pair<String» table = . . .;
Pair<String> pairl = . . .;
Pair<String> pair2 = . .
addAll(table, pairl, pair2);

为了调用这个方法,Java 虚拟机必须建立一个 Pair 数组, 这就违反了前面的规则。不过,对于这种情况, 规则有所放松,你只会得到一个警告,而不是错误。
可以采用两种方法来抑制这个警告。一种方法是为包含 addAll 调用的方法增加注解 @SuppressWamings(“unchecked”)。 或者在 Java SE 7中, 还 可 以 用@SafeVarargs 直接标注addAll 方法:

@SafeVarargs
public static <T> void addAll(Collection<T> coll, T... ts)

5>不能实例化类型变置
不能使用像 new T(…,) newT[…] 或 T.class 这样的表达式中的类型变量。
6>不能构造泛型数组
就像不能实例化一个泛型实例一样, 也不能实例化数组。不过原因有所不同,毕竟数组会填充 mill 值,构造时看上去是安全的。不过, 数组本身也有类型,用来监控存储在虚拟机中的数组。这个类型会被擦除。
7>泛型类的静态上下文中类型变量无效
不能在静态域或方法中引用类型变量。例如, 下列高招将无法施展:

public class Singleton<T>
{
	private static T singlelnstance; // Error
	public static T getSinglelnstance() // Error
	{
		if (singleinstance == null) construct new instance of T
		return singlelnstance;
	}
}

如果这个程序能够运行, 就可以声明一个 Singleton 共享随机数生成器, 声明一个 Singleton <JFileCh00Ser> 共享文件选择器对话框。但是, 这个程序无法工作。类型擦除之后, 只剩下 Singleton 类,它只包含一个 singlelnstance 域。 因此, 禁止使用带有类型变量的静态域和方法。
8>不能抛出或捕获泛型类的实例
既不能抛出也不能捕获泛型类对象。实际上, 甚至泛型类扩展 Throwable 都是不合法的。
9>可以消除对受查异常的检查
Java 异常处理的一个基本原则是, 必须为所有受查异常提供一个处理器。不过可以利用泛型消除这个限制。关键在于以下方法

@SuppressWamings("unchecked")
public static <T extends Throwable〉void throwAs(Throwable e) throws T
{
throw (T) e;
}

假设这个方法包含在类 Block 中, 如果调用

Block.<RuntimeException>throwAs(t);

编译器就会认为 t 是一个非受查异常。 以下代码会把所有异常都转换为编译器所认为的
非受查异常:

try
{
	do work
}
catch (Throwable t)
{
	Block.<RuntimeException>throwAs(t) ;
}

通过使用泛型类、 擦除和 @SuppressWamings 注解, 就能消除 Java 类型系统的部份基本限制。
10>注意擦除后的冲突
当泛型类型被擦除时, 无法创建引发冲突的条件。

public class Pair<T>
{
	public boolean equals(T value) { return first,equals(value) && second,equals(value); }
}

考虑一个 Pair<String>。从概念上讲, 它有两个 equals 方法:

boolean equals(String) // defined in Pair<T>
boolean equals(Object) // inherited from Object

方法擦除

boolean equals(T)

就是

boolean equals(Object)

与 Object.equals 方法发生冲突。
泛型规范说明还提到另外一个原则:“ 要想支持擦除的转换, 就需要强行限制一个类或类型变量不能同时成为两个接口类型的子类,而这两个接口是同一接口的不同参数化。” 例如,下述代码是非法的:

class Employee implements Coinparable<Emp1oyee> { . . . }
class Manager extends Employee implements Comparable<Hanager>
{ . . . } // Error

Manager 会实现 Comparable 和 Comparable, 这是同一接口的不同参数化。
下列非泛型版本是合法的

class Employee implements Comparable { . . . }
class Manager extends Employee implements Comparable { . . . }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

局外人一枚

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

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

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

打赏作者

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

抵扣说明:

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

余额充值