ava does not have a convenient way to treat a function as an object and then pass it around, but suppose it could. Say a function were an implementation of the following interface:
interface Function { Y apply(X x);}
This may not be as convenient as what Python, Ruby, or JavaScript offers, but it gives the programmer a new dimension. For instance, you could apply a function to a collection, element-wise, and produce another collection. (Functional programming has special terms for such an operation, but this example is in Java, so let’s not make things too complicated.)
In a sense, Map
Function function(Map map) { return new Function() { public Y apply(X x) { return map.get(x); } }}
For the argument values that are not in the list of map keys, the function will return null. You could modify function() to take the second parameter, the default value for the function.
On the other hand, if you have a Function
This article introduces maps that have two sets of keys, and shows how useful this class can be in everyday Java coding.
Introducing Two Keys
Often, mapping values of one type to another does not match the general knowledge of a domain. Suppose you have Hunters who from time to time bring home Mammoths, and you want to keep the data who, when, and how big. If you were using SQL, the solution would be obvious. You would use something like this:
CREATE TABLE Log (Hunter String, Time Timestamp, Weight Number);
In Java, however, this is hard to express. Even if you have a class Hunter, how do you link these three data items together? Solution one would be to create a special class:
class Event { Hunter hunter; Timestamp time; double mammothWeight; // won't fit in float}
The disadvantage of this is that it does not help you to find facts about a certain hunter or a certain date. Even if you have a Collection
A natural, database-like solution would be to index such events. But what would be the key? J2EE suggests having a special class for keys, like this:
class EventKey { Hunter hunter; Timestamp time;}
Such a key, while useful for retrieving Entity Beans, does not make any practical sense. There is no such thing as “hunter-timestamp”?hunters are hunters and time is time. Moreover, this kind of “key” does not help you trace the history of successes (or failures) for any given hunter, nor the history of the tribe’s feasts and troubles. This means that you need to introduce separate indexes for hunters and for time. Depending on the problem you think you are solving, you can have one index or two:
Map> hunterIndex;Map> timeIndex;
Now imagine that in addition to a Collection
In practice, when people have such cascading maps, they rarely bother to keep a separate Collection
- When requested, scan through hunterIndex, adding up the sizes of secondary maps.
- Keep a separate counter by “caching” it, and update it on each addition or deletion. In this case, the programmer must take care of threads and exceptions, and imagine the application running for months?and never recounting its mammoths.
As I see it, all this happens because Java programmers are used to thinking in terms of existing classes, and they just pick up whatever they find in java.util or java.lang. Python programmers do not even encounter such problems, and JavaScript programmers do not have a choice: their only option is associative array with strings as keys.
What Would a SQL Programmer Do?
A SQL programmer would have a Collection
public interface Map2 { int size(); boolean containsKeyPair(Object key1, Object key2); V get(X key1, Y key2); V put(X key1, Y key2, V value); V remove(X key1, Y key2); Set keySet1(); Set keySet2(); Collection values(); Map curry1(X key1); Map curry2(Y key2); interface Entry { public X getKey1(); public Y getKey2(); public V getValue(); V setValue(V value); } Set> entrySet();}
Note two new methods, curry1 and curry2. They take one key and return a map from another key to values. The names come from currying, the functional programming term for this operation.
A default implementation, AbstractMap2
The default implementation is not very efficient, so let’s introduce an indexed map: IndexedMap2
Two-Parameter Maps
As you have read, a relatively small group of classes solves a rather frequent problem: when you have a cascading map, which index goes first and how do you scan the whole collection? You could adapt the classes you know, Map and Set, by always designating whether hunters go first and own a collection of time-indexed events, or whether time goes first and each moment has a collection of hunter-indexed events. But the better solution is applying Map2, a map with two sets of keys.