本文代码实践基于 JDK-21:
java 21.0.4 2024-07-16 LTS
Java 语言在第 5 个版本才开始引入泛型,此时距离第一个版本已经过去了 8 年。而 Go 语言则是在 Go 1.18 才支持泛型,此时距离 Go 1.0 也已过去了 10 年,为何泛型如此复杂?因为它要解决抽象的问题,用一套算法来满足不同数据或类型的需求。不过这篇文章暂时不讲这些,我们先来看看 Java 中使用泛型时的一些优秀实践。
26 请不要使用原生态类型
代码示例 | 名称 |
---|---|
List<String> | 参数化的类型 |
String | 实际类型参数 |
List<E> | 泛型 |
E | 形式类型参数 |
List<?> | 无限制通配符类型 |
List | 原生态类型 |
<E extends Number> | 有限制类型参数 |
<T extends Comparable<T>> | 递归类型限制 |
List<? extends Number> | 有限制通配符类型 |
static <E> List<E> asList(E[] a) | 泛型方法 |
String.class | 类型令牌 |
为了方便讨论,笔者将书中的术语表摘抄在上面。这一条说的是:不要使用原生态类型,也就说不要使用 List
之类的形式,而要使用 List<E>
或 List<?>
。为什么?因为有安全风险,并且丢失了自描述性。
List list = new ArrayList<String>();
list.add("abc");
list.add(123);
System.out.println(list);
for (Object o : list) {
String s = (String) o;
System.out.println(s);
}
上面的代码,我们一看就知道存在问题,但在编译时只会有一个警告,并不会报错:
Unchecked call to 'add(E)' as a member of raw type 'java.util.List'
只有当我们运行这段程序时,错误才会浮出水面:
[abc, 123]
abc
Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String
即使我们使用了 new ArrayList<String>()
,但没用,因为虚拟机将添加的元素都当成 Object 类型对待了,即我们所熟知的类型擦除。通过 javap -c -p StringTests.class
反汇编查看字节码(P.S. 可以看到,forEach
被转换成了 iterator
):
Compiled from "StringTests.java"
public class StringTests {
public StringTests();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #7 // class java/util/ArrayList
3: dup
4: invokespecial #9 // Method java/util/ArrayList."<init>":()V
7: astore_1
8: aload_1
9: ldc #10 // String abc
11: invokeinterface #12, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
16: pop
17: aload_1
18: bipush 123
20: invokestatic #18 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
23: invokeinterface #12, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
28: pop
29: getstatic #24 // Field java/lang/System.out:Ljava/io/PrintStream;
32: aload_1
33: invokevirtual #30 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
36: aload_1
37: invokeinterface #36, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
42: astore_2
43: aload_2
44: invokeinterface #40, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z
49: ifeq 76
52: aload_2
53: invokeinterface #46, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
58: astore_3
59: aload_3
60: checkcast #50 // class java/lang/String
63: astore 4
65: getstatic #24 // Field java/lang/System.out:Ljava/io/PrintStream;
68: aload 4
70: invokevirtual #52 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
73: goto 43
76: return
}
而只要我们将 List list
改成 List<String> list
,编译器就会报错:
StringTests.java:9: Error: Incompatible type: int cannot be converted to String
list.add(123);
为什么要使用类型擦除?这是 Java 为了向后兼容,支持 JDK 5 以前的代码而作出的妥协。这种做法又被称为移植兼容性,即 Migration Compatibility
。
除了安全风险,另一个自描述性也很重要。因为通过 List<String>
你立刻能明白它想表达什么:一个字符串类型的列表。但如果换成 List
呢?我们不得而知,也因为不可知,从而让它逃过了泛型检查。那就让我们来看看 List
,List<?>
,List<E>
之间的区别:
1)泛型有子类型化的规则,List<E>
是原生态类型 List
的一个子类型,但不是 List<Object>
的子类型。所以,将 List<String>
传给 List
是合法的,但将 List<String>
传给 List<Object>
则不合法;
public static void main(String[] args) {
List<String> list = new ArrayList<>();
unsafeAdd(list); // Error: Incompatible type: List<String> cannot be converted to List<Object>
System.out.println(list);
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
System.out.println(s);
}
}
private static void unsafeAdd(List<Object> list) {
list.add("abc");
list.add(12333);
}
2)如果将上面的 unsafeAdd
方法进行如下调整,那编译错误则会不同:
private static void unsafeAdd(List<?> list) {
for (Object o : list) {
System.out.println(o);
}
list.add(null);
list.add("abc"); // Error: Incompatible type: String cannot be converted to CAP#1
list.add(12333);
}
所以,List<String>
也可以传给无限制通配符类型 List<?>
,但 List<?>
不能添加元素(null
除外),一般主要用于读操作,比如需要统计各种类型集合的数据量。可以想到,之所以不允许添加元素,就是为了防止新元素破坏原有集合的元素类型约束条件。不过,不能加,不代表不能删:
public interface List<E> extends SequencedCollection<E> {
// ...
boolean add(E e);
boolean remove(Object o);
// ...
}
3)由于泛型擦除,所以在对象实例上使用 instanceof
时,需要用 List
或 List<?>
:
private static void unsafeAdd(Object obj) {
if (!(obj instanceof List<String>)) { // Error: Object cannot be safely converted to List<String>
return;
}
List<?> list = (List<?>) obj;
//...
}
这算是泛型使用的一个例外,与此对应的还有它们 class 对象的表示,List.class
是合法的,但 List<String>.class
和 List<?>.class
都不合法。
List<Obiect>
是个参数化类型,表示可以包含任何对象类型的一个集合;
List<?>
则是一个通配符类型,表示只能包含某种未知对象类型的一个集合;
Set
是一个原生态类型,它脱离了泛型系统。前两种是安全的,最后一种不安全。
27 消除非受检的警告
这一条比较简单,就是针对泛型的使用,如果出现了编译警告,我们需要尽力消除它们:
1)Unchecked Cast Warning
List list = new ArrayList<>();
List<String> list2 = (List<String>)list; // Unchecked cast from List to List<String>
System.out.println(list2);
2)Unchecked Method Invocation Warning
void unchecked_warning() {
List list = new ArrayList<>();
process(list); // Unchecked method invocation
}
private void process(List<String> list) {
System.out.println(list);
}
3)Unchecked Parameterized Vararg Type Warning
void unchecked_warning() {
List<String> list = new ArrayList<>();
process(list, list); // Unchecked generics array creation for varargs parameter
}
private void process(List<String>... lists ) {
}
4)Unchecked Conversion Warning
void unchecked_warning() {
List list = new ArrayList<>();
List<String> list1 = list; // Unchecked conversion
list1.add("1");
System.out.println(list1.getFirst());
}
从上面的例子看,Unchecked Cast Warning
和 Unchecked Conversion Warning
两种类型有些雷同,其实它们生成的字节码没啥差异,只是使用场景不同,前一种主要用于类型转换,后一种则是赋值。针对这些警告,我们需要做的便是消除它们,如果确实消除不掉,并且代码安全,那就要使用 @SuppressWarnings("unchecked")
注解。需要说明的是,@SuppressWarnings("unchecked")
使用的范围要尽量小,能用在一行代码上,就不要用在方法上,能用在方法上,就不要用在类上,然后再加上一条注释进行补充说明,比如:
// This cast is corrent because ...
@SuppressWarnings("unchecked")
List<String> list1 = (List<String>)list;
28 列表优于数组
列表优于数组,顾名思义,尽量用列表替代数组。为什么?还是因为代码安全风险。数组是协变(covariant)的,又是具体化(reified)的:
-
协变:如果
Sub
是Master
的子类,那么Sub[]
也是Master[]
的子类型; -
具体:数组在运行时知道并强化其实际类型。
Number[] nums = new Integer[10];
nums[0] = 1000L; // 编译成功,但运行时报错:java.lang.ArrayStoreException: java.lang.Long...
看上面的示例,nums 声明的是 Number[]
,而实际上则是 Integer[]
,这是协变。当我们向数组中存放一个 Long
型数据后,编译代码并不会出错,但在运行时则会抛出 java.lang.ArrayStoreException
,这就是具体化的表现。
而泛型则不同,它往往是在编译期强调其类型,而在运行时丢弃其实际类型,并且 List<Sub>
也不是 List<Master>
的子类型。正因如此,泛型和数组不能很好地混合使用,我们也无法创建泛型相关的数组:
List<Number> nums = new ArrayList<Integer>(); // error: incompatible types: ArrayList<Integer> cannot be converted to List<Number>
List<String>[] lists = new List<String>[2]; // error: generic array creation
List<?>[] lists = new List<?>[2]; // no error
不过,创建无限制通配类型的数组却是合法的,比如上面的 List<?>[] lists = new List<?>[2]
(前文提到,List<?>
不能添加元素,所以它是安全的)。正因为泛型和数组不能很好的合作,所以我们要优先使用集合类型 List<E>
,而不是数组 E[]
。这也许会损失一些性能(毕竟数组是固定大小,不需要扩容或存储其他信息),但换来的是更高的类型安全性和灵活的互用性,这是值得的。
29 优先考虑泛型
泛型是为了屏蔽掉不同的数据类型,让开发者将重心放在算法上,即让一个算法可以支持不同的数据结构。如果是通用类的算法,更要优先使用泛型,这一条想要表达的就是这个。
public class Stack {
private Object[] elements;
// ...
}
将上面代码示例调整为泛型:
public class Stack<E> {
private E[] elements;
// ...
}
通过这样的改造,原来在客户端各处对 Object
进行强制类型转换的代码就都消失了,何乐而不为?
30 优先考虑泛型方法
上一条说的是将类处理为泛型,但如果一个方法可以使用泛型,那也应该优先考虑。最常见的就是各种静态工具方法,比如:
public class Collections {
// Suppresses default constructor, ensuring non-instantiability.
private Collections() {
}
// ...
public static <T>
int binarySearch(List<? extends Comparable<? super T>> list, T key) {
if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
return Collections.indexedBinarySearch(list, key);
else
return Collections.iteratorBinarySearch(list, key);
}
// ...
@SuppressWarnings("unchecked")
public static final <T> Set<T> emptySet() {
return (Set<T>) EMPTY_SET;
}
// ...
}
像 Collections#emptySet()
这类方法,我们经常会使用,这正是泛型方法的强大之处,不管具体类型是什么,都能很好的协作。所以,各类方法,特别是方法内部实现是无状态的,都要优先考虑泛型。此时,我们可以将这个方法当作是一个通用的算法实现。
31 利用有限制通配符来提升API的灵活性
关于这一条有一个规则需要理解,即 PECS
:
producer-extends, consumer-super。另外,所有的 comparable 和 comparator 都是消费者。
下面我们就来理解一下上面提到的灵活性以及 PECS
,先上代码:
import java.util.List;
import java.util.ArrayList;
public class MyStack<E> {
private final List<E> numbers = new ArrayList<>();
void addAll(List<E> list) {
for (E e : list) {
numbers.add(e); // error: incompatible types: E cannot be converted to Number
}
}
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
MyStack<Number> stack = new MyStack<>();
stack.addAll(list); // error: incompatible types: List<Integer> cannot be converted to List<Number>
System.out.println(stack);
}
}
上述代码在编译时会有两处错误,笔者已将错误信息注释在对应位置。之所以有这类问题,是因为泛型与数组不同,它不是协变的。而解决报错也很简单,调整 addAll 方法声明即可:
void addAll(List<? extends E> list) {
for (E e : list) {
numbers.add(e);
}
}
只是简单调整一下泛型的参数,将其变成有限制类型参数
,便有了如此的神奇的效果,而 ? extends E
想表达的含义也很好理解:E 的某个子类型的列表。与之相对应,我们可以添加一个 removeAll 方法,并将移除的对象添加到指定的集合中:
import java.util.List;
import java.util.ArrayList;
public class MyStack<E> {
private final List<E> numbers = new ArrayList<>();
void addAll(List<? extends E> list) {
for (E e : list) {
numbers.add(e);
}
}
void removeAll(List<E> list) {
for (E e : numbers) {
list.add(e);
}
numbers.clear();
}
public static void main(String[] args) {
List<Object> list = new ArrayList<>();
MyStack<Number> stack = new MyStack<>();
stack.removeAll(list); // error: incompatible types: List<Object> cannot be converted to List<Number>
System.out.println(stack);
}
}
编译报错,解决方案也很简单,调整 removeAll 方法的泛型声明:
void removeAll(List<? super E> list) {
for (E e : numbers) {
list.add(e);
}
numbers.clear();
}
上面是 ? extends E
,此处是 ? super E
,没错,这就是 PECS
:在表示生产者或者消费者的输入参数上使用通配符类型,如果参数化类型表示一个生产者 T
,就使用 <? extends T>
;如果它表示个消费者 T
,就使用 <? super T>
。如果同时是消费者和生产者呢?那就使用严格的类型匹配,而不是通配符。
而 java.lang.Comparable
和 java.util.Comparator
天生就是消费者,所以我们应优先使用 Comparable<? super T>
和 Comparator<? super T>
,比如调用 java.util.Collections.sort()
方法:
// Collections#sort
public static <T extends Comparable<? super T>> void sort(List<T> list) {
list.sort(null);
}
// ArrayList#sort
@Override
@SuppressWarnings("unchecked")
public void sort(Comparator<? super E> c) {
final int expectedModCount = modCount;
Arrays.sort((E[]) elementData, 0, size, c);
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
modCount++;
}
// Arrays#sort
public static <T> void sort(T[] a, int fromIndex, int toIndex,
Comparator<? super T> c) {
if (c == null) {
sort(a, fromIndex, toIndex);
} else {
rangeCheck(a.length, fromIndex, toIndex);
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, fromIndex, toIndex, c);
else
TimSort.sort(a, fromIndex, toIndex, c, null, 0, 0);
}
}
public static void sort(Object[] a, int fromIndex, int toIndex) {
rangeCheck(a.length, fromIndex, toIndex);
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, fromIndex, toIndex);
else
ComparableTimSort.sort(a, fromIndex, toIndex, null, 0, 0);
}
// ComparableTimSort#sort
static void sort(Object[] a, int lo, int hi, Object[] work, int workBase, int workLen) {
assert a != null && lo >= 0 && lo <= hi && hi <= a.length;
int nRemaining = hi - lo;
if (nRemaining < 2)
return; // Arrays of size 0 and 1 are always sorted
// If array is small, do a "mini-TimSort" with no merges
if (nRemaining < MIN_MERGE) {
int initRunLen = countRunAndMakeAscending(a, lo, hi);
binarySort(a, lo, hi, lo + initRunLen);
return;
}
// ...
// ...
}
// ComparableTimSort#binarySort
private static void binarySort(Object[] a, int lo, int hi, int start) {
assert lo <= start && start <= hi;
if (start == lo)
start++;
for ( ; start < hi; start++) {
Comparable pivot = (Comparable) a[start];
// Set left (and right) to the index where a[start] (pivot) belongs
int left = lo;
int right = start;
assert left <= right;
/*
* Invariants:
* pivot >= all in [lo, left).
* pivot < all in [right, start).
*/
while (left < right) {
int mid = (left + right) >>> 1;
if (pivot.compareTo(a[mid]) < 0)
right = mid;
else
left = mid + 1;
}
assert left == right;
// ...
// ...
}
最后是关于 List<E>
和 List<?>
的选择问题,下面是给列表中两个位置的元素进行交换的方法声明:
// Two possible declarations for the swap method
public static <E> void swap(List<E> list, int i, int j);
public static void swap(List<?> list, int i, int j);
在公共的 Api 中,哪一个更好?是 List<?>
,因为它简单。如果类型参数只在方法声明中出现一次,就可以用通配符取代它。
32 谨慎并用泛型和可变参数
前面我们演示了显示创建泛型数组是不可行的,代码无法编译:
List<String>[] lists = new List<String>[2]; // error: generic array creation
但有一个例外,那就是可变参数:
class GenericTests {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("abc");
vartypeTest(list, list); // warning: [unchecked] unchecked generic array creation for varargs parameter of type List<String>[]
}
static void vartypeTest(List<String>... lists) { // warning: [unchecked] Possible heap pollution from parameterized vararg type List<String>
List<String>[] listArr = lists;
System.out.println(listArr.length);
for (List<String> list : listArr) {
System.out.println(list);
}
}
}
除了编译时会出现两个警告外,上述代码运行时没有任何问题。那为什么会允许这样一个例外出现呢?因为带有泛型可变参数或参数化类型的方法在实践中用处很大,为了实际利益,Java 设计者们便容忍了这个矛盾的存在。像下面这些方法就是:
// Arrays#asList
@SafeVarargs
@SuppressWarnings("varargs")
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
// Collections#addAll
@SafeVarargs
public static <T> boolean addAll(Collection<? super T> c, T... elements) {
boolean result = false;
for (T element : elements)
result |= c.add(element);
return result;
}
// List#of (Java 9开始支持)
@SafeVarargs
@SuppressWarnings("varargs")
static <E> List<E> of(E... elements) {
switch (elements.length) { // implicit null check of elements
case 0:
@SuppressWarnings("unchecked")
var list = (List<E>) ImmutableCollections.EMPTY_LIST;
return list;
case 1:
return new ImmutableCollections.List12<>(elements[0]);
case 2:
return new ImmutableCollections.List12<>(elements[0], elements[1]);
default:
return ImmutableCollections.listFromArray(elements);
}
}
而那两个警告,只需要增加 @SafeVarargs
注解便能消除:
@SafeVarargs
static void vartypeTest(List<String>... lists) {
// ...
}
@SafeVarargs
是 Java 7 引入的,专门用于消除泛型可变参数警告,使用这个注解,也就意味着承诺:这里的类型是安全的。那什么时候是不安全的?我们来调整一下代码示例:
@SafeVarargs
static void vartypeTest(List<String>... lists) {
Object[] listArr = lists;
System.out.println(listArr.length);
List<Integer> nums = List.of(123);
listArr[0] = nums;
for (List<String> list : lists) {
String firstStr = list.get(0); // java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String
System.out.println(firstStr);
}
}
编译上述代码没有问题,但运行时就会出现 java.lang.ClassCastException
,这就是堆污染(heap pollution),即运行时具体类型被擦除后引发的类型安全问题,再看一个示例:
class GenericTests {
public static void main(String[] args) {
String[] result1 = toArray("a", "b", "c"); // anewarray #7 // class java/lang/String
System.out.println(result1.length); // 3
String[] result = pickTwo("aa", "bb", "cc"); // java.lang.ClassCastException: class [Ljava.lang.Object; cannot be cast to class [Ljava.lang.String;
System.out.println(result.length);
}
static <T> T[] pickTwo(T a, T b, T c) {
switch(ThreadLocalRandom.current().nextInt(3)) {
case 0: return toArray(a, b); // anewarray #2 // class java/lang/Object
case 1: return toArray(a, c); // anewarray #2 // class java/lang/Object
case 2: return toArray(b, c); // anewarray #2 // class java/lang/Object
default: throw new AssertionError();
}
}
@SafeVarargs
static <T> T[] toArray(T... args) {
return args;
}
}
这里的问题在于,从一个泛型函数调用另一个泛型函数时,如果使用了泛型可变参数,此时创建的数组不再是实际类型(这里是 String[]
),而是 Object[]
。这是编译时决定的,所以就出现了类型转换异常。使用 javap
命令可以看到创建的实际类型,笔者已对其进行了注释。那如何确认这类代码是类型安全的呢?有两个条件:
- 没有在可变参数数组中保存任何值;
- 没有在不被信任的代码中开放该数组(或其克隆程序)。
如果不想使用 @SafeVarargs
注解(该注解在 Java 8 中只能修饰静态方法和 final
实例方法,在 Java 9 中开始支持私有实例方法),我们也可以使用 List 参数来代替可变参数数组:
class GenericTests {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("abc");
vartypeTest(List.of(list, list)); // 这里调整为 List
}
static void vartypeTest(List<List<String>> lists) { // 这里是 List<List>>
System.out.println(lists.size());
for (List<String> list : lists) {
System.out.println(list);
}
}
}
当然,这里的性能肯定要稍微差一点,不过编译器能证明它是安全的,没有类型安全风险。
33 优先考虑类型安全的异构容器
类型安全的异构容器指的是什么?我们先上代码:
class Favorites {
private Map<Class<?>, Object> favorites = new HashMap<>();
public <T> void putFavorite(Class<T> type, T instance) {
favorites.put(Objects.requireNonNull(type), instance);
}
public <T> T getFavorite(Class<T> type) {
return type.cast(favorites.get(type));
}
public static void main(String[] args) {
Favorites t3 = new Favorites();
t3.putFavorite(String.class, "Hello, World!");
t3.putFavorite(Integer.class, 123);
System.out.println("String: " + t3.getFavorite(String.class));
System.out.println("Integer: " + t3.getFavorite(Integer.class));
System.out.println("Long: " + t3.getFavorite(Long.class));
}
}
这里的 Favorites
类就是一个类型安全的异构容器:
1)类型安全指的是它的 key 是泛型化的,并以此来保证 value 的值类型与 key 一致;
2)而异构是指它的 key 都是不同的类型,不像 List<T>
, Set<E>
中只有一种类型参数。
像这样,将 key(而不是 value) 进行参数化,就给我们带来了业务实现的灵活性。注意代码第 10 行中的 type.cast(favorites.get(type))
,这里使用 cast 方法的目的就是确保获得的值一定是 T 类型的实例,否则这行代码将会抛出异常:
public final class Class<T> implements java.io.Serializable,
GenericDeclaration,Type,AnnotatedElement,TypeDescriptor.OfField<Class<?>>,Constable {
// ...
@IntrinsicCandidate
public native boolean isInstance(Object obj);
//...
@SuppressWarnings("unchecked")
@IntrinsicCandidate
public T cast(Object obj) {
if (obj != null && !isInstance(obj))
throw new ClassCastException(cannotCastMsg(obj));
return (T) obj;
}
// ...
//...
}
不过,上面的 Favorites
仍然有类型安全风险,因为客户端可能会使用如下的形式:
public static void main(String[] args) {
Favorites t3 = new Favorites();
Class clazz = Long.class;
t3.putFavorite(clazz, "abc");
System.out.println("Long: " + t3.getFavorite(Long.class));
}
通过使用 Class 对象的原生类型,就能将一个字符串类型存储到 Long 类型的 key 中,当然,编译期会有警告,但不会报错:
$ javac -J-Duser.language=en -J-Duser.country=US -Xlint:unchecked Favorites.java
Favorites.java:22: warning: [unchecked] unchecked method invocation: method putFavorite in class Favorites is applied to given types
t3.putFavorite(clazz, "abc");
^
required: Class<T>,T
found: Class,String
where T is a type-variable:
T extends Object declared in method <T>putFavorite(Class<T>,T)
Favorites.java:22: warning: [unchecked] unchecked conversion
t3.putFavorite(clazz, "abc");
^
required: Class<T>
found: Class
where T is a type-variable:
T extends Object declared in method <T>putFavorite(Class<T>,T)
2 warnings
如果想发现问题,只能等到运行期调用 getFavorite 方法才能发现,如果想早一点发觉呢?也是可以的:
public <T> void putFavorite(Class<T> type, T instance) {
favorites.put(Objects.requireNonNull(type), type.cast(instance));
}
同样是使用 Class#cast
方法,只不过我们将其提前到了 putFavorite 方法,这样 Favorites
中存储的就都是类型安全的数据了。
最后是一个小技巧,当需要将一个 Class<?>
传给有限制通配符类型的 Class<? extends Annotation>
时,怎么做才不会弹出警告?答案是使用 Class#asSubclass
方法,比如:
Class<?> clazz = ...;
Class<? extends Annotation> subClazz = clazz.asSubclass(Annotation.class);
同样的,如果 clazz 不是 Annotation 的子类,该转换将会抛出异常:
@SuppressWarnings("unchecked")
public <U> Class<? extends U> asSubclass(Class<U> clazz) {
if (clazz.isAssignableFrom(this))
return (Class<? extends U>) this;
else
throw new ClassCastException(this.toString());
}
好,本期内容就总结到这里。下图截取自耗子叔的《左耳听风》专栏,他认为泛型的本质是:屏蔽掉数据和操作数据的细节,让算法更为通用,让编程者更多地关注算法的结构,而不是在算法中处理不同的数据类型,供参考。