What is the point of allowing type witnesses on all method calls?

Say we have two methods like the following:

public static <T> T genericReturn() { /*...*/ }
public static String stringReturn() { /*...*/ }

In calling any method, you can supply the type witness regardless of whether or not there is any requirement:

String s;
s = Internet.<String>genericReturn(); //Type witness used in return type, returns String
s = Internet.<Integer>stringReturn(); //Type witness ignored, returns String

However I’m not seeing any realistic use for this in Java at all, unless the type cannot be inferred (which is usually indicative of a bigger issue). Additionally the fact that it is simply ignored when it is not appropriately used seems counterintuitive. So what’s the point of having this in Java at all?

Answer

From the JLS §15.2.12.1:

  • If the method invocation includes explicit type arguments, and the member is a generic method, then the number of type arguments is equal to the number of type parameters of the method.

This clause implies that a non-generic method may be potentially applicable to an invocation that supplies explicit type arguments. Indeed, it may turn out to be applicable. In such a case, the type arguments will simply be ignored.

It’s followed by justification

This rule stems from issues of compatibility and principles of substitutability. Since interfaces or superclasses may be generified independently of their subtypes, we may override a generic method with a non-generic one. However, the overriding (non-generic) method must be applicable to calls to the generic method, including calls that explicitly pass type arguments. Otherwise the subtype would not be substitutable for its generified supertype.

Along this line of reasoning, let’s construct an example. Suppose in Java 1.4, JDK has a class

public class Foo
{
    /** check obj, and return it */
    public Object check(Object obj){ ... }
}

Some user wrote a proprietary class that extends Foo and overrides the check method

public class MyFoo extends Foo
{
    public Object check(Object obj){ ... }
}

When Java 1.5 introduced generics, Foo.check is generified as

    public <T> T check(T obj)

The ambitious backward comparability goal requires that MyFoo still compiles in Java 1.5 without modification; and MyFoo.check[Object->Object] is still an overriding method of Foo.check[T->T].

Now, according to aforementioned justification, since this compiles:

    MyFoo myFoo = new MyFoo();

    ((Foo)myFoo).<String>check("");

This must compile too:

    myFoo.<String>check("");

even though MyFoo.check is not generic.


That sounds like a stretch. But even if we buy that argument, the solution is still too broad and overreaching. JLS could’ve tighten it up so that myFoo.<String,String>check and obj.<Blah>toString() are illegal, because type parameter arity doesn’t match. They probably didn’t have time to iron it out so they just took a simple route.

Leave a Reply

Your email address will not be published. Required fields are marked *