새로운 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)));

그러나 그들의 예에서 나는 "그것을 이해"하고 있지 않다고 생각합니다. 이것을 표현하는 새로운 람다 방식을 사용하도록 코드를 어떻게 변환할까요?


거기의 예에서 이해하지 못하는 것이 무엇인지 잘 모르겠습니까?
Louis Wasserman 2013 년

2
"k"는 무엇입니까? 정의되는 변수입니까? "new Value"는 어떻습니까? Java 8의 것입니까, 아니면 정의하거나 재정의해야하는 객체를 나타내는 것입니까? whoLetDogsOut.computeIfAbsent (키, K -> 새로운 부울 ((k)는) tryToLetOut) 컴파일되지 않습니다 ... 내가 뭔가를 누락 그래서
벤자민 H

정확히 컴파일되지 않는 것은 무엇입니까? 어떤 오류가 발생합니까?
axtavt 2013 년

Temp.java:26 : 오류 : 잘못된 표현식 시작 whoLetDogsOut.computeIfAbsent (key, k-> new Boolean (tryToLetOut (k))); ( ">"를 가리킴)
Benjamin H

나를 위해 잘 컴파일됩니다. 실제로 Java 8 컴파일러를 사용하는지 확인하십시오. 다른 Java 8 기능이 작동합니까?
axtavt 2013 년

답변:


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
동적 프로그래밍으로의 멋진 한 줄 변환. 매우 매끄 럽습니다.
Benjamin H

3
먼저 (n-2) 호출이 있으면 재귀 호출을 더 적게받을 수 있습니까?
Thorbjørn Ravn Andersen

10
computeIfAbsent를 재귀 적으로 사용할 때는 더주의해야합니다. 자세한 내용은 확인하시기 바랍니다 stackoverflow.com/questions/28840047/...을
아지트 쿠마르

12
이 코드 HashMapbugs.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

다중지도

당신이 만들 경우에 정말 유용 과 multimap을 받는 의지하지 않고 구글 구아바 의 구현을위한 라이브러리 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");

Stuart Marks와 Brian Goetz는 이것에 대해 좋은 이야기를했습니다. https://www.youtube.com/watch?v=9uTVXxJjuco


Java 8 (더 간결하게)에서 멀티 맵을 만드는 또 다른 방법 은 JDK에서 studentListSubjectWise.stream().collect(Collectors.GroupingBy(subj::getSubjName, Collectors.toList());유형의 멀티 맵을 Map<T,List<T>보다 간결하게 생성하는 것 입니다.
Zombies
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.