Why Does This Generic Code Compile in Java 8

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.

Why does this generic code in java compile?

It's legal because Integer and String have a common base type. T can be Object, so foo(T, T) will accept any two objects. 32 can be autoboxed to an Integer, which is a kind of Object. "232" is a String, which is a kind of Object.

(As pointed out by comments, for Integer and String, there is a closer common base-type, Serializable; but the principle is the same whichever base type the compiler resolves T to.)

Why does the Java 8 generic type inference pick this overload?

The rules of type inference have received a significant overhaul in Java 8; most notably target type inference has been much improved. So, whereas before Java 8 the method argument site did not receive any inference, defaulting to Object, in Java 8 the most specific applicable type is inferred, in this case String. The JLS for Java 8 introduced a new chapter Chapter 18. Type Inference that's missing in JLS for Java 7.

Earlier versions of JDK 1.8 (up until 1.8.0_25) had a bug related to overloaded methods resolution when the compiler successfully compiled code which according to JLS should have produced ambiguity error Why is this method overloading ambiguous? As Marco13 points out in the comments

This part of the JLS is probably the most complicated one

which explains the bugs in earlier versions of JDK 1.8 and also the compatibility issue that you see.


As shown in the example from the Java Tutoral (Type Inference)

Consider the following method:

void processStringList(List<String> stringList) {
// process stringList
}

Suppose you want to invoke the method processStringList with an empty list. In Java SE 7, the following statement does not compile:

processStringList(Collections.emptyList());

The Java SE 7 compiler generates an error message similar to the following:

List<Object> cannot be converted to List<String>

The compiler requires a value for the type argument T so it starts with the value Object. Consequently, the invocation of Collections.emptyList returns a value of type List, which is incompatible with the method processStringList. Thus, in Java SE 7, you must specify the value of the value of the type argument as follows:

processStringList(Collections.<String>emptyList());

This is no longer necessary in Java SE 8. The notion of what is a target type has been expanded to include method arguments, such as the argument to the method processStringList. In this case, processStringList requires an argument of type List

Collections.emptyList() is a generic method similar to the get() method from the question. In Java 7 the print(String string) method is not even applicable to the method invocation thus it doesn't take part in the overload resolution process. Whereas in Java 8 both methods are applicable.

This incompatibility is worth mentioning in the Compatibility Guide for JDK 8.


You can check out my answer for a similar question related to overloaded methods resolution Method overload ambiguity with Java 8 ternary conditional and unboxed primitives

According to JLS 15.12.2.5 Choosing the Most Specific Method:

If more than one member method is both accessible and applicable to a
method invocation, it is necessary to choose one to provide the
descriptor for the run-time method dispatch. The Java programming
language uses the rule that the most specific method is chosen.

Then:

One applicable method m1 is more specific than another applicable
method m2, for an invocation with argument expressions e1, ..., ek, if
any of the following are true:

  1. m2 is generic, and m1 is inferred to be more specific than m2 for
    argument expressions e1, ..., ek by §18.5.4.

  2. m2 is not generic, and m1 and m2 are applicable by strict or loose
    invocation, and where m1 has formal parameter types S1, ..., Sn and m2
    has formal parameter types T1, ..., Tn, the type Si is more specific
    than Ti for argument ei for all i (1 ≤ i ≤ n, n = k).

  3. m2 is not generic, and m1 and m2 are applicable by variable arity
    invocation, and where the first k variable arity parameter types of m1
    are S1, ..., Sk and the first k variable arity parameter types of m2
    are T1, ..., Tk, the type Si is more specific than Ti for argument ei
    for all i (1 ≤ i ≤ k). Additionally, if m2 has k+1 parameters, then
    the k+1'th variable arity parameter type of m1 is a subtype of the
    k+1'th variable arity parameter type of m2.


The above conditions are the only circumstances under which one method may be more specific than another.

A type S is more specific than a type T for any expression if S <: T (§4.10).

The second of three options matches our case. Since String is a subtype of Object (String <: Object) it is more specific. Thus the method itself is more specific. Following the JLS this method is also strictly more specific and most specific and is chosen by the compiler.

Why the code with such generic bound is compiled

There could be a T that both extends List<Integer> and extends MyClass (for example a class MyListClass extends MyClass implements List<Integer>.

The compiler doesn't look into the createList method to figure out what it returns, it just trusts that it does what the prototype describes.

Since you use an unchecked cast inside the method body, you are allowed to break the type system this way. And that's why you get a warning about that unchecked cast.

Effectively the unchecked cast means you disable the type check at that line and therefore can implement a return type that you don't actually fulfill (you don't actually return a MyListClass as described above, you just claim that you do).

The reason this doesn't work for non-interface types is that the type system doesn't allow a type that is two unrelated non-interface types at the same time (a thing can't be a String and a MyClass at the same type).

Type error using generics under java 8, but not java 7

UPDATE: see other answer - this was a bug in javac which has been fixed.


It feels like this should not compile and that Java 8 is exhibiting the correct behaviour:

  • Iterables.transform expects an Iterable<F> fromIterable and Function<? super F..., so the first generic type of Function needs to be a super class of the generic type of the Iterable
  • in your main, the type of the Iterable is F1 == ? extends ASTNode<?> and the first type of the Function returned by prettyPrint is F2 == T extends ASTNode<?>

I don't think there is a way to prove that F2 is a supertype of F1. For example let's say you have:

class A1 extends ASTNode<A1> {}
class A2 extends ASTNode<A2> {}

and in your main:

List<? extends ASTNode<?>> list = new List<A1>(); //F1 = A1

You could imagine that prettyPrint returns a Function<A2, String> (i.e. F2 = A2) and A2 is not a super class of A1.

So I believe the compile error makes sense. But I won't try to prove it based on the specifications because that would eat most of my day!

Why does this code only pass compilation after adding an unused generic type parameter to the class?

You are trying to pass a List<Long> to a method that expects a List<Integer>. A List<Long> is not a sub-class of List<Integer>, so it's not allowed.

When you make your SomeClass class generic (by adding E), and then instantiate it with the raw type SomeClass, all the generic type parameters of all the methods and their arguments are erased, and the compiler allows you to pass any List to the doSomethink() method.

Note that you'll get the same compilation error in your second snippet if you change it to (i.e. don't use a raw SomeClass type):

