LK_sde89
LK_sde89

Reputation: 177

ClassCastException is System.out.println

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

Answers (4)

Sweeper
Sweeper

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

Jan
Jan

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

Scovetta
Scovetta

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

GhostCat
GhostCat

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

Related Questions