Why does this generic code compile in java 8?

If you declare a type parameter at a method, you are allowing the caller to pick an actual type for it, as long as that actual type will fulfill the constraints. That type doesn’t have to be an actual concrete type, it might be an abstract type, a type variable or an intersection type, in other, more colloquial words, a hypothetical type. So, as said by Mureinik, there could be a type extending String and implementing List. We can’t manually specify an intersection type for the invocation, but we can use a type variable to demonstrate the logic:

public class Main {
    public static <X extends String&List<Integer>> void main(String[] args) {
        String s = Main.<X>newList();
        System.out.println(s);
    }

    private static <T extends List<Integer>> T newList() {
        return (T) new ArrayList<Integer>();
    }
}

Of course, newList() can’t fulfill the expectation of returning such a type, but that’s the problem of the definition (or implementation) of this method. You should get an “unchecked” warning when casting ArrayList to T. The only possible correct implementation would be returning null here, which renders the method quite useless.

The point, to repeat the initial statement, is that the caller of a generic method chooses the actual types for the type parameters. In contrast, when you declare a generic class like with

public class SomeClass<T extends List<Integer>> {
    public  void main(String[] args) {
        String s = newList(); // this doesn't compile anymore
        System.out.println(s);
    }

    private T newList() {
        return (T) new ArrayList<Integer>();
    }
}

the type parameter is part of the contract of the class, so whoever creates an instance will pick the actual types for that instance. The instance method main is part of that class and has to obey that contract. You can’t pick the T you want; the actual type for T has been set and in Java, you usually can’t even find out what T is.

The key point of generic programming is to write code that works independently of what actual types have been chosen for the type parameters.

But note that you can create another, independent instance with whatever type you like and invoke the method, e.g.

public class SomeClass<T extends List<Integer>> {
    public <X extends String&List<Integer>> void main(String[] args) {
        String s = new SomeClass<X>().newList();
        System.out.println(s);
    }

    private T newList() {
        return (T) new ArrayList<Integer>();
    }
}

Here, the creator of the new instance picks the actual types for that instance. As said, that actual type doesn’t need to be a concrete type.

Leave a Comment