javac#genericscasting

Useless expectation from compiler when dealing with generics?


A compiler that must translate a generic type or method (in any language, not just Java) has in principle two choices:

Code specialization. The compiler generates a new representation for every instantiation of a generic type or method. For instance, the compiler would generate code for a list of integers and additional, different code for a list of strings, a list of dates, a list of buffers, and so on.

Code sharing. The compiler generates code for only one representation of a generic type or method and maps all the instantiations of the generic type or method to the unique representation, performing type checks and type conversions where needed.

Java uses code sharing method. I believe C# follows the code specialization method, so all the code below is logical according to me using C#.

Assuming this Java code snippet:

public class Test {

    public static void main(String[] args) {
        Test t = new Test();
        String[] newArray = t.toArray(new String[4]);
    }

    @SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        //5 as static size for the sample...
        return (T[]) Arrays.copyOf(a, 5, a.getClass());
    }
}

Code sharing method will lead to this code after type erasure occurs:

public class Test {

    public static void main(String[] args) {
       Test t = new Test();
       //Notice the cast added by the compiler here
       String[] newArray = (String[])t.toArray(new String[4]);
    }

    @SuppressWarnings("unchecked")
    public Object[] toArray(Object[] a) {
       //5 as static size for the sample...
       return Arrays.copyOf(a, 5, a.getClass());
    }
}

So my question is:

What is the need to precise this initial cast? :

(T[]) Arrays.copyOf(a, 5, a.getClass());

instead of doing simply (before type erasure, at coding time):

Arrays.copyOf(a, 5, a.getClass());

Is this cast really necessary for the compiler?

Ok, Arrays.copyOf returns Object[] and are not directly referenceable by a more specific type without explicit downcast.

But can't the compiler make an effort in this case since it deals with a generic type (the return type!)?

Indeed, isn't it enough that compilers apply an explicit cast to the method's caller line? :

(String[])t.toArray(new String[4]);

UPDATED---------------------------------------------------------------------

Thanks to @ruakh for his answer.

Here a sample that proves that explicit cast even just present at compile-time is relevant:

public static void main(String[] args) {
   Test t = new Test();
   String[] newArray = t.toArray(new String[4]);
}


public <T> T[] toArray(T[] a) {
   return (T[]) Arrays.copyOf(a, 5, Object[].class);
}

Casting to T[] is the only way to put some warning to user signaling the cast may not relevant. And indeed, here we end up with a downcast of Object[] to String[], which leads to a ClassCastException at runtime.

So, to the point saying "isn't it enough that compilers apply an explicit cast to the method's caller line", the answer is:

Developer doesn't master this casting since it is created automatically at compilation step, so this runtime feature doesn't warn the user to check deeply his code for its safety BEFORE launching compilation.

To put it a nutshell, this cast is worth to be present.


Solution

  • There are two problems with your line of reasoning.

    One problem is that explicit casts are both a compile-time feature (part of the static type system) and a run-time feature (part of the dynamic type system). At compile-time, they convert an expression of one static type to an expression of another static type. At run-time, they ensure type-safety, by enforcing the requirement that the dynamic type is actually a subtype of that static type. In your example, of course, the run-time feature is skipped, because erasure means that there's not enough information to enforce the cast at run-time. But the compile-time feature is still relevant.

    Consider this method:

    private void printInt(Number n)
    {
        Integer i = (Integer) n;
        System.out.println(i + 10);
    }
    

    Do you think that the following should be valid:

    Object o = 47;
    printInt(o);            // note: no cast to Number
    

    on the grounds that foo will immediately cast its argument to Integer anyway, so there's no need to require that callers cast it to Number?

    The other problem with your line of reasoning is that, although erasure and unchecked casts do sacrifice a bit of type-safety, the compiler compensates for this sacrifice by issuing warnings. If you write a Java program that does not give any unchecked (or raw-type) warnings, then you can be sure that it won't throw any ClassCastExceptions due to implicit, run-time-only, compiler-generated downcasts. (I mean, unless you're suppressing such warnings, of course.) In your example, you have a method that claims to be generic, and that claims that its return-type is the same as its parameter-type. By providing an explicit cast to T[], you're giving the compiler the opportunity to issue a warning and tell you that it cannot enforce that claim at that location. Without such a cast, there would be no place to warn about the potential resultant ClassCastException in the calling method.