企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
## [泛型和类型安全的集合](https://lingcoder.gitee.io/onjava8/#/book/12-Collections?id=%e6%b3%9b%e5%9e%8b%e5%92%8c%e7%b1%bb%e5%9e%8b%e5%ae%89%e5%85%a8%e7%9a%84%e9%9b%86%e5%90%88) 使用 Java 5 之前的集合的一个主要问题是编译器允许你向集合中插入不正确的类型。例如,考虑一个**Apple**对象的集合,这里使用最基本最可靠的**ArrayList**。现在,可以把**ArrayList**看作“可以自动扩充自身尺寸的数组”来看待。使用**ArrayList**相当简单:创建一个实例,用`add()`插入对象;然后用`get()`来访问这些对象,此时需要使用索引,就像数组那样,但是不需要方括号。[^2](https://lingcoder.gitee.io/onjava8/#/%E8%BF%99%E9%87%8C%E6%98%AF%E6%93%8D%E4%BD%9C%E7%AC%A6%E9%87%8D%E8%BD%BD%E7%9A%84%E7%94%A8%E6%AD%A6%E4%B9%8B%E5%9C%B0%EF%BC%8CC++%E5%92%8CC?id=%e7%9a%84%e9%9b%86%e5%90%88%e7%b1%bb%e9%83%bd%e4%bd%bf%e7%94%a8%e6%93%8d%e4%bd%9c%e7%ac%a6%e9%87%8d%e8%bd%bd%e7%94%9f%e6%88%90%e4%ba%86%e6%9b%b4%e7%ae%80%e6%b4%81%e7%9a%84%e8%af%ad%e6%b3%95%e3%80%82)**ArrayList**还有一个`size()`方法,来说明集合中包含了多少个元素,所以不会不小心因数组越界而引发错误(通过抛出*运行时异常*,[异常](https://lingcoder.gitee.io/onjava8/#/)章节介绍了异常)。 在本例中,**Apple**和**Orange**都被放到了集合中,然后将它们取出。正常情况下,Java编译器会给出警告,因为这个示例没有使用泛型。在这里,使用特定的注解来抑制警告信息。注解以“@”符号开头,可以带参数。这里的`@SuppressWarning`注解及其参数表示只抑制“unchecked”类型的警告([注解](https://lingcoder.gitee.io/onjava8/#/)章节将介绍更多有关注解的信息): ~~~ // collections/ApplesAndOrangesWithoutGenerics.java // Simple collection use (suppressing compiler warnings) // {ThrowsException} import java.util.*; class Apple { private static long counter; private final long id = counter++; public long id() { return id; } } class Orange {} public class ApplesAndOrangesWithoutGenerics { @SuppressWarnings("unchecked") public static void main(String[] args) { ArrayList apples = new ArrayList(); for(int i = 0; i < 3; i++) apples.add(new Apple()); // No problem adding an Orange to apples: apples.add(new Orange()); for(Object apple : apples) { ((Apple) apple).id(); // Orange is detected only at run time } } } /* Output: ___[ Error Output ]___ Exception in thread "main" java.lang.ClassCastException: Orange cannot be cast to Apple at ApplesAndOrangesWithoutGenerics.main(ApplesA ndOrangesWithoutGenerics.java:23) */ ~~~ **Apple**和**Orange**是截然不同的,它们除了都是**Object**之外没有任何共同点(如果一个类没有显式地声明继承自哪个类,那么它就自动继承自**Object**)。因为**ArrayList**保存的是**Object**,所以不仅可以通过**ArrayList**的`add()`方法将**Apple**对象放入这个集合,而且可以放入**Orange**对象,这无论在编译期还是运行时都不会有问题。当使用**ArrayList**的`get()`方法来取出你认为是**Apple**的对象时,得到的只是**Object**引用,必须将其转型为**Apple**。然后需要将整个表达式用括号括起来,以便在调用**Apple**的`id()`方法之前,强制执行转型。否则,将会产生语法错误。 在运行时,当尝试将**Orange**对象转为**Apple**时,会出现输出中显示的错误。 在[泛型](https://lingcoder.gitee.io/onjava8/#/)章节中,你将了解到使用 Java 泛型来创建类可能很复杂。但是,使用预先定义的泛型类却相当简单。例如,要定义一个用于保存**Apple**对象的**ArrayList**,只需要使用**ArrayList**来代替**ArrayList**。尖括号括起来的是*类型参数*(可能会有多个),它指定了这个集合实例可以保存的类型。 通过使用泛型,就可以在编译期防止将错误类型的对象放置到集合中。[^3](https://lingcoder.gitee.io/onjava8/#/%E5%9C%A8[%E6%B3%9B%E5%9E%8B]()%E7%AB%A0%E8%8A%82%E7%9A%84%E6%9C%AB%E5%B0%BE%EF%BC%8C%E6%9C%89%E4%B8%AA%E5%85%B3%E4%BA%8E%E8%BF%99%E4%B8%AA%E9%97%AE%E9%A2%98%E6%98%AF%E5%90%A6%E5%BE%88%E4%B8%A5%E9%87%8D%E7%9A%84%E8%AE%A8%E8%AE%BA%E3%80%82%E4%BD%86%E6%98%AF%EF%BC%8C[%E6%B3%9B%E5%9E%8B]()%E7%AB%A0%E8%8A%82%E8%BF%98%E5%B0%86%E5%B1%95%E7%A4%BAJava%E6%B3%9B%E5%9E%8B%E8%BF%9C%E4%B8%8D%E6%AD%A2%E6%98%AF%E7%B1%BB%E5%9E%8B%E5%AE%89%E5%85%A8%E7%9A%84%E9%9B%86%E5%90%88%E8%BF%99%E4%B9%88%E7%AE%80%E5%8D%95%E3%80%82)下面还是这个示例,但是使用了泛型: ~~~ // collections/ApplesAndOrangesWithGenerics.java import java.util.*; public class ApplesAndOrangesWithGenerics { public static void main(String[] args) { ArrayList<Apple> apples = new ArrayList<>(); for(int i = 0; i < 3; i++) apples.add(new Apple()); // Compile-time error: // apples.add(new Orange()); for(Apple apple : apples) { System.out.println(apple.id()); } } } /* Output: 0 1 2 */ ~~~ 在**apples**定义的右侧,可以看到`new ArrayList<>()`。这有时被称为“菱形语法”(diamond syntax)。在 Java 7 之前,必须要在两端都进行类型声明,如下所示: ~~~ ArrayList<Apple> apples = new ArrayList<Apple>(); ~~~ 随着类型变得越来越复杂,这种重复产生的代码非常混乱且难以阅读。程序员发现所有类型信息都可以从左侧获得,因此,编译器没有理由强迫右侧再重复这些。虽然*类型推断*(type inference)只是个很小的请求,Java 语言团队仍然欣然接受并进行了改进。 有了**ArrayList**声明中的类型指定,编译器会阻止将**Orange**放入**apples**,因此,这会成为一个编译期错误而不是运行时错误。 使用泛型,从**List**中获取元素不需要强制类型转换。因为**List**知道它持有什么类型,因此当调用`get()`时,它会替你执行转型。因此,使用泛型,你不仅知道编译器将检查放入集合的对象类型,而且在使用集合中的对象时也可以获得更清晰的语法。 当指定了某个类型为泛型参数时,并不仅限于只能将确切类型的对象放入集合中。向上转型也可以像作用于其他类型一样作用于泛型: ~~~ // collections/GenericsAndUpcasting.java import java.util.*; class GrannySmith extends Apple {} class Gala extends Apple {} class Fuji extends Apple {} class Braeburn extends Apple {} public class GenericsAndUpcasting { public static void main(String[] args) { ArrayList<Apple> apples = new ArrayList<>(); apples.add(new GrannySmith()); apples.add(new Gala()); apples.add(new Fuji()); apples.add(new Braeburn()); for(Apple apple : apples) System.out.println(apple); } } /* Output: GrannySmith@15db9742 Gala@6d06d69c Fuji@7852e922 Braeburn@4e25154f */ ~~~ 因此,可以将**Apple**的子类型添加到被指定为保存**Apple**对象的集合中。 程序的输出是从**Object**默认的`toString()`方法产生的,该方法打印类名,后边跟着对象的散列码的无符号十六进制表示(这个散列码是通过`hashCode()`方法产生的)。将在[附录:理解equals和hashCode方法](https://lingcoder.gitee.io/onjava8/#/)中了解有关散列码的内容。