Yeni computeIfAbsent işlevini nasıl kullanırım?


115

Map.computeIfAbsent'i kullanmayı çok istiyorum, ancak üniversite öğrencisi lambdalardan beri çok uzun zaman oldu.

Neredeyse doğrudan dokümanlardan: İşleri yapmanın eski yöntemine bir örnek veriyor:

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);
}

Ve yeni yol:

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

Ama onların örneğinde, pek "anlamadığımı" düşünüyorum. Bunu ifade etmek için yeni lambda yolunu kullanmak için kodu nasıl dönüştürebilirim?


Oradaki örnekten neyi anlamadığından emin değilim?
Louis Wasserman

2
"K" nedir? Tanımlanan bir değişken mi? "Yeni Değer" e ne dersiniz - bu java 8'den bir şey mi, yoksa tanımlamam veya geçersiz kılmam gereken bir nesneyi mi temsil ediyor? whoLetDogsOut.computeIfAbsent (key, k -> new Boolean (tryToLetOut (k))) derlenmiyor, bu yüzden bir şeyi kaçırıyorum ...
Benjamin H

Tam olarak ne derlemez? Hangi hatayı üretir?
axtavt

Temp.java:26: hata: yasadışı ifade başlangıcı whoLetDogsOut.computeIfAbsent (key, k -> new Boolean (tryToLetOut (k))); (">" işaretini gösterir)
Benjamin H

Benim için iyi derler. Java 8 derleyicisini gerçekten kullandığınızdan emin olun. Diğer Java 8 özellikleri çalışıyor mu?
axtavt

Yanıtlar:


96

Aşağıdaki koda sahip olduğunuzu varsayalım:

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();
    }
}

O zaman mesajı creating a value for "snoop"tam olarak bir kez göreceksiniz, ikinci çağrıda computeIfAbsentolduğu gibi o anahtar için zaten bir değer var. kLambda ekspresyonundak -> f(k) sadece harita değerinin hesaplanması için lambda geçecek anahtar için bir placeolder (parametresi). Dolayısıyla, örnekte anahtar işlev çağrısına aktarılır.

Alternatif olarak whoLetDogsOut.computeIfAbsent("snoop", k -> k.isEmpty());, aynı sonucu bir yardımcı yöntem olmadan elde etmek için yazabilirsiniz (ancak o zaman hata ayıklama çıktısını göremezsiniz). Ve daha da basit, çünkü yazabileceğiniz mevcut bir yönteme basit bir yetkilendirme: whoLetDogsOut.computeIfAbsent("snoop", String::isEmpty);Bu delegasyonun yazılacak herhangi bir parametreye ihtiyacı yoktur.

Sorunuzdaki örneğe daha yakın olmak için, bunu şu şekilde yazabilirsiniz whoLetDogsOut.computeIfAbsent("snoop", key -> tryToLetOut(key));(parametrenin adını kveya adını vermenizin önemi yoktur key). Veya olarak yazmak whoLetDogsOut.computeIfAbsent("snoop", MyClass::tryToLetOut);eğer tryToLetOutolduğunu staticya whoLetDogsOut.computeIfAbsent("snoop", this::tryToLetOut);eğer tryToLetOutbir örnek yöntemidir.


114

Son zamanlarda ben de bu yöntemle oynuyordum. Metodun nasıl kullanılacağına dair başka bir örnek olarak hizmet edebilecek Fibonacci sayılarını hesaplamak için hafızaya alınmış bir algoritma yazdım.

Biz bir harita tanımlayan ve baz durumlar için buna değerleri koyarak başlayabilirsiniz, yani fibonnaci(0)ve fibonacci(1):

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

Endüktif adım için tek yapmamız gereken Fibonacci fonksiyonumuzu aşağıdaki gibi yeniden tanımlamak:

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

Gördüğünüz gibi, yöntem computeIfAbsentharitada sayı olmadığında Fibonacci sayısını hesaplamak için sağlanan lambda ifadesini kullanacaktır. Bu, geleneksel, ağaç özyinelemeli algoritmaya göre önemli bir gelişmeyi temsil eder.


18
Dinamik programlamaya güzel, tek satırlı dönüştürme. Çok şık.
Benjamin H

3
Önce (n-2) çağrınız varsa daha az özyinelemeli çağrı alabilirsiniz?
Thorbjørn Ravn Andersen

10
ComputeIfAbsent'i yinelemeli olarak kullandığınızda daha dikkatli olmalısınız. Daha fazla ayrıntı için lütfen stackoverflow.com/questions/28840047/…
Ajit Kumar

12
Bu kod HashMap, bugs.openjdk.java.net/browse/JDK-8172951'de olduğu gibi dahili dosyalarının bozulmasına neden olur ve ConcurrentModificationExceptionJava 9'da başarısız olur ( bugs.openjdk.java.net/browse/JDK-8071667 )
Piotr Findeisen

23
Dokümanlar kelimenin tam anlamıyla haritalama işlevinin hesaplama sırasında bu haritayı değiştirmemesi gerektiğini söylüyor , bu nedenle bu yanıt açıkça yanlış.
fps

41

Başka bir örnek. Karmaşık bir harita haritası oluştururken, computeIfAbsent () yöntemi, map'in get () yönteminin yerine geçer. ComputeIfAbsent () çağrılarının birbirine zincirlenmesi yoluyla, eksik kapsayıcılar sağlanan lambda ifadeleri tarafından anında oluşturulur:

  // 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

Çok haritası

Google Guava kitaplığına başvurmadan çoklu harita oluşturmak istiyorsanız bu gerçekten yararlıdır .MultiMap .

Örneğin, belirli bir konuya kaydolan öğrencilerin bir listesini saklamak istediğinizi varsayalım.

Bunun için JDK kitaplığını kullanarak normal çözüm şudur:

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

//continue....

Bazı standart kodlara sahip olduğu için insanlar Guava kullanma eğilimindedir Mutltimap.

Map.computeIfAbsent kullanarak guava Multimap olmadan tek satırda aşağıdaki gibi yazabiliriz.

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

Stuart Marks ve Brian Goetz bu https://www.youtube.com/watch?v=9uTVXxJjuco hakkında güzel bir konuşma yaptı


Java 8'de studentListSubjectWise.stream().collect(Collectors.GroupingBy(subj::getSubjName, Collectors.toList());çoklu harita oluşturmanın başka bir yolu (ve daha özlü), sadece yapmaktır. Bu, JDK'da Map<T,List<T>yalnızca daha kısaca imho bir çoklu harita türü üretir .
Zombies
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.