Tips
《Effective Java, Third Edition》一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将近8年的时间,但随着Java 6,7,8,甚至9的发布,Java语言发生了深刻的变化。
在这里第一时间翻译成中文版。供大家学习分享之用。
自Java 5以来,泛型已经成为该语言的一部分。 在泛型之前,你必须转换从集合中读取的每个对象。 如果有人不小心插入了错误类型的对象,则在运行时可能会失败。 使用泛型,你告诉编译器在每个集合中允许哪些类型的对象。 编译器会自动插入强制转换,并在编译时告诉你是否尝试插入错误类型的对象。 这样做的结果是既安全又清晰的程序,但这些益处,不限于集合,是有代价的。 本章告诉你如何最大限度地提高益处,并将并发症降至最低。
26. 不要使用原始类型首先,有几个术语。一个类或接口,它的声明有一个或多个类型参数( type parameters ),被称之为泛型类或泛型接口[JLS,8.1.2,9.1.2]。 例如,List接口具有单个类型参数E,表示其元素类型。 接口的全名是List<E>(读作“E”的列表),但是人们经常称它为List。 泛型类和接口统称为泛型类型(generic types)。
每个泛型定义了一组参数化类型(parameterized types),它们由类或接口名称组成,后跟一个与泛型类型的形式类型参数[JLS,4.4,4.5]相对应的实际类型参数的尖括号“<>”列表。 例如,List<String>(读作“字符串列表”)是一个参数化类型,表示其元素类型为String的列表。 (String是与形式类型参数E相对应的实际类型参数)。
最后,每个泛型定义了一个原始类型( raw type),它是没有任何类型参数的泛型类型的名称[JLS,4.8]。 例如,对应于List<E>的原始类型是List。 原始类型的行为就像所有的泛型类型信息都从类型声明中被清除一样。 它们的存在主要是为了与没有泛型之前的代码相兼容。
在泛型被添加到Java之前,这是一个典型的集合声明。 从Java 9开始,它仍然是合法的,但并不是典型的声明方式了:
// Raw collection type - don't do this! // My stamp collection. Contains only Stamp instances. private final Collection stamps = ... ;如果你今天使用这个声明,然后不小心把coin实例放入你的stamp集合中,错误的插入编译和运行没有错误(尽管编译器发出一个模糊的警告):
// Erroneous insertion of coin into stamp collection stamps.add(new Coin( ... )); // Emits "unchecked call" warning直到您尝试从stamp集合中检索coin实例时才会发生错误:
// Raw iterator type - don't do this! for (Iterator i = stamps.iterator(); i.hasNext(); ) Stamp stamp = (Stamp) i.next(); // Throws ClassCastException stamp.cancel();正如本书所提到的,在编译完成之后尽快发现错误是值得的,理想情况是在编译时。 在这种情况下,直到运行时才发现错误,在错误发生后的很长一段时间,以及可能远离包含错误的代码的代码中。 一旦看到ClassCastException,就必须搜索代码类库,查找将coin实例放入stamp集合的方法调用。 编译器不能帮助你,因为它不能理解那个说“仅包含stamp实例”的注释。
对于泛型,类型声明包含的信息,而不是注释:
// Parameterized collection type - typesafe private final Collection<Stamp> stamps = ... ;从这个声明中,编译器知道stamps集合应该只包含Stamp实例,并保证它是true,假设你的整个代码类库编译时不发出(或者抑制;参见条目27)任何警告。 当使用参数化类型声明声明stamps时,错误的插入会生成一个编译时错误消息,告诉你到底发生了什么错误:
Test.java:9: error: incompatible types: Coin cannot be converted to Stamp c.add(new Coin()); ^当从集合中检索元素时,编译器会为你插入不可见的强制转换,并保证它们不会失败(再假设你的所有代码都不会生成或禁止任何编译器警告)。 虽然意外地将coin实例插入stamp集合的预期可能看起来很牵强,但这个问题是真实的。 例如,很容易想象将BigInteger放入一个只包含BigDecimal实例的集合中。
如前所述,使用原始类型(没有类型参数的泛型)是合法的,但是你不应该这样做。 如果你使用原始类型,则会丧失泛型的所有安全性和表达上的优势。 鉴于你不应该使用它们,为什么语言设计者首先允许原始类型呢? 答案是为了兼容性。 泛型被添加时,Java即将进入第二个十年,并且有大量的代码没有使用泛型。 所有这些代码都是合法的,并且与使用泛型的新代码进行交互操作被认为是至关重要的。 将参数化类型的实例传递给为原始类型设计的方法必须是合法的,反之亦然。 这个需求,被称为迁移兼容性,驱使决策支持原始类型,并使用擦除来实现泛型(条目 28)。