Creating an array of generic collections

前端 未结 3 543
情话喂你
情话喂你 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条回答
  • 2021-02-07 03:45

    I do know that, relative to the workarounds to this issue, Array.newInstance() is an expensive method to call. IIRC it uses a native method to instantiate the array, amidst the other reflection involved. I can't offer any statistics, but this seems like a good enough reason for such functionality not to be automatically substituted in by the compiler in order to allow generic array creation. Especially given the existence of ArrayList, etc. it just doesn't seem like a pressing issue.

    0 讨论(0)
  • 2021-02-07 03:46

    Actually Java does create generic array for varargs, so you can do

    List<MyDTO>[] dtoLists = array(new ArrayList<MyDTO>(), anExistingDtoList);
    
    @SafeVarargs
    static <E> E[] array(E... array)
    {
        return array;
    }
    

    As to why is explicit generic array creation forbidden, it has something to do with type erasure. (The same concern exists in the above solution, but suppressed by @SafeVarargs) However it is debatable; there are different ways to handle the concern, a compiler warning is probably enough. But they chose to outright ban it, probably because arrays are no longer important anyway now that we have generic collections

    0 讨论(0)
  • 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<MyDTO>[] 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<MyDTO>[] dtoLists = new List[] {
        new ArrayList<MyDTO>(), anExistingDtoList
    };
    

    Or this:

    @SuppressWarnings("unchecked")
    List<MyDTO>[] dtoLists = (List<MyDTO>[]) new List<?>[] {
        new ArrayList<MyDTO>(), 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 <T> 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<Float>());
    

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

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

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

    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.

    0 讨论(0)
提交回复
热议问题