10

Why does the following not compile? The compiler gives an error for the + sign in the print line.

public class Test<T> {
  HashMap<Integer,Integer> m = new HashMap<Integer, Integer>();
  public static void main(String[] args) {
    Integer zero1 = 0;
    Integer zero2 = 0;
    Test t = new Test();
    t.m.put(1,zero1);
    t.m.put(2,zero2);
    System.out.println(t.m.get(1)+t.m.get(2)==t.m.get(2));
  }
}

I understand type erasure, but m is a HashMap<Integer,Integer>, it should not depend on the type <T> at all. Why is the compiler rejecting this? Removing <T> in the first line allows compiling, but I don't see why this shouldn't work as well.

Is this a compiler bug or is there any logic behind such behavior?

7
  • What's Test, and is test1 a HashMap<Integer,Integer> too? Commented Sep 27, 2012 at 1:45
  • Are you sure you have got that example right? The main method does not refer to m, and nothing refers to T. What is the exact compilation error you are getting ... and where? Commented Sep 27, 2012 at 1:49
  • Oops, copy paste error. Fixed now. Commented Sep 27, 2012 at 1:49
  • 1
    Why not, if I may ask? This seems unintended behavior in the Java compiler. If it is, then I would like to get confirmation from experts before submitting a bug report. If it is not, then I would like to know why the compiler is behaving this way. What makes this question less interesting than any other, in your opinion? Commented Sep 27, 2012 at 1:55
  • 1
    +1. This is fascinating. When I run javac -Xlint:unchecked Test.java, I get warnings that the calls to put are unchecked. For some reason, when t belongs to the raw type Test, the compiler treats t.m as belonging to the raw type HashMap. Commented Sep 27, 2012 at 2:04

2 Answers 2

8

I don't have an explanation why, but the behavior does seem to be correct-by-definition.§4.8 "Raw Types" of the Java Language Specification explicitly states that:

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.

In your example, the raw type C is Test (as opposed to Test<Object> or Test<Integer> or whatnot) and the non-static field M is m. As a result of the above rule, the type of t.m is the raw type HashMap, rather than HashMap<Integer, Integer>, so the return-type of t.m.get(Object) is Object rather than Integer.

Sign up to request clarification or add additional context in comments.

4 Comments

I wonder what the point of this behavior is... Seems strange to me
@A.R.S.: If I had to guess, my guess would be that this is an attempt on the part of the language designers to minimize the variety of interactions between raw types and parameterized types, since raw types are basically just for legacy support anyway. (Moreover, it wouldn't shock me to hear that this addresses some sort of weakness that would otherwise appear in the type system -- some sort of seemingly-checked conversion that wouldn't actually be checked -- but I can't quite think of an example of how that might arise, so I can't say for sure.)
+1 So if you use a generic class as a raw type you loose the generics on its fields too.
@A.R.S.: On further review, I find that that section of the spec gives some rationale for the fact that a non-static inner class of a raw type is itself a raw type: the concern is that said non-static inner class might refer to the type parameter of its containing type, and basically they decided not to make a special case for the situation where the non-static inner class doesn't do so. There's no corresponding rationale given for the case of a non-static field, but I guess we can infer that they didn't want different rules for Map<Integer, Integer> m as for Map<T, T> m?
1

Simply replace your line:

Test t = new Test();

by:

Test<Integer> t = new Test<Integer>();

and that will work.

2 Comments

I am aware that that works, my question is why what I wrote doesn't work. I see not sensible reason for the compiler to refuse this.
Because it seems that Class erasure imposes the elements class's erasure to be of its same type. Strange, I admit it, very good question.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.