Effective Java notes (29) Prioritize generics

        In general, it's not too difficult to parameterize collection declarations and use the generic methods provided by the JDK. Writing your own generics is a bit more difficult, but it's worth the time to learn how.

        Take a simple (toy) stacking implementation as an example:

// 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(0bject 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 be parameterized first, but it is not, and we can genericize it later. In other words, it can be parameterized without breaking the original non-parameterized version of the client code. That is, the client must transform objects popped from the heap, and those transformations may fail at runtime. The first step in making a class generic is to add one or more type parameters to its declaration. In this example, there is a type parameter, which represents the element type of the heap, and the name of this parameter is usually E.

        The next step is to replace all Object types with the corresponding type parameters, and then try to compile the final program:

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
}

        Usually, you will get at least one error or warning, and this class is no exception. Fortunately, this class produces only one error, which reads:

        You cannot create arrays of non-reifiable types, such as E . This problem arises whenever writing generics backed by arrays. There are two ways to solve this problem. The first, directly bypasses the ban on creating generic arrays: Create an array of Objects and convert it to the generic array type. Now the error is gone, but the compiler will generate a warning. This usage is legal, but (on the whole) not type-safe:

        It's impossible for the compiler to prove that your program is type-safe, but you can. You yourself must ensure that unchecked conversions do not compromise the type safety of your program. The associated array (the elements variable) is kept in a private field and is never returned to the client, or passed to any other method. The only elements held in this array are those passed to the push method, and they are of type E, so unchecked conversions do no harm. 

        Once you've proven that unchecked conversions are safe, suppress warnings in as small a scope as possible. In this case, the constructor contains only unchecked array creation, so this warning can be suppressed throughout the constructor. By adding an annotation @SuppressWarnings to complete the prohibition, Stack can compile correctly, and you can use it without explicit conversion or worry about ClassCastException:

@SuppressWarnings ("unchecked")
public Stack() {
    elements = (E[]) new Object [DEFAULT_INITIAL_CAPACITY];
}

         The second way to eliminate generic array creation errors in Stack is to change the type of the elements field from E[] to Object[]. Doing so will result in a different error:

         This error can be turned into a warning by converting the elements retrieved from the array from Object to E:

        Since E is a non-reifiable type, the compiler cannot verify the conversion at runtime. You can still verify for yourself that unchecked conversions are safe, so you can suppress the warning. Instead of suppressing the entire pop method, we can simply suppress the warning on tasks containing unchecked transitions, as follows:

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;
}

        These two methods of eliminating the creation of generic arrays have their own strengths. The first approach is more readable: the array declared as type E[] clearly indicates that it contains only E instances. It's also more concise: in a typical generic class, the array can be read in multiple places in the code; the first method requires only one conversion (when creating the array), while the second method is A conversion is required each time an array element is read. Therefore, the first method takes precedence and is more commonly used in practice. However, it can cause heap pollution (heap pollution), see Item 32: The run-time type of an array does not match its compile-time type (unless E happens to be Object). This makes some programmers feel uncomfortable and choose the second option, although heap pollution is not harmful in this case.

        The following program demonstrates the use of the generic Stack class. The program prints its command-line arguments in reverse order, converted to uppercase. If you want to call String's toUpperCase method on an element popped from the heap, no explicit conversion is required, and the auto-generated conversion is guaranteed to succeed:

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();
}

        It appears that the above example contradicts Item 28, which encourages the use of lists in preference to arrays. It's not really possible to always or always want to use lists in generics. Java does not inherently support lists, so some generics, such as ArrayList, must be implemented on arrays. To improve performance, other generics such as HashMap are also implemented on arrays.

        All in all, using generics is safer and easier than using types that require conversion in client code. When designing new types, make sure they can be used without this conversion. This usually means making the class generic. As time permits, make existing types generic. This will make life easier for these types of new users without breaking existing clients.

Guess you like

Origin blog.csdn.net/java_faep/article/details/132160549