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:
m2 is generic, and m1 is inferred to be more specific than m2 for
argument expressions e1, ..., ek by §18.5.4.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).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 anIterable<F> fromIterable
andFunction<? super F...
, so the first generic type ofFunction
needs to be a super class of the generic type of theIterable
- in your main, the type of the Iterable is
F1 == ? extends ASTNode<?>
and the first type of the Function returned byprettyPrint
isF2 == 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
How to Set Icon in a Column of Jtable
Missing Return Statement in a Non-Void Method Compiles
Java Executors: How to Set Task Priority
Is Calling Static Methods via an Object "Bad Form"? Why
How to Round a Double to Two Decimal Places in Java
Mapping a Specific Servlet to Be the Default Servlet in Tomcat
How to Get All Table Names from a Database
Why Does the "Protected" Modifier in Java Allow Access to Other Classes in Same Package
How to Configure Tomcat to Connect with MySQL
How to Change the Arrow Style in a Jcombobox
Java - Reading, Manipulating and Writing Wav Files
Why People Are So Afraid of Using Clone() (On Collection and Jdk Classes)
How to Create Splash Screen with Transparent Background in Javafx
Checked VS Unchecked Exception
Java Regex: Repeating Capturing Groups
How to Avoid Type Safety Warnings with Hibernate Hql Results