Effective Java notes (28) lists are better than arrays

        Arrays differ from generics in two important ways. First, arrays are covariant. This word sounds a bit scary, but it just means that if Sub is a subtype of Super, then the array type Sub[ ] is a subtype of Super[ ]. On the contrary, generics are variable (invariant): For any two different types Type1 and Type2, List<Type1> is neither a subtype of List<Type2> nor a supertype of List<Type2>. You might think that this means that generics are flawed, but in fact it can be said that arrays are flawed. The following code snippet is legal:

Object[] objectArray = new Long[1] ;
objectArray[0] = "I don't fit in";

        But the following code is illegal:

List<0bject> o1 = new ArrayList<Long>();
o1.add("I don't fit in");

        No matter which method you use, you cannot put a String into a Long container, but with an array, you will find out that you made a mistake at runtime; with a list, you can find a mistake at compile time. Of course we want to catch errors at compile time.

        The second big difference between arrays and generics is that arrays are reified. Arrays thus know and enforce their element types at runtime. As mentioned above, if you try to store a String in an array of Long, you will get an ArrayStoreException. In contrast, generics are implemented through erasure. This means that generics only enforce their type information at compile time, and discard (or erase) their element type information at runtime. Erasure is about making generics freely interoperable with code that doesn't use them, to ensure a smooth transition to generics in Java 5.

        Because of these fundamental differences, arrays and generics don't play well together. For example, it is illegal to create arrays of generic types, parameterized types, or type parameters3 None of these array creation expressions are legal: new List<E>[], new List<String>[], and new E[]. These will cause a generic array creation (generic array creation) error at compile time.

        Why is it illegal to create a generic array? Because it's not type safe. If it were legal, the cast that the compiler would have made in an otherwise correct program would fail at runtime with a ClassCastException. This violates a basic guarantee provided by the generic system.

        To illustrate this more concretely, consider the following code snippet as an example:

        Let's assume line 1 is legal and it creates a generic array. Line 2 creates and initializes a List<Integer> containing a single element. Line 3 saves the List<String> array into an Object array variable, which is legal because arrays are covariant. Line 4 saves the List<Integer> into the only element in the Object array, which is ok because generics are implemented by erasure: the runtime type of the List<Integer> instance is just List, List<String>[ ] The runtime type of the instance is List[], so this arrangement will not generate an ArrayStoreException. But now we have trouble. We saved a List<Integer> instance into an array that was originally declared to contain only List<String> instances. In line 5, we get the unique element from the unique list in this array. The compiler automatically converts the obtained element into a String, but it is an Integer, so we get a ClassCastException at runtime. To prevent this, line 1 (which creates the generic array) must generate a compile-time error.

        From a technical point of view, types like E, List<E>, and List<String> should be called nonreifiable types. Intuitively, a non-reifiable type is one whose runtime representation contains less information than its compile-time representation. The only reifiable parameterized types are unrestricted wildcard types such as List<? > and Map<?,?>. Although not commonly used, it is legal to create arrays of unbounded wildcard types.

        Disallowing the creation of generic arrays can be a bit of a nuisance. For example, this shows that it is generally not possible for a generic to return its element type array. It also means puzzling warnings when using variadic argument ( varargs ) methods with generics. This is due to the fact that an array is created to hold the varargs parameters whenever a variadic method is called. If the element type of this array is not reifiable ( reifialbe ), you will get a warning. This problem can be solved by using SafeVarargs annotation.

        When you get generic array creation errors, the best solution is usually to use the collection type List<E> in preference to the array type E[] . This may lose some performance or simplicity, but in exchange for higher type safety and interoperability.

        For example, suppose you want to write a Chooser class that takes a collection through its constructor and a method that returns an element randomly chosen from the collection. Depending on the type of collection passed to the constructor, chooser can be used as dice for a game, Magic 8 Ball (a card game), or a data source for a Monte Carlo simulation. Here's a simple implementation without using generics:

public class Chooser
{
    private final Object[] choiceArray;

    public Chooser(Collection choices) {
        choiceArray = choices.toArray();
    }

    public object choose() {
        Random rnd = ThreadLocalRandom.current();
        return choiceArray[rnd.nextInt(choiceArray.length)];
    }
}

        To use this class, the return value of the choose method must be converted from Object to the desired type each time the method is called. If you get the wrong type, the conversion will fail at runtime. Try to modify the Chooser into a generic type, the modified part is as follows:

public class Chooser<T>
{
    private final T[] choiceArray;

    public Chooser(Collection<T> choices) {
        choiceArray = choices.toArray();
    }
    // choose method unchanged
}

        If you try to compile this class, you will get the following error message:

        You might say: it's no big deal, I can convert the Object array to T array: 

choiceArray = (T[]) choices.toArray() ;

        Doing this did get rid of the error message, but now I get a warning:

        The compiler tells you that it can't check the safety of the conversion at runtime because the program doesn't know what T is yet at runtime -- remember, element type information is erased from generics at runtime. Can this program work? You can, but the compiler can't prove it. You can prove it yourself, just put the evidence in a comment, with a comment to suppress the warning, but it's better to eliminate the source of the warning.

        To get rid of the unchecked conversion warning, you must choose to use a list instead of an array. Here is a version of the Chooser class that compiles without errors or warnings:

public class Chooser<T> {
    private final List<T> choiceList;
    public Chooser(Collection<T> choices) {
        choiceList = new ArrayList<> (choices);
    }
    public T choose() {
        Random rnd = ThreadLocalRandom.current();
        return choiceList.get(rnd.nextInt(choiceList.size());
    }
}

        This version is a little more verbose and probably a little slower to run, but it's worth it for not getting a ClassCastException at runtime.

        All in all, arrays and generics have very different type rules. Arrays are covariant and reifiable; generics are immutable and erasable. Thus, arrays provide run-time type safety, but not compile-time type safety, and vice versa for generics. In general, arrays and generics don't mix well. If you find yourself mixing them and getting compile-time errors or warnings, your first instinct should be to use lists instead of arrays.

Guess you like

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