@@ -2832,9 +2832,6 @@ public class GenericReading {
28322832
28332833```java
28342834// generics/UnboundedWildcards1.java
2835- // (c)2017 MindView LLC: see Copyright.txt
2836- // We make no guarantees that this code is fit for any purpose.
2837- // Visit http://OnJava8.com for more book information.
28382835import java.util.*;
28392836
28402837public class UnboundedWildcards1 {
@@ -2896,9 +2893,6 @@ public class UnboundedWildcards1 {
28962893
28972894```java
28982895// generics/UnboundedWildcards2.java
2899- // (c)2017 MindView LLC: see Copyright.txt
2900- // We make no guarantees that this code is fit for any purpose.
2901- // Visit http://OnJava8.com for more book information.
29022896import java. util. * ;
29032897
29042898public class UnboundedWildcards2 {
@@ -2939,9 +2933,6 @@ public class UnboundedWildcards2 {
29392933
29402934```java
29412935// generics/Wildcards.java
2942- // (c)2017 MindView LLC: see Copyright.txt
2943- // We make no guarantees that this code is fit for any purpose.
2944- // Visit http://OnJava8.com for more book information.
29452936// Exploring the meaning of wildcards
29462937
29472938public class Wildcards {
@@ -3244,9 +3235,6 @@ public class Wildcards {
32443235
32453236```java
32463237// generics/CaptureConversion.java
3247- // (c)2017 MindView LLC: see Copyright.txt
3248- // We make no guarantees that this code is fit for any purpose.
3249- // Visit http://OnJava8.com for more book information.
32503238
32513239public class CaptureConversion {
32523240 static <T > void f1 (Holder<T > holder ) {
@@ -3312,6 +3300,255 @@ Double
33123300
33133301## 问题
33143302
3303+ 本节将阐述在使用Java 泛型时会出现的各类问题。
3304+
3305+ ### 任何基本类型都不能作为类型参数
3306+
3307+ 正如本章早先提到过的,你将在Java 泛型中发现的限制之一是,不能将基本类型用作类型参数。因此,不能创建 `ArrayList<int> ` 之类的东西。
3308+ 解决之道是使用基本类型的包装器类以及JavaSE5 的自动包装机制。如果创建一个 `ArrayList<Integer > `,并将基本类型 ** int ** 应用于这个容器,那么你将发现自动包装机制将自动地实现 ** int ** 到 ** Integer ** 的双向转换——因此,这几乎就像是有一个 `ArrayList<int> `一样:
3309+
3310+ ```java
3311+ // generics/ListOfInt.java
3312+ // Autoboxing compensates for the inability
3313+ // to use primitives in generics
3314+ import java.util.*;
3315+ import java. util. stream. * ;
3316+
3317+ public class ListOfInt {
3318+ public static void main (String [] args ) {
3319+ List<Integer > li = IntStream . range(38 , 48 )
3320+ .boxed() // Converts ints to Integers
3321+ .collect(Collectors . toList());
3322+ System . out. println(li);
3323+ }
3324+ }
3325+ /* Output:
3326+ [38, 39, 40, 41, 42, 43, 44, 45, 46, 47]
3327+ */
3328+ ```
3329+
3330+ 注意,自动包装机制甚至允许用foreach语法来产生 ** int ** 。
3331+ 通常,这种解决方案工作得很好——能够成功地存储和读取 ** int ** ,有一些转换碰巧在发生的同时会对你屏蔽掉。但是,如果性能成为了问题,就需要使用专门适配基本类型的容器版。** Org . apache. commons. collections. primitives** 就是一种开源的这类版本。
3332+ 下面是另外一种方式,它可以创建持有 ** Byte ** 的 ** Set ** :
3333+
3334+ ```java
3335+ // generics/ByteSet.java
3336+ import java. util. * ;
3337+
3338+ public class ByteSet {
3339+ Byte [] possibles = { 1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 };
3340+ Set<Byte > mySet =
3341+ new HashSet<> (Arrays . asList(possibles));
3342+ // But you can't do this:
3343+ // Set<Byte> mySet2 = new HashSet<>(
3344+ // Arrays.<Byte>asList(1,2,3,4,5,6,7,8,9));
3345+ }
3346+ ```
3347+
3348+ 注意,自动包装机制解决了一些问题,但并不是解决了所有问题。
3349+
3350+ 在下面的示例中,** FillArray** 接口包含一些通用方法,这些方法使用 **Supplier** 来用对象填充数组(这使得类泛型在本例中无法工作,因为这个方法是静态的) **Supplier** 实现来自“数组”一章,并且在 `main ()` 中,可以看到 `FillArray.fill ()` 使用它在数组中填充对象:
3351+
3352+ ```java
3353+ // generics/PrimitiveGenericTest.java
3354+ import onjava.*;
3355+ import java. util. * ;
3356+ import java. util. function. * ;
3357+
3358+ // Fill an array using a generator:
3359+ interface FillArray {
3360+ static <T > T [] fill (T [] a , Supplier<T > gen ) {
3361+ Arrays . setAll(a, n - > gen. get());
3362+ return a;
3363+ }
3364+ static int [] fill (int [] a , IntSupplier gen ) {
3365+ Arrays . setAll(a, n - > gen. getAsInt());
3366+ return a;
3367+ }
3368+ static long [] fill (long [] a , LongSupplier gen ) {
3369+ Arrays . setAll(a, n - > gen. getAsLong());
3370+ return a;
3371+ }
3372+ static double [] fill (double [] a , DoubleSupplier gen ) {
3373+ Arrays . setAll(a, n - > gen. getAsDouble());
3374+ return a;
3375+ }
3376+ }
3377+
3378+ public class PrimitiveGenericTest {
3379+ public static void main (String [] args ) {
3380+ String [] strings = FillArray . fill(
3381+ new String [5 ], new Rand .String (9 ));
3382+ System . out. println(Arrays . toString(strings));
3383+ int [] integers = FillArray . fill(
3384+ new int [9 ], new Rand .Pint ());
3385+ System . out. println(Arrays . toString(integers));
3386+ }
3387+ }
3388+ /* Output:
3389+ [btpenpccu, xszgvgmei, nneeloztd, vewcippcy, gpoalkljl]
3390+ [635, 8737, 3941, 4720, 6177, 8479, 6656, 3768, 4948]
3391+ */
3392+ ```
3393+
3394+ 自动装箱不适用于数组,因此我们必须创建 `FillArray.fill ()` 的重载版本,或创建产生 **Wrapped** 输出的生成器。 **FillArray** 仅比 `java.util.Arrays.setAll ()` 有用,因为它返回填充的数组。
3395+
3396+ ### 实现参数化接口
3397+
3398+ 一个类不能实现同一个泛型接口的两种变体,由于擦除的原因,这两个变体会成为相同的接口。下面是产生这种冲突的情况:
3399+
3400+ ```java
3401+ // generics/MultipleInterfaceVariants.java
3402+ // {WillNotCompile}
3403+ package generics;
3404+
3405+ interface Payable <T> {}
3406+
3407+ class Employee implements Payable<Employee > {}
3408+
3409+ class Hourly extends Employee
3410+ implements Payable<Hourly > {}
3411+ ```
3412+
3413+ ** Hourly ** 不能编译,因为擦除会将 `Payable<Employe > ` 和 `Payable<Hourly > ` 简化为相同的类 ** Payable ** ,这样,上面的代码就意味着在重复两次地实现相同的接口。十分有趣的是,如果从 ** Payable ** 的两种用法中都移除掉泛型参数(就像编译器在擦除阶段所做的那样)这段代码就可以编译。
3414+
3415+ 在使用某些更基本的 Java 接口,例如 `Comparable<T > ` 时,这个问题可能会变得十分令人恼火,就像你在本节稍后就会看到的那样。
3416+
3417+ ### 转型和警告
3418+
3419+ 使用带有泛型类型参数的转型或 ** instanceof ** 不会有任何效果。下面的容器在内部将各个值存储为 ** Object ** ,并在获取这些值时,再将它们转型回 ** T ** :
3420+
3421+ ```java
3422+ // generics/GenericCast.java
3423+ import java. util. * ;
3424+ import java. util. stream. * ;
3425+
3426+ class FixedSizeStack <T> {
3427+ private final int size;
3428+ private Object [] storage;
3429+ private int index = 0 ;
3430+ FixedSizeStack (int size ) {
3431+ this . size = size;
3432+ storage = new Object [size];
3433+ }
3434+ public void push (T item ) {
3435+ if (index < size)
3436+ storage[index++ ] = item;
3437+ }
3438+ @SuppressWarnings (" unchecked" )
3439+ public T pop () {
3440+ return index == 0 ? null : (T )storage[-- index];
3441+ }
3442+ @SuppressWarnings (" unchecked" )
3443+ Stream<T > stream () {
3444+ return (Stream<T > )Arrays . stream(storage);
3445+ }
3446+ }
3447+
3448+ public class GenericCast {
3449+ static String [] letters =
3450+ " ABCDEFGHIJKLMNOPQRS" . split(" " );
3451+ public static void main (String [] args ) {
3452+ FixedSizeStack<String > strings =
3453+ new FixedSizeStack<> (letters. length);
3454+ Arrays . stream(" ABCDEFGHIJKLMNOPQRS" . split(" " ))
3455+ .forEach(strings:: push);
3456+ System . out. println(strings. pop());
3457+ strings. stream()
3458+ .map(s - > s + " " )
3459+ .forEach(System . out:: print);
3460+ }
3461+ }
3462+ /* Output:
3463+ S
3464+ A B C D E F G H I J K L M N O P Q R S
3465+ */
3466+ ```
3467+
3468+ 如果没有 ** @SuppressWarnings ** 注解,编译器将对 `pop ()` 产生 “unchecked cast” 警告。由于擦除的原因,编译器无法知道这个转型是否是安全的,并且 `pop ()` 方法实际上并没有执行任何转型。
3469+ 这是因为,**T** 被擦除到它的第一个边界,默认情况下是 **Object** ,因此 `pop ()` 实际上只是将 **Object** 转型为 **Object**。
3470+ 有时,泛型没有消除对转型的需要,这就会由编译器产生警告,而这个警告是不恰当的。例如:
3471+
3472+ ```java
3473+ // generics/NeedCasting.java
3474+ import java.io.*;
3475+ import java. util. * ;
3476+
3477+ public class NeedCasting {
3478+ @SuppressWarnings (" unchecked" )
3479+ public void f (String [] args ) throws Exception {
3480+ ObjectInputStream in = new ObjectInputStream (
3481+ new FileInputStream (args[0 ]));
3482+ List<Widget > shapes = (List<Widget > )in. readObject();
3483+ }
3484+ }
3485+ ```
3486+
3487+ 正如你将在附件:对象序列化( Appendix : Object Serialization )中学到的那样,`readObject () `无法知道它正在读取的是什么,因此它返回的是必须转型的对象。但是当注释掉 **@SuppressWarnings** 注解,并编译这个程序时,就会得到下面的警告。
3488+
3489+ ```
3490+ NeedCasting.java uses unchecked or unsafe operations.
3491+ Recompile with -Xlint:unchecked for details.
3492+
3493+ And if you follow the instructions and recompile with -
3494+ Xlint:unchecked :(如果遵循这条指示,使用-Xlint:unchecked来重新编译:)
3495+
3496+ NeedCasting.java:10: warning: [unchecked] unchecked cast
3497+ List<Widget > shapes = (List<Widget > )in.readObject ();
3498+ required: List<Widget >
3499+ found: Object
3500+ 1 warning
3501+ ```
3502+
3503+ 你会被强制要求转型,但是又被告知不应该转型。为了解决这个问题,必须使用在 Java SE5 中引入的新的转型形式,既通过泛型类来转型:
3504+
3505+ ```java
3506+ // generics/ClassCasting.java
3507+ import java. io. * ;
3508+ import java. util. * ;
3509+
3510+ public class ClassCasting {
3511+ @SuppressWarnings (" unchecked" )
3512+ public void f (String [] args ) throws Exception {
3513+ ObjectInputStream in = new ObjectInputStream (
3514+ new FileInputStream (args[0 ]));
3515+ // Won't Compile:
3516+ // List<Widget> lw1 =
3517+ // List<>.class.cast(in.readObject());
3518+ List<Widget > lw2 = List . class. cast(in. readObject());
3519+ }
3520+ }
3521+ ```
3522+
3523+ 但是,不能转型到实际类型( `List<Widget > ` )。也就是说,不能声明:
3524+
3525+ ```
3526+ List<Widget > .class.cast (in .readobject ())
3527+ ```
3528+
3529+ 甚至当你添加一个像下面这样的另一个转型时:
3530+
3531+ ```
3532+ (List<Widget > )List.class.cast (in .readobject ())
3533+ ```
3534+
3535+ 仍旧会得到一个警告。
3536+
3537+ ### 重载
3538+
3539+ 下面的程序是不能编译的,即使编译它是一种合理的尝试:
3540+
3541+ ```java
3542+ // generics/UseList.java
3543+ // {WillNotCompile}
3544+ import java.util.*;
3545+
3546+ public class UseList <W, T> {
3547+ void f (List<T > v ) {}
3548+ void f (List<W > v ) {}
3549+ }
3550+ ```
3551+
33153552< ! -- Self - Bounded Types -- >
33163553
33173554## 自限定的类型
@@ -3325,7 +3562,12 @@ class SelfBounded<T extends SelfBounded<T>> { // ...
33253562```
33263563
33273564这就像两面镜子彼此照向对方所引起的目眩效果一样,是一种无限反射。** SelfBounded ** 类接受泛型参数 ** T ** ,而T 由一个边界类限定,这个边界就是拥有 ** T ** 作为其参数的 ** SelfBounded ** 。
3328- 当你首次看到它时,很难去解析它,它强调的是当 `extends` 关键字用于边界与用来创建子类明显是不同的。
3565+ 当你首次看到它时,很难去解析它,它强调的是当 ** extends** 关键字用于边界与用来创建子类明显是不同的。
3566+
3567+ ### 古怪的循环泛型
3568+
3569+ 为了理解自限定类型的含义,我们从这个惯用法的一个简单版本入手,它没有自限定的边界。
3570+ 不能直接继承一个泛型参数,但是,可以继承在其自己的定义中使用这个泛型参数的类。也就是说,可以声明:
33293571
33303572## 动态类型安全
33313573
0 commit comments