Skip to content

Latest commit

 

History

History
202 lines (148 loc) · 15.7 KB

Chapter-5-Item-29-Favor-generic-types.md

File metadata and controls

202 lines (148 loc) · 15.7 KB

Chapter 5. Generics(泛型)

Item 29: Favor generic types(优先使用泛型)

It is generally not too difficult to parameterize your declarations and make use of the generic types and methods provided by the JDK. Writing your own generic types is a bit more difficult, but it’s worth the effort to learn how.

通常,对声明进行参数化并使用 JDK 提供的泛型和方法并不太难。编写自己的泛型有点困难,但是值得努力学习。

Consider the simple (toy) stack implementation from Item 7:

考虑 Item-7 中简单的堆栈实现:

// Object-based collection - a prime candidate for generics
public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public Object pop() {
        if (size == 0)
            throw new EmptyStackException();
        Object result = elements[--size];
        elements[size] = null; // Eliminate obsolete reference
        return result;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }
}

This class should have been parameterized to begin with, but since it wasn’t, we can generify it after the fact. In other words, we can parameterize it without harming clients of the original non-parameterized version. As it stands, the client has to cast objects that are popped off the stack, and those casts might fail at runtime. The first step in generifying a class is to add one or more type parameters to its declaration. In this case there is one type parameter, representing the element type of the stack, and the conventional name for this type parameter is E (Item 68).

这个类一开始就应该是参数化的,但是因为它不是参数化的,所以我们可以在事后对它进行泛化。换句话说,我们可以对它进行参数化,而不会损害原始非参数化版本的客户端。按照目前的情况,客户端必须转换从堆栈中弹出的对象,而这些转换可能在运行时失败。生成类的第一步是向其声明中添加一个或多个类型参数。在这种情况下,有一个类型参数,表示堆栈的元素类型,这个类型参数的常规名称是 E(Item-68)。

The next step is to replace all the uses of the type Object with the appropriate type parameter and then try to compile the resulting program:

下一步是用适当的类型参数替换所有的 Object 类型,然后尝试编译修改后的程序:

