user9170
user9170

Reputation: 1000

Tree of lambda functions in Java?

I am porting some software and it's working in Swift and JavaScript, but I cannot figure out how to do it in Java.

The JavaScript version looks like:

var obj = { "A": {
      "1": (params) => { console.log("A1", params); },  
      "2": (params) => { console.log("A2", params); }
    }, // A
    "B": {
      "1": (params) => { console.log("B1", params); },  
      "2": (params) => { console.log("B2", params);}
     }, //B
}; // obj

Where at runtime, the application engine calls

var a = 'A'; 
var i = '2';
obj[a][i](params);

I for the life of me cannot figure out how to do this in Java. One key constraint (no pun intended) is that the code must be structurally similar to the other ports.

Previously, I was trying to use Object[][], with

Map<String, Object> map = Stream.of(obj).collect(Collectors.toMap(data -> (String) data[0], data -> (Object) data[1]));

to be able to embed some Map<String, Integer> objects. Example:

Map<String, Integer> colorMap = Stream.of(new Object[][] {
        {"black", 0x000000},
        {"navy", 0x000080},
};

I tried to arrive at similar code, but Java did not like it, because lambdas and Object are not compatible.

The code I am looking to arrive at is:

Map<String, Map<String, Callable<Void> > > obj =  Stream.of(new Object[][] {
  {"A", Stream.of(new Object[][] {
    {"1", (params)->{ System.out.println("A1"); } },
    {"2", (params)->{ System.out.println("A2"); } } }),
  {"B", Stream.of(new Object[][] {
    {"1", (params)->{ System.out.println("B1"); } },
    {"2", (params)->{ System.out.println("B2"); } } } )}
  } 
});
...
// call the function (assuming A and 1 exist)
obj.get("A").get("1")(params);

But I am stumped on how to convert this to being able to use lambdas. The error I keep getting is: error: incompatible types: Object is not a functional interface or error: lambda expression not expected here

Upvotes: 2

Views: 263

Answers (1)

Joni
Joni

Reputation: 111239

One possible structure-preserving translation to Java is:

Map<String, Map<String, Runnable>> obj = Map.of(
    "A", Map.of(
        "1", () -> System.out.println("A1"),
        "2", () -> System.out.println("A2")
    ),
    "B", Map.of(
        "1", () -> System.out.println("B1"),
        "2", () -> System.out.println("B2")
    )
);

You use it like this:

obj.get("A").get("2").run();

If you need to pass arguments, or return values from the functions, replace Runnable with a different functional interface. You may have to defined one of your own. For example:

@FunctionalInterface
interface FunctionWithObjectParameters {
    void run(Object... args);
}

Update: If you need to do this in Java 8, one option is to use an external library that has methods to construct maps in a more convenient way - like guava collect.

Another option is to implement the convenience methods yourself. Map.of does not do any magic, you can easily replace it with your own "mapOf" method:

static <K, V> Map<K, V> mapOf(K key1, V value1) {
    return mapOfEntries(entry(key1, value1));
}

static <K, V> Map<K, V> mapOf(K key1, V value1, K key2, V value2) {
    return mapOfEntries(entry(key1, value1), entry(key2, value2));
}

static <K, V> Map<K, V> mapOf(K key1, V value1, K key2, V value2, K key3, V value3) {
    return mapOfEntries(entry(key1, value1), entry(key2, value2), entry(key3, value3));
}

// Add  more overloads if you need

static <K, V> Map<K, V> mapOfEntries(Map.Entry<K, V>... args) {
    // On Android, you may want to use ArrayMap instead of HashMap
    Map<K, V> map = new HashMap<>();
    for (Map.Entry<K, V> arg : args) {
        map.put(arg.getKey(), arg.getValue());
    }
    return map;
}

static <K, V> Map.Entry<K, V> entry(K key, V value) {
    return new AbstractMap.SimpleEntry<>(key, value);
}

Upvotes: 6

Related Questions