|
| 1 | +《《《 [返回首页](../README.md) <br/> |
| 2 | +《《《 [上一节](04_The_Get_and_Put_Principle.md) |
| 3 | + |
| 4 | +## 数组 |
| 5 | + |
| 6 | +- 在`Java`中对列表和数组的处理进行比较是有益的,同时牢记替换原则和获取和放置原则。 |
| 7 | +- 在`Java`中,数组的子类型是协变的,这意味着当`S`是`T`的子类型时,类型`S []`被认为是`T []`的一个子类型。 |
| 8 | +考虑下面的代码片段,它分配一个整数数组,分配一个数组 的数字,然后尝试在数组中分配一个`double`: |
| 9 | + |
| 10 | + ```java |
| 11 | + Integer[] ints = new Integer[] {1,2,3}; |
| 12 | + Number[] nums = ints; |
| 13 | + nums[2] = 3.14; // array store exception |
| 14 | + assert Arrays.toString(ints).equals("[1, 2, 3.14]"); // uh oh! |
| 15 | + ``` |
| 16 | +- 这个程序有什么问题,因为它把一个整数数组放到一个`double`中!哪里有问题? |
| 17 | +由于`Integer []`被认为是`Number []`的子类型,所以根据替换原则, |
| 18 | +第二行的赋值必须是合法的。 相反,问题出现在第三行,并在运行时被捕获。 |
| 19 | +当一个数组被分配时(如在第一行),它被标记为它的被指定的类型(它的组件类型的运行时表示, |
| 20 | +在这个例子中是`Integer`),并且每次数组被分配到 第三行),如果指定的类型与指定的值不兼容, |
| 21 | +则会引发数组存储异常(在这种情况下,`double`不能存储到`Integer`数组中)。 |
| 22 | + |
| 23 | +- 相比之下,泛型的子类型关系是不变的,意味着类型`List<S>`不被认为是`List<T>`的子类型, |
| 24 | +除了`S`和`T`相同的普通情况。 这是一个类似于前一个的代码片段,用列表替换数组: |
| 25 | + |
| 26 | + ```java |
| 27 | + List<Integer> ints = Arrays.asList(1,2,3); |
| 28 | + List<Number> nums = ints; // compile-time error |
| 29 | + nums.set(2, 3.14); |
| 30 | + assert ints.toString().equals("[1, 2, 3.14]"); // uh oh! |
| 31 | + ``` |
| 32 | +- 由于`List<Integer>`不被认为是`List<Number>`的子类型, |
| 33 | +因此在第二行而不是第三行检测到问题,并且在编译时检测到,而不是在运行时检测到。 |
| 34 | + |
| 35 | +- 通配符重新引入泛型的协变子类型,在当S是T的子类型时,这种类型中`List<S>`被认为是`List<? extends T>`的子类型? |
| 36 | +这是片段的第三个变体: |
| 37 | + |
| 38 | + ```java |
| 39 | + List<Integer> ints = Arrays.asList(1,2,3); |
| 40 | + List<? extends Number> nums = ints; |
| 41 | + nums.set(2, 3.14); // compile-time error |
| 42 | + assert ints.toString().equals("[1, 2, 3.14]"); // uh oh! |
| 43 | + ``` |
| 44 | +- 和数组一样,第三行是错误的,但是与数组相比,这个问题在编译时被检测到, |
| 45 | +而不是运行时。 该分配违反了“获取和放置原则”,因为您不能将值放入使用`extends`通配符声明的类型中。 |
| 46 | + |
| 47 | +- 通配符还引入了泛型的逆变分类,当`S`是`T`的超类型(而不是子类型)时, |
| 48 | +这种类型`List<S>`是被认为是`List<? super T>`一个子类型? 。 数组不支持逆分类。 |
| 49 | +例如,回想一下,方法`count`接受了一个类型`Collection<? super Integer>`的参数。 |
| 50 | +并填充整数。 因为`Java`不允许你编写`(?super Integer)[]`,所以没有与数组做同样的方法。 |
| 51 | + |
| 52 | +- 在编译时而不是在运行时检测问题会带来两个优点,一个小问题和一个主要问题。 |
| 53 | +次要优点是它更有效率。 系统不需要在运行时随身携带一个元素类型的描述, |
| 54 | +而且每次执行一个数组赋值时,系统都不需要检查这个描述。 |
| 55 | +主要优点是编译器检测到一个常见的错误族。 这改善了程序生命周期的各个方面: |
| 56 | +编码,调试,测试和维护都变得更简单,更快速,而且更轻量级。 |
| 57 | + |
| 58 | +- 除了错误之前被捕获的事实之外,还有许多其他原因可以将收集类更倾向于数组。 |
| 59 | +集合比数组更灵活。数组支持的唯一操作是获取或设置一个组件,并且该表示是固定的。 |
| 60 | +集合支持许多额外的操作,包括测试遏制,添加和删除元素,比较或合并两个集合, |
| 61 | +以及提取列表的子列表。集合可以是列表(其中的顺序是重要的,元素可以重复) |
| 62 | +或集合(顺序不重要,元素可能不重复),可以使用许多表示,包括数组,链表, |
| 63 | +树和散列表。最后,便利类Collections和Arrays的比较表明,集合提供了非数组提供的许多操作, |
| 64 | +包括旋转或打乱列表的操作,查找集合的最大值以及使集合不可修改或同步。 |
| 65 | + |
| 66 | +- 尽管如此,还是有少数情况下数组比数据集更受欢迎。 原始类型的数组更有效率, |
| 67 | +因为它们不涉及拳击; 并分配到这样的数组不需要检查数组存储异常, |
| 68 | +因为基元类型的数组没有子类型。 尽管检查了数组存储异常, |
| 69 | +即使是使用当前代编译器的引用类型数组也可能比集合类更有效, |
| 70 | +所以您可能希望在关键的内部循环中使用数组。 与往常一样, |
| 71 | +您应该测量性能来验证这样的设计,尤其是因为未来的编译器可能会专门优化收集类。 |
| 72 | +最后,在某些情况下,出于兼容性的原因,数组可能是优选。 |
| 73 | + |
| 74 | +- 总而言之,最好在编译时检测错误,而不是在运行时检测错误,但是`Java`数组在运行时被强制检测到某些错误, |
| 75 | +因为决定做出数组子类型协变。 这是一个很好的决定? 在泛型出现之前,这是绝对必要的。 |
| 76 | +例如,看下面的方法,这些方法用于对任何数组进行排序或使用给定值填充数组: |
| 77 | + |
| 78 | + ```java |
| 79 | + public static void sort(Object[] a); |
| 80 | + public static void fill(Object[] a, Object val); |
| 81 | + ``` |
| 82 | +- 由于协变,这些方法可以用来排序或填充任何引用类型的数组。 没有协变性, |
| 83 | +没有泛型,就没有办法声明适用于所有类型的方法。 但是,现在我们已经有了泛型, |
| 84 | +协变阵列就不再需要了。 现在我们可以给这些方法以下签名,直接说明它们适用于所有类型: |
| 85 | + |
| 86 | + ```java |
| 87 | + public static <T> void sort(T[] a); |
| 88 | + public static <T> void fill(T[] a, T val); |
| 89 | + ``` |
| 90 | +- 从某种意义上讲,协变数组是早期`Java`版本中缺乏泛型的人为因素。 一旦你有泛型,协变数组可能是错误的设计选择, |
| 91 | +保留它们的唯一原因是向后兼容。 |
| 92 | + |
| 93 | +- 第6.4节 - 第6.8节讨论泛型和数组之间的不方便的交互。 出于多种目的, |
| 94 | +将数组视为一个已弃用的类型可能是明智的。我们回到6.9节的这一点。 |
| 95 | + |
| 96 | +《《《 [上一节](06_Wildcards_Versus_Type_Parameters.md) <br/> |
| 97 | +《《《 [返回首页](../README.md) |
0 commit comments