class SomeClass<E> {
void doSomethink(List<Integer> params) { }
}

class AnotherClass {
public void method() {
SomeClass<Integer> someClass = new SomeClass<>();
List<Long> list = new ArrayList<>();

someClass.doSomethink(list);
}
}

Why does this code with generics throw a ClassCastException in Java 11?

Indeed it was a bug in Java 8, which was fixed in Java 9 - bugfix.
In some scenarios, the javac CHECKCAST instruction was skipped.

If you are curious, consider those 2 lines of code:

Set<Car> set = new HashSet<>(); // line 11
set.add(getAnimal()); // line 12

Java 8 bytecode will look like this:

 LINENUMBER 11 L1
ALOAD 1
ALOAD 0
INVOKEVIRTUAL UserManagerTest.getAnimal ()LUserManagerTest$Animal;
INVOKEINTERFACE java/util/Set.add (Ljava/lang/Object;)Z (itf)
POP

But Java 9 will look like this:

LINENUMBER 11 L1
ALOAD 1
ALOAD 0
INVOKEVIRTUAL UserManagerTest.getAnimal ()LUserManagerTest$Animal;
CHECKCAST UserManagerTest$Car
INVOKEINTERFACE java/util/Set.add (Ljava/lang/Object;)Z (itf)
POP

The only difference is CHECKCAST instruction, which (according to JavaDoc) states that the named class, array, or interface type is resolved. If object can be cast to the resolved class, array, or interface type, the operand stack is unchanged; otherwise, the checkcast instruction throws a ClassCastException.

Java 8. Generics. Using raw type. Unexpected converting of types

The answer is two-folded. First, generics are erased during compilation. The second part is the actual implementation of ArrayList. Let's start with type erasure.

At compile time, all generic types are replaced with their upper bound. For example a generic parameter <T extends Comparable<T>> collapses to Comparable<T>. If no upper bound is given, it is replaced with Object. This makes generics an efficient tool for type-checking at compile-time, but we loose all type information at runtime. Project Valhalla may or may not fix that in the future. Since your method deals with raw- and unbounded types, the compiler assumes Object as generic type and thus list1.add("12sdf34"); passes type checking.

So why don't you get some exception at runtime? Why does ArrayList not "recognize" that the value you give it is of wrong type? Because ArrayList uses an Object[] as its backing buffer. Next logical question is: Why does ArrayList use an Object[] instead of an T[]? Because of type erasure: we cannot instantiate anything of T or T[] at runtime. Furthermore, the fact that arrays are covariant and retained, while generics are invariant and erased, makes for an explosive product if these two are mixed.

For your program, that means that neither a compilation-error nor a runtime exception will be thrown and thus your ArrayList<Integer> may contain a String. You will, however, get into troubles by writing

...
System.out.println("Contains? " + (new Wildcards()).contains(list1, list2));
System.out.println("List1 element: " + list1.get(0))
int i = list1.get(0);

From the view of the lexer, the code is still valid. But at runtime, the assignment will generate a ClassCastException. This is one of the reasons why raw types should be avoided.

Generic method does not compile when passed a generic argument

I have included a simplified version of the issue. In the following example, the program will not compile because of the method broken.

import java.util.List;
import java.util.ArrayList;

public class Main{
static interface Testing{

<S> List<S> getAList();
<S> List<S> broken(List<String> check);
}

static class Junk implements Testing{
public List<Number> getAList(){
return new ArrayList<>();
}
public List<Number> broken(List<String>check){
return new ArrayList<>();
}
}

public static void main (String[] args) throws java.lang.Exception
{
// your code goes here
}
}

There are two errors and one warning.

  • Main.java:11: error: Junk is not abstract and does not override

    abstract method broken(List) in Testing static class Junk
    implements Testing{
    ^ where S is a type-variable:
    S extends Object declared in method broken(List)

  • Main.java:12: warning: [unchecked] getAList() in Junk implements
    getAList() in Testing public List getAList(){
    ^ return type requires unchecked conversion from List to List where S is a type-variable:
    S extends Object declared in method getAList()

  • Main.java:15: error: name clash: broken(List) in Junk and
    broken(List) in Testing have the same erasure, yet neither
    overrides the other public List broken(Listcheck){
    ^ where S is a type-variable:
    S extends Object declared in method broken(List)

2 errors
1 warning

Why does the first method infer a cast and only give a warning?

"An unchecked conversion is allowed in the definition, despite being unsound, as a special allowance to allow smooth migration from non-generic to generic code."

https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.5

The other method has a Generic in the argument list, therefor it is not a migration from previously non-generic code, and it is unsound and should be considered an error.



Related Topics



Leave a reply



Submit