views:

300

answers:

1

Hello,

I've been learning and experimenting with Java Generics for a while but I have run into something that I cannot explain. Take for example the following code:

public class Question {
    public <T> Sub<T> getSub(Class<T> c) {
        return new Sub<T>(c);
    }
    public class Sub<S> {
        private Class<S> c;
        public Sub(Class<S> c) {
            this.c = c;
        }
        public void add(S s) {
        }
    }
}

And the test code:

import generics.Question.Sub;

public class Answer {
    public static void main(String [] args) {
        Question q = new Question();
        Sub<String> s = q.getSub(String.class);
        s.add("");
    }
}

When this is run it gives a wonderfully cryptic error message:

C:\Answer.java:8: incompatible types
found   : generics.Question.Sub<java.lang.String>
required: generics.Question.Sub<java.lang.String>
        Sub<String> s = q.getSub(String.class);
1 error

Now, through some experimentation I have worked out how to prevent the compiler error. I can either make the Sub class a static inner class, or I need to refer to the Sub class as Question.Sub<String>. What I can't do is explain why I need to do this.

I've done some reading of the Java documentation on Generics but none cover this particular case.

Can anyone explain why the code is an incompatible type in its current form?

-Edit-

Looking at this closer I can see that I get the same behaviour outside of Netbeans. If I have the code in the following structure:

generics\
generics\Question.java
generics\Answer.java

When I compile the files together, I do not get the error:

C:\>javac generics\Question.java generics\Answer.java

C:\>

However, when I compile Question first and then Answer, I get the error:

C:\>javac generics\Question.java

C:\>javac generics\Answer.java
generics\Answer.java:8: incompatible types
found   : generics.Question.Sub<java.lang.String>
required: generics.Question.Sub<java.lang.String>
        Sub<String> s = q.getSub(String.class);
                                ^
1 error

I have heard something mentioned about Type Erasure. Is this the case in this situation?

+1  A: 

Type erasure is a property of the way generics are currently implemented in Java. What this means is that the type of the variables are only known at compile time, but not at runtime. So, for example, in the following:

Map<String,String> map = new HashMap<String,String>();

then the compiler knows to check against items being put in at a String/String basis. However, the compiled code doesn't know anything about the String,String - you can still insert objects in with the wrong type, e.g.:

Map other = (Map)map;
other.put(new Integer(3), new Double( 4.5 );

The problem is that the compiled code doesn't check for argument types when passing in, and nor does runtime (since the type information has been erased, hence, type erasure).

I doubt that type erasure is an issue here - since at compilation time, you have the full type information - but rather that it's probably a bug. There's quite a few hairy problems with generics (from an implementation) and there are different compilers in use with JavaC and Eclipse, so might exhibit different bugs. In some cases, the Eclipse compiler has been more faithful to the spec than the Sun compiler has (so Eclipse creates errors whilst Sun doesn't) and it's mostly due to the complexity of the way that the type system works.

So it's most likely one (or more) bugs with generics in the 1.5.0_14 compiler ...

AlBlue
I can confirm this is the case, see below:C:\>javac -versionjavac 1.6.0_18C:\>javac src\generics\Question.javaC:\>javac src\generics\Answer.java -cp srcWorks fine. Thank you!
gencoreoperative
Actually "What this means is that the type of the variables are only known at compile time, but not at runtime.": the Type information is available at runtime and available by using reflection. In class files, generic types are still kept (how else will it compile against an external class with generics), hence "type erasure" isn't erasing the types from the class, only from the runtime.
Pindatjuh
To be clear; the information about the generic types is stored in annotations on the class, but not on the types of the method. So any non-generic call can still be used with it; however, the annotations are only used by the compiler at compile time. So they're not really part of the runtime, even if you can introspect the class file at runtime.
AlBlue