Reputation: 177
Below code throws a java.lang.ClassCastException if I directly use returned object of t.getTheString(implClass.class) in System.out.println() . Although when I assign object to some reference , it works fine, Why ?
interface testInterface{
public String testMethod();
}
class testClass{
public <T extends testInterface> T getTheString(Class< ? extends testInterface> classType){
try {
testInterface obj = classType.newInstance();
return (T)obj;
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
}
class implClass implements testInterface{
@Override
public String testMethod() {
return "Sample String";
}
@Override
public String toString(){
return super.toString() + " Overridden toString()";
}
}
public class genericTest {
public static void main(String args[]){
testClass t = new testClass();
testInterface ti = t.getTheString(implClass.class);
implClass i = t.getTheString(implClass.class);
//System.out.println(i); //<= Works fine
//System.out.println(ti); //<= Works fine
System.out.println(t.getTheString(implClass.class)); // Exception in thread "main" java.lang.ClassCastException: implClass cannot be cast to java.lang.String
}
}
Edit: Many people are getting confused with the question. The use of upper bound is just to create this example . I know this is incorrect . The doubt is , if you decompile above code , you will see the compiler typecast the t.getTheString(implClass.class) result to String because may be it is unsure of the type probably . My question is wont it be better if it is typecast to Object . In that case it will call toString() method and code will work fine . Is this an issue with Java Compiler or am i missing any thing here ?
Upvotes: 2
Views: 177
Reputation: 273153
The causes of the problem is already mentioned in other answers - your classType
parameter can be any type that implements testInterface
, not necessarily T
.
Here's a solution.
The problem currently is that classType
is not constrained, and without enough context, T
cannot be inferred correctly, causing obj
to be casted to a wrong type. So if we change the method to this:
public <T extends testInterface> T getTheString(Class<T> classType){
T
can be inferred correctly every time from the parameter!
Another solution would be to spoonfeed the compiler with the type you want:
System.out.println((testInterface)t.getTheString(implClass.class));
From your comment to another answer:
If compiler is uncertain about the type why not type cast it to Object instead of String
There are only two overloads of println
that the compiler can choose to call because the others are primitive types. It's either println(Object)
or println(String)
. According to the JLS (section 15.12.2):
This step uses the name of the method and the types of the argument expressions to locate methods that are both accessible and applicable There may be more than one such method, in which case the most specific one is chosen.
See this post for more info.
Since String is more specific than Object, the latter is chosen.
Upvotes: 2
Reputation: 4369
Generics are erased at runtime, so what happens is that when JVM needs to choose which variant of System.out.println()
to call it has no clue what impl it is and assumes incoming class is String
and takes System.out.println(String string)
and obviously your implClass
instance is not String
.
This all comes from you using generics, but not declaring them at class level. Change testClass
declaration to declare your generic T
class testClass<T> {
once you do, compiler should start complaining about missing cast when trying to convert testInterface
to implClass
at
implClass i = t.getTheString(implClass.class);
once you correct it like:
implClass i = (implClass) t.getTheString(implClass.class);
the System.out.println()
will start working as expected.
If you are wondering why compiler makes wrong choice of method, think about this:
- Type T is not declared at class level, so when it is erased during compilation, it becomes "anything",
- when compiler is later on trying to find out matching variant of println()
method to call, it takes first that matches, since type has become "anything", the first method variant will match
- and in Oracle JDK impl of PrintStream, first impl happens to be the one with String as parameter (check the source code ;)
Upvotes: 1
Reputation: 3152
If you un-comment the two "it works" lines and run genericTest.class through a decompiler (I used http://www.javadecompilers.com/), you'll see:
public static void main(String[] arrstring) {
testClass testClass2 = new testClass();
Object t = testClass2.getTheString(implClass.class);
implClass implClass2 = (implClass)testClass2.getTheString(implClass.class);
System.out.println((Object)implClass2);
System.out.println(t);
System.out.println((String)testClass2.getTheString(implClass.class));
}
The first two calls to println
call the method that accepts an Object, which in turn calls toString()
and prints the result you expect. In that third println
, the compiler expects the result to really be a String, and attempts the cast, which is where you're getting the exception from.
Upvotes: 0
Reputation: 140553
Works fine for me; renamed and ran as Main.java:
> javac Main.java
Note: Main.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
> java Main
implClass@15dc8287 Overridden toString()
Then:
> javac -Xlint:unchecked Main.java
Main.java:10: warning: [unchecked] unchecked cast
return (T)obj;
^
required: T
found: testInterface
where T is a type-variable:
T extends testInterface declared in method <T>getTheString(Class<? extends testInterface>)
The problem here: in your method getTheString() you cast to T; but there is no guarantee that the incoming class has anything to do with that T! A class that implements that interface isn't necessarily also a T.
And for the exception that you see; as said - not reproducible (some setup problem on your side)?
Upvotes: 0