2

I have an Iterable<Map<? extends K, ? extends V>> and I'd like to provide a view into the union of these maps' entry sets, i.e. Set<Map.Entry<K, V>>.

I can construct a new Set like so (the cast triggers an unchecked warning, but compiles):

ImmutableSet.Builder<Map.Entry<K, V>> builder = ImmutableSet.builder();
for(Map<? extends K, ? extends V> map : chain) {
  for(Map.Entry<? extends K, ? extends V> e : map.entrySet()) {
    builder.add((Map.Entry<K, V>) e);
  }
}
return builder.build();

However this requires copying the sets, and doesn't stay in-sync with the backing maps. I've tried things like:

Set<Map.Entry<K, V>> unionSet = ImmutableSet.of();
for(Map<? extends K, ? extends V> map : chain) {
  unionSet = Sets.union((Set<Map.Entry<K, V>>)map.entrySet(), unionSet);
}
return unionSet;

But this fails to compile, with the error:

Cannot cast from Set<Map.Entry<capture#27-of ? extends K,
capture#28-of ? extends V>> to Set<Map.Entry<K,V>>

I expected a similar unsafe cast, but that seems forbidden in this context, presumably due to the nested generics.

So two questions:

  1. Why is this sort of cast forbidden, yet casts of the top-level generic is allowed?
  2. Is there a better way to provide a view into the union of these sets (with the type Set<Map.Entry<K, V>>)?
dimo414
  • 47,227
  • 18
  • 148
  • 244

2 Answers2

0

This is technically doable with the unsafe raw cast

Set<Map.Entry<K, V>> unionSet = ImmutableSet.of();
for(Map<? extends K, ? extends V> map : chain) {
  unionSet = Sets.union((Set) map.entrySet(), unionSet);
}

...and that is almost certainly the best way to provide a live view of the union of the entry sets.

As far as why the casts of generic arguments aren't allowed, I suspect there's technically no subtyping relationship between Set<Map.Entry<? extends K, ? extends V>> and Set<Map.Entry<K, V>>. It might be different for the verbose but technically different Set<? extends Map.Entry<? extends K, ? extends V>>, which is technically a generalization of Set<Map.Entry<K, V>> whereas the other is not.

Louis Wasserman
  • 191,574
  • 25
  • 345
  • 413
  • Well, that, or use a `Set extends Map.Entry extends K, ? extends V>>`. Or use `Map` throughout your code. – Louis Wasserman Jun 19 '15 at 22:26
  • I actually did go through and cleaned `chain` up to be an `Iterable>`; the constructor now has an unsafe cast, but the rest of the class is much cleaner. The raw cast worked before I refactored it, though. – dimo414 Jun 19 '15 at 22:39
0

You can get your unsafe cast to compile without using a raw type by doing a double cast instead, for example:

static <K, V> Set<Map.Entry<K, V>> unionSet(Iterable<Map<? extends K, ? extends V>> maps) {
    Set<Map.Entry<K, V>> unionSet = ImmutableSet.of();
    for (Map<? extends K, ? extends V> map : maps) {
        @SuppressWarnings("unchecked") // unsafe! see below
        Set<Map.Entry<K, V>> castEntrySet = (Set<Map.Entry<K, V>>) (Set<?>) map.entrySet();
        unionSet = Sets.union(castEntrySet, unionSet);
    }
    return unionSet;
}

But generic types aren't covariant for a reason. Here's an example of why this is unsafe:

Map<String, Integer> intMap = new HashMap<>();
intMap.put("key", 42);

Set<Map.Entry<String, Number>> unionSet = unionSet(ImmutableList.of(intMap));

Iterables.getOnlyElement(unionSet).setValue(0.42);

// java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.Integer
Integer shouldBeAnInt = intMap.get("key");
Community
  • 1
  • 1
Paul Bellora
  • 54,340
  • 18
  • 130
  • 181
  • Is there a benefit to the double-casting trick? Seeing as the first cast just clears the generic type entirely, I don't see the advantage over the raw cast. Good point about `Entry.setValue()`, I'll be sure to make `unionSet()` return immutable `Entry`s. – dimo414 Jun 21 '15 at 23:18
  • The advantage is to avoid having to suppress the additional raw type warning. Instead you would need `@SuppressWarnings({ "unchecked", "rawtypes" })`. Personally, I try to avoid raw types as much as I can, since their use can sometimes erase other generic types in surprising ways. – Paul Bellora Jun 22 '15 at 03:41