Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why am I losing type information?

I have found something interesting to happen with Maps, rawtypes and generics. Following code:

static {
          Map map = new HashMap ();
          Set <Map.Entry> set = map.entrySet ();
          for (Map.Entry entry : set) {} // fine 
          for (Map.Entry entry : map.entrySet()) {} // compilation error
}

I am getting a compilation error about a Type incompatibility, namely: "Object cannot be cast to Entry".

Ideone for convenience

Why is the iterator over entrySet() losing the type information if there's no variable storing it again?

The rawtypes shouldn't affect the type so that Map.Entry suddenly is an Object. Or am I mistaken?

like image 464
Vogel612 Avatar asked Apr 14 '15 16:04

Vogel612


Video Answer


2 Answers

Your example makes it look like you have type information that you never had. You have written:

Map map = new HashMap ();
Set <Map.Entry> set = map.entrySet();
for (Map.Entry entry : set) {} // fine 
for (Map.Entry entry : map.entrySet()) {} // compilation error

But map.entrySet() is returning Set, not Set <Map.Entry>. You've performed an unchecked assignment which "adds" type information.

In the second for loop, we don't know what's inside the Set, so we can't iterate over Set <Map.Entry> without an explicit cast.

For example, compare the original example to this one where we don't "add" type information with the unchecked assignment.

Map map = new HashMap();
Set set = map.entrySet();
for (Map.Entry entry : set) {
} // Object cannot be cast to Entry
for (Map.Entry entry : map.entrySet()) {
} // Object cannot be cast to Entry

In this case, both for loops produce a compilation error.

This behaviour is documented in the Java Language Specification, section 4.8:

The type of a constructor (§8.8), instance method (§8.8, §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 erasure of its type in the generic declaration corresponding to C. The type of a static member of a raw type C is the same as its type in the generic declaration corresponding to C.

like image 117
DavidS Avatar answered Oct 09 '22 14:10

DavidS


I think the short answer is that Java allows "unchecked cast" in some situations but not others. Dealing with raw types (Generic types without a specified type) is one of these instances.

Keep in mind that for (Map.Entry entry : set) is equivalent to:

Iterator it = set.iterator();
while (it.hasNext())
{
    Map.Entry entry = it.next();
}

The assignment:

Set set = map.entrySet();

is allowed and will not generate any warning, as you are not introducing any new type, but in the for loop it.next() will return type Object and you will get compiler exception if you assign it without an explicit cast.

The assignment:

Set <Map.Entry> set = map.entrySet(); 

is allowed but will generate an "unchecked cast" warning because of the explicit type Map.Entry and in the for loop it.next() will return type Map.Entry and the assignment will work fine.

You can put the explicit cast in the for loop like this:

for(Map.Entry entry : (Set<Map.Entry>) map.entrySet())
like image 27
Richard Avatar answered Oct 09 '22 14:10

Richard