Использование Java 8 Необязательно для безопасного перемещения по карте

У меня есть Map<String, Map<String, String>> myMap в моем классе Java 8. Мне нужно перейти к строке String как myMap['keyA']['keyB'], возвращая null, если в корреляционной карте не существует 'keyA' или 'keyB'.

В groovy я бы использовал myMap?.keyA?.keyB и делал с ним. Я понимаю, что Java 8 Optional<T> приводит подобное поведение в java. Есть ли способ использовать это новое поведение, чтобы кратко имитировать функциональность groovy? Если нет, есть ли еще один краткий способ получить это поведение в Java 8, или я все еще придерживаюсь сложного процедурного кода?

Ответ 1

Вы можете использовать Optional ofNullable метод для создания Optional, который может или не может представлять значение null. Затем вы можете использовать метод map, который с Function сопоставляет результат с новым значением, если значение не было уже null.

Здесь я поставлю Function как выражение лямбда, чтобы получить значение из второго map, используя второй ключ.

Optional<String> result = Optional.ofNullable(myMap.get("keyA")).map(m -> m.get("keyB"));

Оттуда вы можете увидеть, имеет ли значение Optional значение с isPresent(), и если да, получите его с помощью get().

Тестирование:

public static Optional<String> method(Map<String, Map<String, String>> map,
    String key1, String key2)
{
    return Optional.ofNullable(map.get(key1)).map(m -> m.get(key2));
}

Код вызова:

Map<String, Map<String, String>> myMap = new HashMap<>();
Map<String, String> inner = new HashMap<>();
inner.put("one", "two");
myMap.put("three", inner);
System.out.println(method(myMap, "three", "one"));
System.out.println(method(myMap, "three", "dne"));
System.out.println(method(myMap, "dne", "dne"));

Вывод:

Optional[two]
Optional.empty
Optional.empty

Ответ 2

String valueOrNull = Optional.ofNullable(myMap.get("keyA"))
                             .map(x -> x.get("keyB"))
                             .orElse(null);

Сначала он завершает результаты первого поиска в Optional, который действует как монада. Если вы добавите третий слой (myMap.?keyA.?keyB.?keyC), он будет выглядеть так:

String valueOrNull = Optional.ofNullable(myMap.get("keyA"))
                             .map(x -> x.get("keyB"))
                             .map(x -> x.get("keyC"))
                             .orElse(null);

Ответ 3

Интересный вопрос.

Вы можете использовать рекурсию.

/**
* Finds the value of a node in nested maps.
* @return leaf value or null if none
*/
public <K, V> V getValueFromKeys(Map<K, V> map, K... keys) {
    V value = map.getOrDefault(keys[0], null);
    if (keys.length == 1) return value;
    if (value instanceof Map) {
        K[] remainingKeys = Arrays.copyOfRange(keys, 1, keys.length);
        return getValueFromKeys((Map<K, V>) value, remainingKeys);
    }
    return null;
}

Это будет работать с Java >= 9 (вы можете легко адаптировать его к предыдущим версиям).

Бонус (требуется Гуава):

@Test
public void getValueFromKeys_level1() {
    Map<String, String> mapLevel1 = ImmutableMap.of("key1", "value1");

    assertEquals("value1", getValueFromKeys(mapLevel1, "key1"));
    assertNull(getValueFromKeys(mapLevel1, null));
    assertNull(getValueFromKeys(mapLevel1, ""));
    assertNull(getValueFromKeys(mapLevel1, "wrong"));
    assertNull(getValueFromKeys(mapLevel1, "key1", "wrong"));
}

@Test
public void getValueFromKeys_level2() {
    Map<String, Map<String, String>> mapLevel2 = ImmutableMap.of("key1", ImmutableMap.of("subkey1", "value1"));

    assertEquals("value1", getValueFromKeys(mapLevel2, "key1", "subkey1"));
    assertNull(getValueFromKeys(mapLevel2, null));
    assertNull(getValueFromKeys(mapLevel2, ""));
    assertNull(getValueFromKeys(mapLevel2, "wrong"));
    assertNull(getValueFromKeys(mapLevel2, "key1", "wrong"));
    assertNull(getValueFromKeys(mapLevel2, "key1", "subkey1", "wrong"));
    assertTrue(getValueFromKeys(mapLevel2, "key1") instanceof Map);
}

@Test
public void getValueFromKeys_level3() {
    Map<String, Map<String, Map<String, String>>> mapLevel3 = ImmutableMap.of("key1", ImmutableMap.of("subkey1", ImmutableMap.of("subsubkey1", "value1")));

    assertEquals("value1", getValueFromKeys(mapLevel3, "key1", "subkey1", "subsubkey1"));
    assertNull(getValueFromKeys(mapLevel3, null));
    assertNull(getValueFromKeys(mapLevel3, ""));
    assertNull(getValueFromKeys(mapLevel3, "wrong"));
    assertNull(getValueFromKeys(mapLevel3, "key1", "wrong"));
    assertNull(getValueFromKeys(mapLevel3, "key1", "subkey1", "wrong"));
    assertNull(getValueFromKeys(mapLevel3, "key1", "subkey1", "subsubkey1", "wrong"));
}