All,
To test the Java Generics Type Inference I created below sample code.
public static <U> U addBox2(U u1, U u2) {
return u2;
}
static interface A {}
static interface B {}
static class S {}
static class C extends S implements A,B {}
static class D extends S implements B,A {}
If you see above A and S have no relationship.
In the code below -
A a = addBox2(new C(),new D());
I was hoping to receive the compilation error since the type inferred was S and I am assigning it to A and A and S has no relationship still this worked just fine.
Can someone help me explain why this behavior?
As requested:
A and S don't have a relationship indeed but since you pass a C and D to the method and both implement A (and B) they have something in common, i.e. they both are As.
That means the following would work:
A a = addBox2(new C(),new D()); //common type is A
B b = addBox2(new C(),new D()); //common type is B
S s = addBox2(new C(),new D()); //common type is S
Object o = addBox2(new C(),new D()); //common type is Object
As long as type inference can solve the generic type from the assignment as well as the parameters it should work (note that type inference isn't as good in Java versions prior to 8, especially in 5 and 6).
You can, however, define the type yourself by passing it to the method call:
A a = EnclosingClass.<S>addBox2(new C(),new D()); //static method within EnclosingClass
A a = this.<S>addBox2(new C(),new D()); //instance method
In both cases you define the generic type to be S and thus the assignment won't work.
Type parameter U is inferred from the type of the a variable you are assigning the result of addBox2(...) to, which in your case is A. As both C and D implement A it works flawlessly.
The following of course won't work:
S s = addBox2(new C(),new D());
A a = s; // compile error, type mismatch
I suspect Eclipse and IntelliJ aren't annotating these properly. With this (slightly tweaked) example:
public static <U> U getU(U u1, U u2) {
return u2;
}
static interface A {}
static class S {}
static class C extends S implements A {}
static class D extends S implements A {}
static class T implements A {}
public static void main(String[] args) {
A a = getU(new C(), new D());
A b = getU(new C(), new T());
}
I see the following when I mouse-over the first call to getU() in main():
<? extends S> ? extends S analysis.Delme.getU(? extends S u1, ? extends S u2)
Which is clearly not correct - A does not extend S. I suspect Eclipse is mistakenly oversimplifying the generics here, and preferring to report that C and D both extend from S, rather than both implement A.
If I mouse-over the second call I instead get the much more reasonable:
<A> A analysis.Delme.getU(A u1, A u2)
Which is presumably what the compiler is actually doing here for both calls.
Note as Thomas points out, even if two classes have nothing in common (such as S and T) they still extend from Object, so Object c = getU(new S(), new T()); is valid.
Don’t trust the tooltip of an editor that might not cope with complex generic constructs. The inferred type is S & A & B (before Java 8), which can be assigned to A. In Java 8, the inferred type is just A, as generic method invocations are so-called poly expressions using the target type. For your addBox invocation, it makes no difference.
To illustrate the issue, you may write
Object o = Arrays.asList(new C(), new D());
in Eclipse using Java 7 compliance mode, and hover the mouse over asList. It will print
<? extends S> List<? extends S> java.util.Arrays.asList(? extends S... a), similar to the addBox invocation of your question, but when you change the code to
List<A> list = Arrays.asList(new C(), new D());
you get the compiler error “Type mismatch: cannot convert from List<S&A&B> to List<A>” (still in Java 7 mode), showing that the actual inferred type is S & A & B, not ? extends S.
When you turn the language compliance level to Java 8, the compiler error will go away, as in Java 8, the target type will be used to infer the type, which will be A instead of S & A & B.
As said, for your addBox invocation, it makes no difference whether A or S & A & B is inferred, as both can be assigned to A, so it works under both, Java 7 and Java 8 compliance level. But when you use an invocation, where it makes a difference, like with Arrays.asList, you can see the actually inferred type, which is never ? extends S as the editor’s tooltip claims…
Related
I understand how ? extends .. and ? super .. work on their own and which generic types are possible, but I just can't understand how the following is possible with this hierarchy:
-> means extends
Classes are X (lowest), A to E (highest)
Interface is F
X -> A (implements F) -> B -> C -> E (implements F)
also D -> E
public class Node<T extends ClassE> {
private T info;
public T getInfo() {
return info;
}
public void setInfo(T info) {
this.info = info;
}
}
public static void main (String [] args){
Node<? super ClassB> n2 = new Node<ClassC>();
// this makes sense, since Node accepts below E and above B
InterfaceF i2 = n2.getInfo();
// how? Not only outside of <? extends E> but also getting value even though
// <? super B> is defined above, what's up with PECS?
n2.setInfo(new ClassX());
// also.. how? I'm setting a class that's out of the allowed range +
// seemingly violating the PECS for <? extends E>
}
As you can see, I'm totally confused when it comes to combining them and it's quite surprising for me having those declarations pass the compiler without problems.
I read somewhere that a combination of both bounds isn't possible in Java, but how does that work then?
The first line InterfaceF i2 = n2.getInfo(); compiles because the lower-bounded wildcard still retains the bound on the type variable itself. Since the type variable has an upper bound ClassE, getInfo() still returns a ClassE. Since ClassE implements InterfaceF, the assignment compiles.
In other words, we could imagine that when you did Node<? super ClassB> you implicitly actually did something like (made-up syntax) Node<? extends ClassE & super ClassB>. The type argument to the Node is both a supertype of ClassB and a subtype of ClassE.
This is similar to how Node<?> is implicitly the same as Node<? extends ClassE>.
The way this is actually specified is a little bit complicated, but it's in capture conversion. Capture conversion is the process whereby the compiler takes a type with wildcards and treats it as if it was a type without wildcards, for the purpose of determining subtyping.
Let G name a generic type declaration with n type parameters A1,...,An with corresponding bounds U1,...,Un.
There exists a capture conversion from a parameterized type G<T1,...,Tn> to a parameterized type G<S1,...,Sn, where, for 1 ≤ i ≤ n :
[...]
If Ti is a wildcard type argument of the form ? super Bi, then Si is a fresh type variable whose upper bound is Ui[A1:=S1,...,An:=Sn] and whose lower bound is Bi.
In other words, Si (the type argument after capture conversion corresponding to ? super ClassB) gains its lower bound from the bound of the wildcard and its upper bound from the bound of the type variable declaration.
The second line n2.setInfo(new ClassX()); compiles because ClassX is a subclass of ClassB so it's implicitly convertible to it. We could imagine that n2 was a Node<ClassB> and it might be more obvious why this line compiles:
Node<ClassB> n2 = ...;
n2.setInfo(new ClassX());
setInfo accepts ClassB as well as any subtype of ClassB.
Also, with respect to this:
I read somewhere that a combination of both bounds isn't possible in Java, but how does that work then?
The type system in the compiler does a lot of things that we can't explicitly do ourselves. Another good example (although unrelated) is type inference with anonymous classes:
int num = Objects.requireNonNull(new Object() {int num = 42;}).num;
System.out.println(num); // 42
It compiles because type inference is allowed to infer that the type argument to T of requireNonNull is the anonymous object type, even though we could never provide that type as an explicit type argument ourselves.
Disclaimer: I do not know the concrete type inference rules but explain to my best understanding.
Regarding InterfaceF i2 = n2.getInfo() - since Node<T extends E> it is assured that Node.getInfo() returns something extends E. According to your description E implements F. As such it is ensured that T extends E will also implement F. Therefore Node.getInfo() = T extends E implements F. So n2.getInfo() implements F is fine.
Regarding n2.setInfo(new ClassX()) - I do not have a formal-like explanation for this as above but let's try to think about it: Basically you Node<? super ClassB> tells everyone to expect at most ClassB as its lowest for the Node's content. However, since ClassX transitively inherits ClassB it is perfectly valid since it will satisfy all interface guarantees posed by ? super ClassB.
Hope this helps!
public class Test {
public interface F {}
public static class E implements F {}
public static class D extends E {}
public static class C extends E {}
public static class B extends C {}
public static class A extends B implements F {}
public static class X extends A {}
public static class Node<T extends E> {
private T info;
public T getInfo() {return info;}
public void setInfo(T info) {this.info = info;}
}
public static void main(String[] args) {
Node<? super B> n = new Node<C>(); // C is superclass of B = OK
F i = n.getInfo(); // node type = B|C|E all these types implements F (since E implements F) = OK
n.setInfo(new X()); // X has supertypes A,B,C,E = can be casted to B and so satisfy <? super B>
}
}
I have read that the entire point of Generics is that the add stability to our code by making more of our bugs (essentially that ones which occur when a variable is assigned a value whose type is not compatible with the type of the variable) detectable at compile time.
Following is a non generic class in which I get a RunTimeException, ClassCastException occures, at the statement B bForStoringReturnedAOne = (B) box.aMethod(c);. I was expecting that if I make this code Generic, this ClassCastException will not occur, as the use of Generics will somehow make the bug causing the exception, DETECTABLE AT COMPILE TIME.
So I posted a generic version of this code as well. The problem is that no bug is detected at COMPILE TIME, and I get the same ClassCastException at the same statement. So the question is that what is the difference? What have generics helped with? What is the point of existence of generics? Even after using generics, the bug/exception is still not detected at compile time.
NON-generic version:
public class SomeClass {
private class A {}
private class B extends A {}
private class C extends A {}
private class Box {
private A aMethod(A a) {
return a;
}
}
public static void main(String[] args) {
SomeClass someClass = new SomeClass();
B b = someClass.new B();
C c = someClass.new C();
Box box = someClass.new Box();
B bForStoringReturnedA = (B) box.aMethod(b);
B bForStoringReturnedAOne = (B) box.aMethod(c);//*****ClassCastException
}
}
Generic version:
public class AnotherClass {
private class A {}
private class B extends A {}
private class C extends A {}
private class Box<T> {
private T aMethod(T t) {
return t;
}
}
public static void main(String[] args) {
AnotherClass someClass = new AnotherClass();
B b = someClass.new B();
C c = someClass.new C();
Box<A> box = someClass.new Box<>();
B bForStoringReturnedA = (B) box.aMethod(b);
B bForStoringReturnedAOne = (B) box.aMethod(c);//*****ClassCastException
}
}
Generics are perfectly doing their job in the example you gave.
You make a Box<A>, which has aMethod that takes in A and returns A (after type inference).
You pass it a B, and the method returns it as an A. You then cast it to a B, which works since the object actually is a B.
You then pass it a C and it is also returned as an A. You then cast it to a B which throws an exception since the object is not actually a B.
This is basically the same as doing:
Box<A> box = someClass.new Box<>();
A a1 = box.aMethod(b);
A a2 = box.aMethod(c);
B b1 = (B) a1;
B b2 = (B) a2;
I don't see how you expected generics to help you there.
If you however made a Box<B>:
Box<B> box = someClass.new Box<>();
B b1 = box.aMethod(b); // OK, + no need to cast
B b2 = box.aMethod(c); // Compile time error
error: method aMethod in class Box<T> cannot be applied to given types;
B b2 = box.aMethod(c); // Compile time error
^
required: B
found: C
reason: argument mismatch; C cannot be converted to B
where T is a type-variable:
T extends Object declared in class Box
1 error
The compiler correctly guarantees type safety, by giving an error.
I was expecting that if I make this code Generic, this ClassCastException will not occur, as the use of Generics will somehow make the bug causing the exception, DETECTABLE AT COMPILE TIME.
Nope. An explicit type cast isn't a compile-time operation, it's a run-time operation. Any time you cast a type, you're basically telling the compiler that you know more than it does about what the run-time type will be and that the compiler should trust you on the matter.
In both the presented cases, the information you knew that the compiler didn't know turned out to be incorrect. (Intentionally, of course, for the sake of what you were illustrating.) Hence the exception.
Generics carry just as much compile-time type safety as any other classes. There's no magic behind them, they can't detect future run-time errors before they happen.
What they do is provide a kind of "template" for a variety of types. A Box<A> is an entirely different type from a Box<B>, and carries all the compile-type type safety for itself that Java provides. Box<> by itself is just sort of a template for those types, but at compile time the specific type must still be known.
Basically, the return value of a Box<A>.aMethod() is A. It's not dynamic, it's not changeable, it's A. Just like in the non-generic version.
What generics give you is the ability to write these re-usable "template" types which can be combined with many other types to form the actual compile-time resulting type.
I have a Generic method that should work similarly to recursion, but calling different instances of the method for each calling.
public <M extends A> void doSomething(Class<M> mClass, M mObject)
{
// ... Do something with mObject.
A object = getObject();
Class<? extends A> objectClass = object.getClass();
doSomething(objectClass, objectClass.cast(object)); // Does not compile.
}
private A getObject() {...}
The problem is the line with a comment does not compile, giving the following error:
The method doSomething(Class, M) in the type MainTest is not applicable for the arguments (Class, capture#3-of ? extends A)
I don't quite understand why the compiler does not compile if it can call doSomething with M = "? extends A".
Why doesn't it compile?
Ok here is a crude explanation
You've typed your method so that it will accept M which is a subtype of A
Now you are calling your method using 'objectClass' which is a subtype of A BUT not necessarily a subtype of M.
Hence the compiler is complaining...
If you can explain what you are trying to do a bit more, I can help with a solution.
The language does not track wildcards like that (it seems). What you need to do is to capture that wildcard, which can be done with a method call with type inference.
public <M extends A> void doSomething(Class<M> mClass, M mObject) {
// ... Do something with mObject.
A object = getObject();
Class<? extends A> objectClass = object.getClass();
privateSomething(objectClass, object);
}
private <T extends A> void privateSomething(Class<T> objectClass, A object) {
doSomething(objectClass, objectClass.cast(object)); // Should compile.
}
As always, whilst reflection has some uses, it's usually a sign of confusion.
When you are asking the compiler to perform a cast, the exact type to perform the cast must be known. It is not sufficient to tell the compiler that you don't know about the exact type excerpt that it's a subclass of A.
Class tell the compiler that the type of the object is a subclass of A but it doesn't tell the compilator the exact type to be used for the casting.
Your problem is that you are trying to replace Polymorphism with Generic. As you are learning the hard way, Generic is not the new modern way of doing Polymorphism.
I am puzzled by generics. You can declare a field like:
Class<Collection<String>> clazz = ...
It seems logical that you could assign this field with:
Class<Collection<String>> clazz = Collection<String>.class;
However, this generates an error:
Syntax error on token ">", void expected after this token
So it looks like the .class operator does not work with generics. So I tried:
class A<S> { }
class B extends A<String> { }
Class<A<String>> c = B.class;
Also does not work, generates:
Type mismatch: cannot convert from Class<Test.StringCollection> to Class<Collection<String>>
Now, I really fail to see why this should not work. I know generic types are not reified, but in both cases it seems to be fully type safe without having access to runtime generic types. Anybody an idea?
Generics are invariant.
Object o = "someString"; // FINE!
Class<Object> klazz = String.class; // DOESN'T COMPILE!
// cannot convert from Class<String> to Class<Object>
Depending on what it is that you need, you may be able to use wildcards.
Class<? extends Number> klazz = Integer.class; // FINE!
Or perhaps you need something like this:
Class<List<String>> klazz =
(Class<List<String>>) new ArrayList<String>().getClass();
// WARNING! Type safety: Unchecked cast from
// Class<capture#1-of ? extends ArrayList> to Class<List<String>>
As for the non-reified at run-time case, you seem to have a good grasp, but here's a quote anyway, from the Java Tutorials on Generics, The Fine Print: A Generic Class is Shared by All Its Invocations:
What does the following code fragment print?
List <String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
System.out.println(l1.getClass() == l2.getClass());
You might be tempted to say false, but you'd be wrong. It prints true, because all instances of a generic class have the same run-time class, regardless of their actual type parameters.
That is, there's no such thing as List<String>.class or List<Integer>.class; there's only List.class.
This is also reflected in the JLS 15.8.2 Class Literals
A class literal is an expression consisting of the name of a class, interface, array, or primitive type, or the pseudo-type void, followed by a . and the token class.
Note the omission of any allowance for generic type parameters/arguments. Furthermore,
It is a compile time error if any of the following occur:
The named type is a type variable or a parameterized type, or an array whose element type is a type variable or parameterized type.
That is, this also doesn't compile:
void <T> test() {
Class<?> klazz = T.class; // DOESN'T COMPILE!
// Illegal class literal for the type parameter T
}
Basically you can't use generics with class literals, because it just doesn't make sense: they're non-reified.
I agree with the other answers, and would like to explain one point further:
Class objects represent classes that are loaded into the JVM memory. Each class object is actually an in-memory instance of a .class file. Java generics are not separate classes. They are just a part of the compile-time type-checking mechanism. Therefore, they have no run-time representation in a class object.
There seems to be a lack in class literals in Java, there is no way to create class literals with generic information while this can be useful in certain cases. Therefore, the following code cannot be called because it is impossible to provide the class literal
class A<S> {}
<S> A<S> foo( Class<A<S>> clazz ) {}
A<String> a = foo( A<String>.class ) // error
However, my main problem was I could also not call it with a class B that extended A. This was caused by the invariance restrictions. This was solved by using a wildcard:
class A<S> {}
class B extends A<String> {}
<S> A<S> foo( Class<? extends A<S>> clazz ) { return null; }
void test () {
A<String> s = foo( B.class );
}
That said I have not found a reason what the underlying reason is that Class<A<S>>.class is invalid. Neither erasure nor bounds seem require that this is invalid.
I have an interface A, which class B implements.
The following generic method works
public static <T, U extends T> List<T> listFactory(Collection<U> source) {
return new ArrayList<T>(source);
}
but
public static <T> List<T> listFactory(Collection<? extends T> source) {
return new ArrayList<T>(source);
}
does not (compilation error, type mismatch), when I am directing the output into
List<A> tester = listFactory(B.defaultCollectionFactory(3));
defaultCollectionFactory(int count) statically provides a collection of Bs, with a default labeling scheme.
Any insights as to why that is? It seems like the generic U and wildcard are doing the same thing.
The compiler is inferring a different type parameter for the listFactory method than you expect. It infers that T is type B, so the signature is effectively List<B> listFactory(Collection<? extends B> source). Specify the type parameter A by being explicit in the method invocation:
List<A> tester = Test.<A> listFactory(B.defaultCollectionFactory(3));
In the first construct, you are specifying that you are returning a List of the interface of the item that was passed in. You specify the relationship between the passed in Object and the return Object type in the U extends T direction. In this case, the compiler can associate A and B with T andU respectively.
In the second, there is no such differentiation, so the compiler assumes that T refers to B and will type the return value as List<B>. You then fall into the trap where, although B is an instance of A, List<B> is not an instance of List<A>. The compiler will complain:
Type mismatch: cannot convert from List<B> to List<A>
You will find that, with the first construct, you have the liberty of specifying a List of any interface the B implements or any superclass in the B hierarchy (List<Object>, for example), and the compiler will not complain.