Как использовать новую функцию computeIfAbsent?
Я очень хочу использовать Map.computeIfAbsent, но это было слишком долго, так как лямбды в undergrad.
Почти непосредственно из документов: он дает пример старого способа делать вещи:
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)));
Но в их примере я думаю, что я не совсем "получаю". Как преобразовать код, чтобы использовать новый лямбда-способ выражения этого?
Ответы
Ответ 1
Предположим, что у вас есть следующий код:
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
является методом экземпляра.
Ответ 2
Недавно я тоже играл с этим методом. Я написал memoized алгоритм для вычисления чисел Фибоначчи, которые могли бы служить еще одной иллюстрацией того, как использовать метод.
Мы можем начать с определения карты и размещения в ней значений для базовых случаев, а именно: 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
будет использовать предоставленное лямбда-выражение для вычисления числа Фибоначчи, когда число не будет присутствовать на карте. Это представляет собой значительное улучшение по сравнению с традиционным рекурсивным алгоритмом дерева.
Ответ 3
Другой пример. При построении сложной карты карт метод computeIfAbsent() является заменой метода map 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");
Ответ 4
Это действительно полезно, если вы хотите создать Multimap без использования библиотеки guava (https://google.github.io/guava/releases/19.0/api/docs/com/google/common/collect/Multimap.html)
Например: Если вы хотите сохранить список студентов, которые поступили для определенного предмета.
Обычным решением для этого с использованием библиотеки 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 следующим образом.
studentListSubjectWise.computeIfAbsent("a", (x -> new ArrayList<>())).add("John");
Стюарт Маркс и Брайан Гетц расскажут об этом
https://www.youtube.com/watch?v=9uTVXxJjuco
Ответ 5
Нет никакой разницы между использованием computeIfAbsent() и простых функций put() get() get() для карты. Другими словами, вы можете переписать свою функцию таким образом
for (char ch : input){
Integer value;
if(countMap.containsKey(ch)){
value = countMap.get(ch);
value++;
countMap.put(ch, value);
}
else{
value = 1;
countMap.put(ch, value);
}
}