// Initial attempt to generify Stack - won't compile!
public class Stack<E> {
    private E[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        elements = new E[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(E e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public E pop() {
        if (size == 0)
            throw new EmptyStackException();
        E result = elements[--size];
        elements[size] = null; // Eliminate obsolete reference
        return result;
    } ... // no changes in isEmpty or ensureCapacity
}

You’ll generally get at least one error or warning, and this class is no exception. Luckily, this class generates only one error:

通常至少会得到一个错误或警告,这个类也不例外。幸运的是,这个类只生成一个错误:

Stack.java:8: generic array creation
elements = new E[DEFAULT_INITIAL_CAPACITY];
^

As explained in Item 28, you can’t create an array of a non-reifiable type, such as E. This problem arises every time you write a generic type that is backed by an array. There are two reasonable ways to solve it. The first solution directly circumvents the prohibition on generic array creation: create an array of Object and cast it to the generic array type. Now in place of an error, the compiler will emit a warning. This usage is legal, but it’s not (in general) typesafe:

正如 Item-28 中所解释的,你不能创建非具体化类型的数组,例如 E。每当你编写由数组支持的泛型时,就会出现这个问题。有两种合理的方法来解决它。第一个解决方案直接绕过了创建泛型数组的禁令:创建对象数组并将其强制转换为泛型数组类型。现在,编译器将发出一个警告来代替错误。这种用法是合法的,但(一般而言)它不是类型安全的:

Stack.java:8: warning: [unchecked] unchecked cast
found: Object[], required: E[]
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
^

The compiler may not be able to prove that your program is typesafe, but you can. You must convince yourself that the unchecked cast will not compromise the type safety of the program. The array in question (elements) is stored in a private field and never returned to the client or passed to any other method. The only elements stored in the array are those passed to the push method, which are of type E, so the unchecked cast can do no harm.

编译器可能无法证明你的程序是类型安全的,但你可以。你必须说服自己,unchecked 的转换不会损害程序的类型安全性。所涉及的数组(元素)存储在私有字段中,从未返回给客户端或传递给任何其他方法。数组中存储的惟一元素是传递给 push 方法的元素,它们属于 E 类型,因此 unchecked 的转换不会造成任何损害。

Once you’ve proved that an unchecked cast is safe, suppress the warning in as narrow a scope as possible (Item 27). In this case, the constructor contains only the unchecked array creation, so it’s appropriate to suppress the warning in the entire constructor. With the addition of an annotation to do this, Stack compiles cleanly, and you can use it without explicit casts or fear of a ClassCastException:

一旦你证明了 unchecked 的转换是安全的,就将警告限制在尽可能小的范围内(Item-27)。在这种情况下,构造函数只包含 unchecked 的数组创建,因此在整个构造函数中取消警告是合适的。通过添加注解来实现这一点,Stack 可以干净地编译,而且你可以使用它而无需显式强制转换或担心 ClassCastException:

// The elements array will contain only E instances from push(E).
// This is sufficient to ensure type safety, but the runtime
// type of the array won't be E[]; it will always be Object[]!
@SuppressWarnings("unchecked")
public Stack() {
    elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}

The second way to eliminate the generic array creation error in Stack is to change the type of the field elements from E[] to Object[]. If you do this, you’ll get a different error:

消除 Stack 中泛型数组创建错误的第二种方法是将字段元素的类型从 E[] 更改为 Object[]。如果你这样做,你会得到一个不同的错误:

Stack.java:19: incompatible types
found: Object, required: E
E result = elements[--size];
^

You can change this error into a warning by casting the element retrieved from the array to E, but you will get a warning:

通过将从数组中检索到的元素转换为 E,可以将此错误转换为警告,但你将得到警告:

Stack.java:19: warning: [unchecked] unchecked cast
found: Object, required: E
E result = (E) elements[--size];
^

Because E is a non-reifiable type, there’s no way the compiler can check the cast at runtime. Again, you can easily prove to yourself that the unchecked cast is safe, so it’s appropriate to suppress the warning. In line with the advice of Item 27, we suppress the warning only on the assignment that contains the unchecked cast, not on the entire pop method:

因为 E 是不可具体化的类型,编译器无法在运行时检查强制转换。同样,你可以很容易地向自己证明 unchecked 的强制转换是安全的,因此可以适当地抑制警告。根据 Item-27 的建议,我们仅对包含 unchecked 强制转换的赋值禁用警告,而不是对整个 pop 方法禁用警告:

// Appropriate suppression of unchecked warning
public E pop() {
    if (size == 0)
        throw new EmptyStackException();
    // push requires elements to be of type E, so cast is correct
    @SuppressWarnings("unchecked")
    E result =(E) elements[--size];
    elements[size] = null; // Eliminate obsolete reference
    return result;
}

Both techniques for eliminating the generic array creation have their adherents. The first is more readable: the array is declared to be of type E[], clearly indicating that it contains only E instances. It is also more concise: in a typical generic class, you read from the array at many points in the code; the first technique requires only a single cast (where the array is created), while the second requires a separate cast each time an array element is read. Thus, the first technique is preferable and more commonly used in practice. It does, however, cause heap pollution (Item 32): the runtime type of the array does not match its compile-time type (unless E happens to be Object). This makes some programmers sufficiently queasy that they opt for the second technique, though the heap pollution is harmless in this situation.

消除泛型数组创建的两种技术都有其追随者。第一个更容易读:数组声明为 E[] 类型,这清楚地表明它只包含 E 的实例。它也更简洁:在一个典型的泛型类中,从数组中读取代码中的许多点;第一种技术只需要一次转换(在创建数组的地方),而第二种技术在每次读取数组元素时都需要单独的转换。因此,第一种技术是可取的,在实践中更常用。但是,它确实会造成堆污染(Item-32):数组的运行时类型与其编译时类型不匹配(除非 E 恰好是 Object)。尽管堆污染在这种情况下是无害的,但这使得一些程序员感到非常不安,因此他们选择了第二种技术。

The following program demonstrates the use of our generic Stack class. The program prints its command line arguments in reverse order and converted to uppercase. No explicit cast is necessary to invoke String’s toUpperCase method on the elements popped from the stack, and the automatically generated cast is guaranteed to succeed:

下面的程序演示了通用 Stack 的使用。程序以相反的顺序打印它的命令行参数并转换为大写。在从堆栈弹出的元素上调用 String 的 toUpperCase 方法不需要显式转换,自动生成的转换保证成功:

// Little program to exercise our generic Stack
public static void main(String[] args) {
    Stack<String> stack = new Stack<>();
    for (String arg : args)
        stack.push(arg);
    while (!stack.isEmpty())
        System.out.println(stack.pop().toUpperCase());
}

The foregoing example may appear to contradict Item 28, which encourages the use of lists in preference to arrays. It is not always possible or desirable to use lists inside your generic types. Java doesn’t support lists natively, so some generic types, such as ArrayList, must be implemented atop arrays. Other generic types, such as HashMap, are implemented atop arrays for performance. The great majority of generic types are like our Stack example in that their type parameters have no restrictions: you can create a Stack, Stack<int[]>, Stack<List>, or Stack of any other object reference type. Note that you can’t create a Stack of a primitive type: trying to create a Stack or Stack will result in a compile-time error.

前面的例子可能与 Item-28 相矛盾,Item-28 鼓励优先使用列表而不是数组。在泛型中使用列表并不总是可能的或可取的。Java 本身不支持列表,因此一些泛型(如 ArrayList)必须在数组之上实现。其他泛型(如 HashMap)是在数组之上实现的,以提高性能。大多数泛型与我们的 Stack 示例相似,因为它们的类型参数没有限制:你可以创建 Stack<Object>、Stack<int[]>、Stack<List> 或任何其他对象引用类型的堆栈。注意,不能创建基本类型的 Stack:试图创建 Stack<int>Stack<double> 将导致编译时错误。

This is a fundamental limitation of Java’s generic type system. You can work around this restriction by using boxed primitive types (Item 61). There are some generic types that restrict the permissible values of their type parameters. For example, consider java.util.concurrent.DelayQueue, whose declaration looks like this:

这是 Java 泛型系统的一个基本限制。你可以通过使用装箱的基本类型(Item-61)来绕过这一限制。有一些泛型限制了其类型参数的允许值。例如,考虑 java.util.concurrent.DelayQueue,其声明如下:

class DelayQueue<E extends Delayed> implements BlockingQueue<E>

The type parameter list () requires that the actual type parameter E be a subtype of java.util.concurrent.Delayed. This allows the DelayQueue implementation and its clients to take advantage of Delayed methods on the elements of a DelayQueue, without the need for explicit casting or the risk of a ClassCastException. The type parameter E is known as a bounded type parameter. Note that the subtype relation is defined so that every type is a subtype of itself [JLS, 4.10], so it is legal to create a DelayQueue.

类型参数列表()要求实际的类型参数 E 是 java.util.concurrent.Delayed 的一个子类型。这允许 DelayQueue 实现及其客户端利用 DelayQueue 元素上的 Delayed 方法,而不需要显式转换或 ClassCastException 的风险。类型参数 E 称为有界类型参数。注意,子类型关系的定义使得每个类型都是它自己的子类型 [JLS, 4.10],所以创建 DelayQueue<Delayed> 是合法的。

In summary, generic types are safer and easier to use than types that require casts in client code. When you design new types, make sure that they can be used without such casts. This will often mean making the types generic. If you have any existing types that should be generic but aren’t, generify them. This will make life easier for new users of these types without breaking existing clients (Item 26).

总之,泛型比需要在客户端代码中转换的类型更安全、更容易使用。在设计新类型时,请确保可以在不使用此类类型转换的情况下使用它们。这通常意味着使类型具有通用性。如果你有任何应该是泛型但不是泛型的现有类型,请对它们进行泛型。这将使这些类型的新用户在不破坏现有客户端(Item-26)的情况下更容易使用。


Back to contents of the chapter(返回章节目录)