2017-08-02 99 views
11

Java8 wprowadzono te ładne metody getOrDefault() i putIfAbsent(), pozwalające na pisanie kodu, takich jak:Czy powinienem użyć put() lub putIfAbsent() po użyciu getOrDefault()?

Map<Foo, List<Bar>> itemsByFoo = ... 
List<Bar> bars = itemsByFoo.getOrDefault(key, new ArrayList<>()); 
bars.add(someNewBar); 

Teraz zastanawiam się, czy istnieją powody faktyczne albo zrobić:

itemsByFoo.put(key, bars); 

lub

itemsByFoo.putIfAbsent(key, bars); 

Oba będą działać:

  • opcja 1 może zrobić wiele niepotrzebnych „put” połączeń, gdy dodawanie elementów do listy zdarza się często
  • opcja2 może zrobić wiele niepotrzebnych „containsKey” wzywa gdy dodawania nowych wpisów do nowych kluczy dominuje

SO: Czy istnieją uzasadnione powody, aby wybrać opcję 1 lub opcję 2 "zawsze"?

+13

Ahem, * ani *. Użyj 'itemsByFoo.computeIfAbsent (key, x -> new ArrayList <>()) .add (someNewBar);' dla całej operacji. – Holger

+0

@ Hger tak :) doskonały punkt. Ponieważ 'putIfAbsent' może zwrócić' null', ponieważ zwraca * poprzednią * wartość ... Również 'computeifAbsent' jest obecne w java-8, nie 7. Zrobiłem to wcześniej ... – Eugene

+2

@Eugene: 'putIfAbsent' został dodany do interfejsu' Map' w Javie 8, ponieważ był teraz możliwy dzięki 'default' metodom, ale musiał zachować kontrakt z' ConcurrentMap.putIfAbsent', który istnieje od wersji Java 5, więc nie jest tak wygodne, jak "computeIfAbsent" ... – Holger

Odpowiedz

19

getOrDefault jest odpowiedni, jeśli chcesz użyć stand-in dla nieobecnej wartości bez modyfikowania mapy. Jeśli chcesz dodać nową wartość dla nieobecnych klawiszy, możesz to zrobić w jednej operacji.

List<Bar> bars = itemsByFoo.computeIfAbsent(key, x -> new ArrayList<>()); 
bars.add(someNewBar); 

lub nawet

itemsByFoo.computeIfAbsent(key, x -> new ArrayList<>()).add(someNewBar); 

W najlepszym przypadku, gdy są zastępowane przez wdrożenie Map, jak z HashMap, to ponosi jeden odnośnika tylko hash.

Nie tylko putIfAbsent zawiera tylko dwa odnośniki podczas korzystania z implementacji default, ale, oczywiście, większość implementacji Map zapewni pojedynczą implementację wyszukiwania. Mimo to kombinacja getOrDefault i putIfAbsent nadal będzie zawierać dwa wyszukiwania w najlepszym przypadku, podczas gdy zoptymalizowany computeIfAbsent wykonuje tylko jeden.

+0

nie jest 'itemsFoo.compute ...; bars.add (someNewBar) 'stan wyścigu teraz? Ponieważ nie jest to pojedyncza operacja atomowa; ktoś może usunąć ten wpis przed zakończeniem 'add'? Zastanawiam się, czy dziś przekraczam twój limit cierpliwości z tak wieloma następującymi pytaniami ... – Eugene

+5

@Eugene: to ogólne pytanie 'Map'. Bycie atomistą nie było wymogiem. W przeciwnym razie masz dużo więcej do zrobienia. Chociaż możesz bezpiecznie wprowadzić wątek wstawiania, wykonując wszystko wewnątrz "obliczeń", nie ma to związku z kodem, który ostatecznie odczytuje "Listę" i musi być odczytany kod, jeśli pamięć nie jest celem samym w sobie, więc dla każdego prawdziwego przypadku i tak będzie potrzebny dodatkowy wysiłek. – Holger

+0

dziękuję. gdzie na świecie widzę tutaj "CHM"? mój błąd. To dobry punkt na temat 'putIfAbsent', który wykonuje 2 wyszukiwania w domyślnej implementacji ... Zauważyłem to właśnie teraz, gdy robi' get', a następnie 'put'. Byłby to świetny przykład na to, dlaczego domyślne metody są nadpisywane. – Eugene

5

Ważnym punktem o computeIfAbsent jest to, że zajmuje Function który zostanie wykonany tylko wtedy, gdy Key jest nieobecny i musimy domyślnie Value.

Podczas gdy wymaga domyślnego już Value, już obliczonego. W tym przypadku domyślnym Value potrzebowalibyśmy new ArrayList<Bar>(), który ma efekt uboczny przydziału nowego obiektu na stercie.

Chcemy odłożyć na później, dopóki nie będziemy pewni, że key nie jest już w wersji itemsByFoo. W przeciwnym razie generowalibyśmy niepotrzebne śmieci, aby je zebrać.

+0

Doskonałe dodatkowe informacje ;-) – GhostCat