Як я можу використовувати нову функцію computeIfAbsent?


115

Я дуже хочу використовувати Map.computeIfAbsent, але це дуже давно, оскільки лямбдаси були в недограді.

Майже безпосередньо з Документів: він наводить приклад старого способу робити речі:

Map<String, Boolean> whoLetDogsOut = new ConcurrentHashMap<>();
String key = "snoop";
if (whoLetDogsOut.get(key) == null) {
  Boolean isLetOut = tryToLetOut(key);
  if (isLetOut != null)
    map.putIfAbsent(key, isLetOut);
}

І новий спосіб:

map.computeIfAbsent(key, k -> new Value(f(k)));

Але в їх прикладі, я думаю, я не зовсім "отримую це". Як би я перетворив код, щоб використовувати новий лямбда-спосіб вираження цього?


Я не впевнений, що ви не розумієте з прикладу?
Луї Вассерман

2
Що таке "k"? Чи визначається змінна? Як щодо "нової вартості" - це щось із java 8, або являє собою об'єкт, який мені потрібно визначити або переосмислити? whoLetDogsOut.computeIfAbsent (key, k -> new Boolean (tryToLetOut (k))) не компілюється, тому я щось пропускаю ...
Benjamin H

Що саме не компілюється? Яку помилку вона створює?
axtavt

Temp.java:26: помилка: незаконний запуск вираження whoLetDogsOut.computeIfAbsent (ключ, k -> новий булевий (tryToLetOut (k))); (вказує на ">")
Бенджамін Н

Компілює для мене чудово. Переконайтеся, що ви справді використовуєте компілятор Java 8. Чи працюють інші функції Java 8?
axtavt

Відповіді:


96

Припустимо, у вас є такий код:

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class Test {
    public static void main(String[] s) {
        Map<String, Boolean> whoLetDogsOut = new ConcurrentHashMap<>();
        whoLetDogsOut.computeIfAbsent("snoop", k -> f(k));
        whoLetDogsOut.computeIfAbsent("snoop", k -> f(k));
    }
    static boolean f(String s) {
        System.out.println("creating a value for \""+s+'"');
        return s.isEmpty();
    }
}

Тоді ви побачите повідомлення creating a value for "snoop"рівно один раз, оскільки при другому виклику computeIfAbsentвже є значення для цього ключа. kВ лямбда - вираз k -> f(k)просто placeolder (параметр) для ключа , який карта буде передавати в лямбда для обчислення значення. Так у прикладі ключ передається до виклику функції.

Крім того, ви можете написати: whoLetDogsOut.computeIfAbsent("snoop", k -> k.isEmpty());досягти того ж результату без допоміжного методу (але вивід налагодження тоді не побачите). І навіть простіше, оскільки ви можете написати просте делегування існуючому методу: whoLetDogsOut.computeIfAbsent("snoop", String::isEmpty);цій делегації не потрібно писати жодних параметрів.

Щоб бути ближчим до прикладу у вашому запитанні, ви можете записати його як whoLetDogsOut.computeIfAbsent("snoop", key -> tryToLetOut(key));(не має значення, називаєте ви цей параметр kчи key). Або запишіть його так, whoLetDogsOut.computeIfAbsent("snoop", MyClass::tryToLetOut);ніби tryToLetOutце метод, staticабо whoLetDogsOut.computeIfAbsent("snoop", this::tryToLetOut);якщо tryToLetOutце примірник.


114

Останнім часом я теж грав із цим методом. Я написав запам'ятований алгоритм для обчислення чисел Фібоначчі, який може послужити ще однією ілюстрацією щодо використання методу.

Ми можемо почати з визначення карти і введення значень в ньому базових випадках, а саме, fibonnaci(0)і fibonacci(1):

private static Map<Integer,Long> memo = new HashMap<>();
static {
   memo.put(0,0L); //fibonacci(0)
   memo.put(1,1L); //fibonacci(1)
}

А для індуктивного кроку все, що нам потрібно зробити, - це переосмислити нашу функцію Фібоначчі наступним чином:

public static long fibonacci(int x) {
   return memo.computeIfAbsent(x, n -> fibonacci(n-2) + fibonacci(n-1));
}

Як бачимо, метод computeIfAbsentвикористовуватиме наданий лямбда-вираз для обчислення числа Фібоначчі, коли число немає на карті. Це означає значне вдосконалення в порівнянні з традиційним алгоритмом, що рекурсує на дерево.


18
Приємне однолінійне перетворення в динамічне програмування. Дуже струнка.
Бенджамін Н

3
Ви можете отримати менше рекурсивних дзвінків, якщо спочатку у вас буде (n-2) виклик?
Thorbjørn Ravn Andersen

10
Вам слід бути обережнішими, коли ви використовуєте computeIfAbsent рекурсивно. Для отримання більш детальної інформації, будь ласка, перевірте stackoverflow.com/questions/28840047/…
Ajit Kumar

12
Цей код призводить до HashMapпошкодження внутрішніх приміщень, як і у bugs.openjdk.java.net/browse/JDK-8172951, і не вдасться працювати ConcurrentModificationExceptionв Java 9 ( bugs.openjdk.java.net/browse/JDK-8071667 )
Piotr Findeisen

23
Документи буквально кажуть, що функція відображення не повинна змінювати цю карту під час обчислень , тому ця відповідь явно неправильна.
fps

41

Ще один приклад. Під час складання складної карти карт метод computeIfAbsent () є заміною методу get () карти. Через ланцюг викликів computeIfAbsent () разом, відсутні контейнери будуються на ходу за допомогою наданих лямбда-виразів:

  // Stores regional movie ratings
  Map<String, Map<Integer, Set<String>>> regionalMovieRatings = new TreeMap<>();

  // This will throw NullPointerException!
  regionalMovieRatings.get("New York").get(5).add("Boyhood");

  // This will work
  regionalMovieRatings
    .computeIfAbsent("New York", region -> new TreeMap<>())
    .computeIfAbsent(5, rating -> new TreeSet<>())
    .add("Boyhood");

31

мульти-карта

Це дуже корисно, якщо ви хочете створити мультимап, не звертаючись до бібліотеки Google Guava для її реалізації MultiMap.

Наприклад, припустимо, ви хочете зберегти список студентів, які записалися на певний предмет.

Нормальне рішення для цього за допомогою бібліотеки JDK:

Map<String,List<String>> studentListSubjectWise = new TreeMap<>();
List<String>lis = studentListSubjectWise.get("a");
if(lis == null) {
    lis = new ArrayList<>();
}
lis.add("John");

//continue....

Оскільки він має код котла, люди, як правило, використовують Guava Mutltimap.

Використовуючи Map.computeIfAbsent, ми можемо писати в один рядок без guava Multimap наступним чином.

studentListSubjectWise.computeIfAbsent("a", (x -> new ArrayList<>())).add("John");

Стюарт Маркс та Брайан Гец гарно поговорили про це https://www.youtube.com/watch?v=9uTVXxJjuco


Ще один спосіб зробити мультимап в Java 8 (і більш стислий) - це просто зробити. studentListSubjectWise.stream().collect(Collectors.GroupingBy(subj::getSubjName, Collectors.toList());Це створює мульти-карту типу Map<T,List<T>в JDK лише більш стисло.
Зомбі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.