• Post Reply Bookmark Topic Watch Topic
  • New Topic

Strange issue with Java Generics  RSS feed

 
Greg Charles
Sheriff
Posts: 3015
12
Firefox Browser IntelliJ IDE Java Mac Ruby
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
While going through Jeanne and Scott's excellent book on Java 8 certification, I have been experimenting with different scenarios to test my understanding of generics. In one case at least, I've managed to baffle myself.



This compiles fine, and prints 2 as I expected it would. However, if I parameterize the GenTest class itself:



... now I get a compile error on line 15: Error: (15, 31) java: incompatible types: java.lang.Number cannot be converted to java.lang.Integer

If I add literally any concrete class to the declaration:



... now it compiles and runs again, and I don't even need to change the instantiation part of it. What's going on here?
 
Jeanne Boyarsky
author & internet detective
Marshal
Posts: 37496
546
Eclipse IDE Java VI Editor
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
The original example has compiler warnings about the unchecked cast. When working with generics and ignoring conversion warnings, all bets are pretty much off re compiler time safety.

Changing your original example to the following compiles, but throws an exception at runtime:


When you add the generic in your second example, the compiler correctly complains. So the question is why isn't the compiler picky enough in the first example?

 
Greg Charles
Sheriff
Posts: 3015
12
Firefox Browser IntelliJ IDE Java Mac Ruby
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Yes, that's a good way of putting it. Why isn't the compiler more picky in the first example? What about the second example causes it to become more picky? And, what about the third example makes it not picky again?

