0
点赞
收藏
分享

微信扫一扫

《Effective Java》—— 泛型篇

爱奔跑的读书者 2022-04-16 阅读 63
后端

在泛型篇开始介绍之前,要先明确几个书中的术语

术语中文含义举例所在条目
Parameterized type参数化类型List<String>条目 26
Actual type parameter实际类型参数String条目 26
Generic type泛型类型List<E>条目 26 和 条目 29
Formal type parameter形式类型参数E条目 26
Unbounded wildcard type无限制通配符类型List<?>条目 26
Raw type原始类型List条目 26
Bounded type parameter限制类型参数<E extends Number>条目 29
Recursive type bound递归类型限制<T extends Comparable<T>>条目 30
Bounded wildcard type限制通配符类型List<? extends Number>条目 31
Generic method泛型方法static <E> List<E> asList(E[] a)条目 30
Type token类型令牌String.class条目 33

26、不要使用原始类型(如List)

每一种泛型类型都定义一个原生态类型,例如List<String>对应的原生态类型就是List,他们的存在主要是为了与泛型出现之前的代码兼容。

有了泛型之后,类型声明中可以包含信息,而不是通过注释去提醒:

private final Collection<Stamp> stamps = ....

从这个声明中,编译器知道stamps 集合应该只包含Stamp 实例,错误的插入会生成一个编译时错误消息,提醒具体是哪里出错了:

Test.java:9: error: incompatible types: Coin cannot be converted to Stamp
c.add(new Coin());
         ^

当从集合中检索元素时,编译器会为你插入不可⻅的强制转换


如果使用诸如List 之类的原始类型,则会丢失类型安全性,但是如果使用参数化类型(例如List<String>)则不会

原始类型List和参数化类型List 之间有什么区别呢?
前者逃避了泛型检查,而后者明确地告诉编译器,它能够保存任何类型的对象。

可以将List<String> 传递给List 类型的参数,但不能将其传递给List<Object> 类型的参数。泛型有子类型化的规则,List<String> 是原始类型 List 的子类型,但不是参数化类型List<Object> 的子类型

为了更直观的说明,给出下面的代码:

public static void main(String[] args) {
        List<String> strings = new ArrayList<>();
        unsafeAdd(strings, Integer.valueOf(42));
        String s = strings.get(0); // Has compiler-generated cast
    }

    private static void unsafeAdd(List list, Object o) {
        list.add(o);
    }

如果运行该程序,则当程序尝试调用strings.get(0)的结果(一个Integer)转换为一个String 时,会得到ClassCastException 异常。

如果在unsafeAdd的声明中的原始类型List 替换参数化类型List<Object>,则编译器直接就会给出报错信息:

在不确定或不在意集合中元素类型时,可能会用到原始类型。例如编写一个返回两个集合中重复元素个数的程序:

static int numElementsInCommon(Set s1, Set s2) {
   int result = 0;
   for (Object o1 : s1)
	   if (s2.contains(o1))
		result++;
    return result;
}

这种方法使用原始类型,是危险的。安全替代方式是使用无限制通配符类型(unbounded wildcard types)。如果要使用泛型类型,但不知道或关心实际类型参数是什么,则可以使用问号来代替。例如,泛型类型 Set<E> 的无限制通配符类型是Set<?>

static int numElementsInCommon(Set<?> s1, Set<?> s2){...}

“不要使用原始类型”这条规则有几个特例情况:

1. 必须在类签名(class literals)中使用原始类型

例如List.classString[].class 和int.class 都是合法的,但List<String>.class 和List<?>.class 不合法

2. 因为泛型类型信息在运行时被擦除,所以在<?>以外的参数化类型上使用instanceof是非法的
下面是使用泛型类型的instanceof 运算的示例:

if (o instanceof Set) { // Raw type
	Set<?> s = (Set<?>) o; // Wildcard type
	...
}

一旦确定 o 对象是一个Set,则必须将其转换为通配符Set<?>。这是一个强制转换,所以不会导致编译器警告。

举报

相关推荐

0 条评论