Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Map<K,V> not extends Function<K,V>?

Tags:

java-8

While playing around with the new Java 8 Stream API I got to wondering, why not:

public interface Map<K,V> extends Function<K, V>

Or even:

public interface Map<K,V> extends Function<K, V>, Predicate<K>

It would be fairly easy to implement with default methods on the Map interface:

@Override default boolean test(K k) {
    return containsKey(k);
}

@Override default V apply(K k) {
    return get(k);
}

And it would allow for the use of a Map in a map method:

final MyMagicMap<String, Integer> map = new MyMagicHashMap<>();
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);
map.put("D", 4);

final Stream<String> strings = Arrays.stream(new String[]{"A", "B", "C", "D"});
final Stream<Integer> remapped = strings.map(map);

Or as a Predicate in a filter method.

I find that a significant proportion of my use cases for a Map are exactly that construct or a similar one - as a remapping/lookup Function.

So, why did the JDK designers not decide to add this functionality to the Map during the redesign for Java 8?

like image 647
Boris the Spider Avatar asked Apr 01 '15 10:04

Boris the Spider


2 Answers

The JDK team was certainly aware of the mathematical relationship between java.util.Map as a data structure and java.util.function.Function as a mapping function. After all, Function was named Mapper in early JDK 8 prototype builds. And the stream operation that calls a function on each stream element is called Stream.map.

There was even a discussion about possibly renaming Stream.map to something else like transform because of possible confusion between a transforming function and a Map data structure. (Sorry, can't find a link.) This proposal was rejected, with the rationale being the conceptual similarity (and that map for this purpose is in common usage).

The main question is, what would be gained if java.util.Map were a subtype of java.util.function.Function? There was some discussion in comments about whether subtyping implies an "is-a" relationship. Subtyping is less about "is-a" relationships of objects -- since we're talking about interfaces, not classes -- but it does imply substitutability. So if Map were a subtype of Function, one would be able to do this:

Map<K,V> m = ... ;
source.stream().map(m).collect(...);

Right away we're confronted with baking in the behavior of what is now Function.apply to one of the existing Map methods. Probably the only sensible one is Map.get, which returns null if the key isn't present. These semantics are, frankly, kind of lousy. Real applications are probably going to have to write their own methods that supply key-missing policy anyway, so there seems to be very little advantage of being able to write

map(m)

instead of

map(m::get)

or

map(x -> m.getOrDefault(x, def))
like image 111
Stuart Marks Avatar answered Mar 27 '23 16:03

Stuart Marks


The question is “why should it extend Function?”

Your example of using strings.map(map) doesn’t really justify the idea of changing the type inheritance (implying adding methods to the Map interface), given the little difference to strings.map(map::get). And it’s not clear whether using a Map as a Function is really that common that it should get that special treatment compared to, e.g. using map::remove as a Function or using map::get of a Map<…,Integer> as ToIntFunction or map::get of a Map<T,T> as BinaryOperator.

That’s even more questionable in the case of a Predicate; should map::containsKey really get a special treatment compared to map::containsValue?

It’s also worth noting the type signature of the methods. Map.get has a functional signature of Object → V while you suggests that Map<K,V> should extend Function<K,V> which is understandable from a conceptional view of maps (or just by looking at the type), but it shows that there are two conflicting expectations, depending on whether you look at the method or at the type. The best solution is not to fix the functional type. Then you can assign map::get to either Function<Object,V> or Function<K,V> and everyone is happy…

like image 45
Holger Avatar answered Mar 27 '23 16:03

Holger