Another interesting point: the unchecked cast warnings you note for the first example are on lines 5 and 7, the casts to T. However, the ClassCastException if it happens, happens on line 15. I can't think of a case where the CCE could actually occur where the warnings say it might.

 
praveen kumaar
Ranch Hand
Posts: 461
22
Android Chrome Eclipse IDE Google App Engine Java Notepad Oracle Ubuntu Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Hi Greg,
If you will look the difference b/w the 2 cases then you will found in the latter case you have generified the class(GenTest).
and As per JLS,if you will use the raw type object then the type of a constructor,instance methods or any non-static field that is not inherited will also be a raw type(leading to the erasure of generic declaration).so your 1st case is a raw type and thus you just get a warning.
compiler will look your second case as a generic(as per the variable declaration,but the way you declare the GenTest(generified,line 5 in the last code snippet) is not a type safe) so this time the compiler will give you an error as compiler can only be sure that T is a Number(you have to do some task to sure the compiler that it is a Integer During when it is compiled,Generics!)
Here is what the JLS((§4.8) says:
Java® Language Specification((§4.8) wrote:The type of a constructor (§8.8), instance method (§8.4, §9.4), or non-static field (§8.3) M of a raw type C that is not inherited from its superclasses or superinterfaces is the raw type that corresponds to the erasure of its type in the generic declaration corresponding to C.

Do one thing in your second code snippet,add a static modifier to the method and you will note the difference,Reason is inside the JLS link.

Hope it Helps!

Kind Regards,
Praveen.
 
praveen kumaar
Ranch Hand
Posts: 461
22
Android Chrome Eclipse IDE Google App Engine Java Notepad Oracle Ubuntu Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Greg Charles wrote:Yes, that's a good way of putting it. Why isn't the compiler more picky in the first example?

That has to do with legacy code.suppose the GenTest is a legacy class(before java 1.5) then may be later the owner of the class can think about adding generics to the method thus java allows for it.
Greg Charles wrote:What about the second example causes it to become more picky?

In the second example java knows it's a new code and thus they don't want you to code in a old style(they just want that if some functionality has been given then make use of it.)
Greg Charles wrote:And, what about the third example makes it not picky again?

In the third example compiler knows that you had use the generic version so it continue normally but still here you had got a warning because compiler is not sure about T that it is a Integer really.
Greg Charles wrote:Another interesting point: the unchecked cast warnings you note for the first example are on lines 5 and 7, the casts to T. However, the ClassCastException if it happens, happens on line 15. I can't think of a case where the CCE could actually occur where the warnings say it might.

What CCE is ? please don't use acronyms.

Kind Regards,
Praveen.
 
Knute Snortum
Sheriff
Posts: 4281
127
Chrome Eclipse IDE Java Postgres Database VI Editor
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
CCE is ClassCastException.  Since he mentioned it in the proceeding sentence, he may have thought it was clear.  But yes, in general you should avoid acronyms that aren't explained.
 
Knute Snortum
Sheriff
Posts: 4281
127
Chrome Eclipse IDE Java Postgres Database VI Editor
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
You could clarify the acronym like this:
However, the ClassCastException (CCE) if it happens... 
 
praveen kumaar
Ranch Hand
Posts: 461
22
Android Chrome Eclipse IDE Google App Engine Java Notepad Oracle Ubuntu Windows
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Thanks knute,I am confused as it can be Compiler Code Execution,Continous and Comprehensive Evaluation or the Class Cast Exception..
so i simply ask..leave it.
Still i am not clear about the original poster(OP) last question,if it's about when it could occur then changing line 15→Integer j = g.myMethod("D") would cause it to happen.

Kind regards,
Praveen.
 
Campbell Ritchie
Marshal
Posts: 56553
172
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
One can also confirm that b/w (sic) doesn't mean black and white . We often forget that many readers of this website don't have Englsh as a first language, so please don't forget this how‑to.
The bit about explaining abbreviations has been in that how‑to for a very long time. At least 5 minutes, since I added it
 
Greg Charles
Sheriff
Posts: 3015
12
Firefox Browser IntelliJ IDE Java Mac Ruby
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Hey Praveen, sorry for all the confusion!

Knute was correct that I thought CCE would be clear from context, since I had written it out in my previous sentence. It's similar to your use of JLS, which is only written out in your quoted text, but it's still perfectly clear what you mean. BTW (hah!), both are abbreviations, but not acronyms. Yes, those terms are often confused, but it still bugs us poor pedants.

You were also unclear about my last question, but I'm not sure how I can clarify it beyond just reiterating. The casts and the compiler warnings are on lines 5 and 7, but the ClassCastException (aka CCE) happens on line 15, where there is no cast. It's not so much a question as it is an observation of an interesting anomaly -- one that I thought might shed light on my original question.

Now, back to the original question. I think you may have hit on something with that link to the Java Language Specification (JLS), and type erasure. The spec (specification) explains how type erasure is used when a parameterized class isn't bound to a specific type. In my example, that would lead to the erasure of type U throughout the class if it were used in fields, instance methods, and such (though it is not in my example). However, it does appear that the compiler is over-aggressively applying this type erasure to all types in instance-level contexts in the class. IOW (i.e, in other words), for my example, the compiler needs to use type erasure for U, but ends up using it for both U and T. Granted, the spec (specification) doesn't explicitly address what to do in this situation, but that behavior is sufficiently bizarre that I believe it could still be called a bug.

 
Brian Cole
Author
Ranch Hand
Posts: 959
1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I think Praveen Kumaar's explanation is correct, but I would just like to point out that the three situations are the same even without type bounding and unchecked casts.

If you add a type parameter on line 1, and don't provide type parameters on line 8, the compiler will flag an error: can't convert java.lang.Object to java.lang.Void.

also possibly relevant:
JLS §4.8 wrote:A non-generic class or interface type is not a raw type.

JLS §4.6 wrote:The type parameters of a constructor or method (§8.4.4), and the return type (§8.4.5) of a method, also undergo erasure if the constructor or method's signature is erased.

 
Greg Charles
Sheriff
Posts: 3015
12
Firefox Browser IntelliJ IDE Java Mac Ruby
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I also think Praveen's explanation was correct, though it didn't go far enough. My contention is this shows there is a bug in the Java compiler. At least, I don't see anything in the JLS (Justice League of Siberia) that says when type erasure is applied to a parametrized class, it must also be extended to unrelated generics used in instance methods. Moreover, I can't see any reason why this would be true. However, I'm actually hoping that someone will show me the error in my reasoning!

In any case, you are right that adding a type parameter on line 1 of your example also demonstrates this bug. The cleanest demonstration of it that I have come up with is:



That's probably what I'll submit with the bug report, once it's been vetted by the Ranchers.
 
Brian Cole
Author
Ranch Hand
Posts: 959
1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
The three situations are:
  • GenTest is a non-generic class
  • GenTest is a generic class with instance g a raw type
  • GenTest is a generic class with instance g a parameterized type [GenTest<Foo> g = new GenTest<>();]

  • Greg Charles wrote:At least, I don't see anything in the JLS (Justice League of Siberia) that says when type erasure is applied to a parametrized class, it must also be extended to unrelated generics used in instance methods

    In the middle situation, since the method in question (myMethod/getNullAs/identity) is an instance method that is not inherited from its superclass, the text Praveen Kumaar quoted from JLS §4.8 applies. Therefore the type of that method is the raw type that corresponds to the erasure of its declared type.

    The definition of erasure (§4.6) doesn't say anything about "unrelated generics." It simply says that "The erasure of a type variable is the erasure of its leftmost bound," which in this case is Object. (§4.4: "Every type variable declared as a type parameter has a bound. If no bound is declared for a type variable, Object is assumed.")

    So it seems to me that when g is a raw type, that the compiler error that you encountered does comply with the spec.

    Moreover, I can't see any reason why this would be true.

    Well, that's a separate issue. The compiler doesn't care why the spec is the way it is, only that it is in compliance.

    That said, JLS §4.8 does try to give some motivation: "To see why a non-static type member of a raw type is considered raw, consider the following example...."

    You could argue that it's not convincing. Presumably they could have introduced the notion of "unrelated generics" and handled them differently.

     
    praveen kumaar
    Ranch Hand
    Posts: 461
    22
    Android Chrome Eclipse IDE Google App Engine Java Notepad Oracle Ubuntu Windows
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Greg wrote:...The casts and the compiler warnings are on lines 5 and 7, but the ClassCastException (aka CCE) happens on line 15, where there is no cast...

    you are correct about warnings on line 5 and 7 but the way you you implement generics in myMethod can only sure one thing to the compiler that T is a Number(and it infers T as a Number so is the return type of your method) but what have you said is not true,indeed myMethod run successfully(that's why you have not seen the stack of myMethod while the ClassCastException prints the stack trace for its cause) on line 15 where the type of a expression g.myMethod is a Number and since you assigned this expression to the Integer type variable compiler does things behind the scenes,compiler is pretty much smart now a days.here is your code:
    we can disassemble this code by executing the command "javap -c GenTest".here is the disassembled code for our main method.
    Look at the line 10 in the disassembled code(however,it's marked 14 by the javap).you will see an opcode checkcast just after the Method mymethod is invoked which implies that their is a cast at that line.so probably you are clear about ClassCastException(CCE) now.you can find the description of checkcast→here.
    Greg wrote:My contention is this shows there is a bug in the Java compiler.
    The compiler works as per the rules described in the java® Language Specification,so it's not a bug it's just obeying the rules.
    Greg wrote:At least, I don't see anything in the JLS (Justice League of Siberia) that says when type erasure is applied to a parametrized class, it must also be extended to unrelated generics used in instance methods.
    I think you missed it have a look on itGenerics erasure for conctructor,instance method,non-static field when using a Raw Type..
    If you will look at the GenTest above you will find i have not parameterize the class so it's not a Raw type and thus it's generics is kept stored but in your last post you have generify the GenTest and using the Raw Type so as per rules it's generics get's deleted.
    If you will look in the specification(i have provided the link above),they have not put any justification for why the unrelated generics(not really any term in Java language specification) get's deleted.but may be i have one point that let's you think about this: since our object is a raw type(which means our object doesn't have any generics included,the class of an object is prameterized,though we have used a raw type) so generics of instance methods(as a member of this object) should also be get's erased(as per the object oriented paradigm).if it doesn't make you a sense then you can simply stick with the rules.why the java has kept their approach away from the multiple inheritance because it makes the things complex to handle(so instead of making rules for those complex things they have totally banned it,they put a another approach though(interface)).might me they have same approach for this point.

    I hope these things will help you!

    Kind regards,
    Praveen.
     
    • Post Reply Bookmark Topic Watch Topic
    • New Topic
    Boost this thread!