My name is Zemian Deng, and I am a Senior Application Engineer working at Oracle for the Enterprise Knowledge Management product. NOTE: The views expressed on my blog and social network are my own and do not necessarily reflect the views of my employer. Zemian is a DZone MVB and is not an employee of DZone and has posted 84 posts at DZone. You can read more from them at their website. View Full User Profile

A Strange case of Java Generic and Inheritage Parameter Passing

12.30.2012
| 3067 views |
  • submit to reddit

I came a cross some strange Java code and I would like to share it here. Take a look few of classes I have here:

// file: AFoo.java
package atest.deng;
public abstract class AFoo<T> {
}

// file: Foo.java
package atest.deng;
public class Foo extends AFoo<String> {
}

// file: FooProcessor.java
package atest.deng;
public class FooProcessor<T> {
    public void process(Class<AFoo<?>> cls) {
        System.out.println(cls);
    }
}

// file: FooMain.java
package atest.deng;
public class FooMain {
    public static void main(String[] args) {
        new FooProcessor().process(Foo.class);
    }
}


bash> mvn compile
bash> [INFO] BUILD SUCCESS

I tried this with JDK6 + Maven and it compiles without problem. But try to remove the <T> part from FooProcessor and it will fail to compile!

// file: FooProcessor.java
package atest.deng;
public class FooProcessor {
    public void process(Class<AFoo<?>> cls) {
        System.out.println(cls);
    }
}

bash> mvn clean compile
bash> [ERROR] java-demo\src\main\javatest\deng\FooMain.java:[4,26] process(java.lang.Class<atest.deng.AFoo<?>>) in atest.deng.FooProcessor cannot be applied to (java.lang.Class<atest.deng.Foo>)

With the <T> the code won't compile, and yet we are not even using it in this case. How and why <T> affects the method parameters invocation?

Now, we can improve the FooProcessor in this way so that the presence of <T> doesn't have any affect.

package atest.deng;
public class FooProcessor {
    public void process(Class<? extends AFoo<?>> cls) {
        System.out.println(cls);
    }
}

That's a more proper way to write the generic parameter anyway. But despite a better solution, the puzzle is that the original code compiled under the compiler, but only with the <T> presented, and yet it's not used. Wouldn't you consider this as a Java compiler bug?

Published at DZone with permission of Zemian Deng, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Peter Levart replied on Sun, 2012/12/30 - 5:57am

 > Wouldn't you consider this as a Java compiler bug?

I would say it's a feature, not a bug. It has to do with the so called "raw types". When you invoke a constructor of a generic class and don't specify the <Type, ...> generic parameters, you end up with an expression that represents a "raw type". Raw type is nothing but a plain non-generic type (like pre 1.5 types) with a consequence that any members of that type (methods, fields) are turned into "raw typed" members too. So when you invoke the process() method on such type, it behaves as if you invoked the method with the following signature:

public void process(Class cls)


If you declare FooProcessor as a type without generic parameters and then invoke it's constructor as you only can, then you get a type that is not "raw-converted". Any generic methods on such type are still generic. That's why you get a compile-time error, which is entirely correct.

Lesson: Don't invoke constructors of generic classes without type parameters. This "feature" was meant to facilitate gradual migration of pre 1.5 code to generified code. When you write new code, you should not use this form - consider it deprecated.


Regards, Peter

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.