Creating an array of generic collections

前端 未结 3 540
情话喂你
情话喂你 2021-02-07 03:20

Actually, the question should be

Creating an array of generic anything.

Why can\'t the compiler take care of it?

The following would be

3条回答
  •  慢半拍i
    慢半拍i (楼主)
    2021-02-07 03:50

    Compilers can spontaneously perform the conversion, they are just specified not to because generic arrays can't behave like non-generic arrays.

    See 10.5. Array Store Exception:

    For an array whose type is A[], where A is a reference type, an assignment to a component of the array is checked at run time to ensure that the value being assigned is assignable to the component.

    If the type of the value being assigned is not assignment-compatible with the component type, an ArrayStoreException is thrown.

    If the component type of an array were not reifiable, the Java Virtual Machine could not perform the store check described in the preceding paragraph. This is why an array creation expression with a non-reifiable element type is forbidden.

    A List[] would not throw if we put some other kind of List in it, so it doesn't behave as an array. Note the last sentence from the quote: "This is why an array creation expression with a non-reifiable element type is forbidden." This is the reason, it's specified to be so. (And, for the record, this reasoning has always existed, so it was present when the question was posted in 2011.)

    We can still do this:

    @SuppressWarnings({"unchecked","rawtypes"})
    List[] dtoLists = new List[] {
        new ArrayList(), anExistingDtoList
    };
    

    Or this:

    @SuppressWarnings("unchecked")
    List[] dtoLists = (List[]) new List[] {
        new ArrayList(), anExistingDtoList
    };
    

    (Besides statically checking the argument types, the varargs thing is equivalent: it creates a List[] and suppresses warnings.)

    Now, sure, the specification could be changed to something like "If the type of the value being assigned is not assignment-compatible with the raw type of the component type...", but what is the point? It would save a handful of characters in some unusual situations but otherwise suppress warnings for those who don't understand the implications.

    Furthermore, what the tutorial and other typical explanations I've seen don't demonstrate is just how baked in to the type system covariant arrays are.

    For example, given the following declaration:

    // (declaring our own because Arrays.fill is defined as
    // void fill(Object[], Object)
    // so the next examples would more obviously pass)
    static  void fill(T[] arr, T elem) {
        Arrays.fill(arr, elem);
    }
    

    Did you know that this compiles?

    // throws ArrayStoreException
    fill(new String[1], new Integer(0));
    

    And this compiles too:

    // doesn't throw ArrayStoreException
    fill(dtoLists, new ArrayList());
    

    Before Java 8, we could make those calls to fill fail by giving it the following declaration:

    static  void fill(T[] arr, U elem) {...}
    

    But that was only a problem with type inference, and now it works "correctly", blindly putting List in to a List[].

    This is called heap pollution. It can cause a ClassCastException to be thrown sometime later, likely somewhere completely unrelated to the actions that actually caused the problem. Heap pollution with a generic container like List requires more obvious unsafe actions, like using raw types, but here, we can cause heap pollution implicitly and without any warnings.

    Generic arrays (and really, arrays in general) only give us static checking in the simplest of circumstances.

    So it's evident that the language designers thought it was better to just not allow them, and programmers who understand the problems they present can suppress warnings and bypass the restriction.

提交回复
热议问题