第8章 泛型程序设计
定义简单泛型类¶
下面代码实现了 C++
中的模板类 pair
:
class Pair<F extends Comparable<F>, S extends Comparable<S>> implements Comparable<Pair<F, S>> {
public F first = null;
public S second = null;
public Pair(F f, S s) {
first = f;
second = s;
}
@Override
public int compareTo(Pair<F, S> o) {
int d = first.compareTo(o.first);
return d != 0 ? d : second.compareTo(o.second);
}
}
泛型方法¶
下面方法是一个泛型方法,返回任意类型数组的中间元素:
public static <T> T getMiddle(T[] a)
{return a[a.length / 2];}
在大多数情况下,调用泛型方法时可以省略具体的类型参数,编译会自动推倒并匹配。
类型变量的限定¶
在定义泛型的位置可以可以对泛型进行限定,例如可以声明泛型必须是实现某个接口。
下面的例子是一个求出数组中最小和最大元素的泛型方法,其中限定了泛型只能是实现了 Comparable
接口的类:
class ArrayAlg {
public static <T extends Comparable> Pair<T, T> minmax(T[] a) {
if (a == null || a.length == 0)
return null;
T m = a[0], M = a[0];
for (int i = 1; i < a.length; i++) {
if (a[i].compareTo(m) < 0)
m = a[i];
else if (a[i].compareTo(M) > 0)
M = a[i];
}
return new Pair<>(m, M);
}
}
Java 泛型和 C++ 模板类的区别¶
Java
泛型可以对泛型的类型加以限制,而C++
不行。Java
虚拟机没有泛型类型对象,类型变量会被“擦除”,并替换为限定类型(如果是无限定类型的变量则替换为Object
)。而C++
会为每个模板的实例化产生不同的类型,导致“模板代码膨胀”。Java
中不能用基本类型实例化类型参数。
泛型的限制与局限性¶
-
不能用基本类型实例化类型参数 类型会被擦除,擦除之后的类型(例如
Object
)不能存储int
等基本类型。 -
运行时类型查询只适用于原始类型。 不能使用
instanceof
查询一个对象是否属于某个泛型类型,编译器会报错,如果使用强制类型转换来查询,则会得到一个警告。 对泛型变量使用getClass
总是返回原始类型。 -
不能使用
new
实例化一个参数化类型的数组。例如下面代码就是非法的:
var table = new Pair<String, String>[10]; // 错误
!!! note "注意" 声明 `Pair<String, String>[]` 是合法的,仅仅是不能用 Pair<String, String>[10] 初始化这个变量。
-
变长参数与泛型共用警告
会导致堆污染,因此会得到一个警告。
public static <T> T getMiddle(T... a) //警告 {return a[a.length / 2];}
可以改成数组:
public static <T> T getMiddle(T[] a) {return a[a.length / 2];}
-
不能实例化类型变量
不能在类似
new T(...)
的表达式中使用类型变量public Pair() {first = new T(); second = new U();} // 错误
这同样是因为类型被擦除后
T()
将变成Object()
。解决的办法是让调用者提供一个构造器表达式。
-
不能构造泛型数组。
T[] mm = new T[2]; // 错误
-
不能使用带有类型变量的静态字段和方法。
public class Singleton<T> { private static T singleInstance; // 错误 public static T getSingleInstance() // 错误 {} }
-
能不能抛出或捕获泛型类的实例。
泛型类扩展
Throwable
是不合法的。
泛型类型的继承规则¶
- 无论
S
和T
有什么关系,Pair<T, T>
和Pair<S, S>
之间都没有继承关系。 - 泛型类可以扩展或实现其他的泛型类。例如
ArrayList<T>
实现了List<T>
接口,这意味着一个ArrayList<Manager>
可以转换为一个List<Manger>
,但是,即使Manager
是Employee
的子类,但ArrayList<Manager>
也不是一个ArrayList<Employee>
或者List<Employee>
。
通配符类型¶
? extend Employee
表示Employee
或Employee
的子类。在类型参数中使用? extend
的类型可以使用其中的访问器方法,但不能使用更改器方法。? super Manager
表示Manager
或Manager
的子类。在类型参数中使用? super
的类型可以使用其中的更改器方法,但不能使用访问器方法。
最后更新:
2023-12-17
创建日期: 2023-12-12
创建日期: 2023-12-12