Was ist der Unterschied zwischen putIfAbsent und computeIfAbsent in Java 8 Map?

77

Beim Lesen eines interessanten Artikels behaupten die Jungs, dass der Unterschied zwischen den beiden Funktionen sind:

Beide Funktionen möchten ein Element hinzufügen, wenn der angegebene Schlüssel noch nicht in Map vorhanden ist.

putIfAbsent fügt ein Element mit dem angegebenen Wert hinzu, während computeIfAbsent ein Element mit dem mit dem Schlüssel berechneten Wert hinzufügt. http://www.buggybread.com/2014/10/java-8-difference-between-map.html

Und

Wir haben gesehen, dass putIfAbsent die zwingende Möglichkeit beseitigt, die if-Anweisung definieren zu müssen, aber was ist, wenn das Abrufen der Java-Artikel unsere Leistung wirklich beeinträchtigt?

Um dies zu optimieren, möchten wir die Artikel erst abrufen, wenn wir wirklich sicher sind, dass wir sie benötigen. Dies bedeutet, dass wir vor dem Abrufen der Artikel wissen müssen, ob der Schlüssel fehlt. http://www.deadcoderising.com/2017-02-14-java-8-declarative-ways-of-modifying-a-map-using-compute-merge-and-replace/

Ich habe nicht verstanden, was die Unterschiede sind. Können Sie diese beiden Funktionen näher erläutern?

Adelin
quelle
1
Haben oder nicht haben. Wenn Sie den Wert haben, können Sie ihn setzen; Wenn Sie es nicht haben, müssen Sie es berechnen und dann setzen.
Laune
1
siehe diese stackoverflow.com/questions/10743622/… , sieht auch aus wie ein Duplikat
Eugene

Antworten:

143

Unterschied # 1

computeIfAbsent Nimmt eine Zuordnungsfunktion auf, die aufgerufen wird, um den Wert zu erhalten, wenn der Schlüssel fehlt.

putIfAbsent nimmt den Wert direkt.

Wenn der Wert teuer zu erhalten ist, wird dies putIfAbsentverschwendet, wenn der Schlüssel bereits vorhanden ist.

Ein üblicher "teurer" Wert ist z. B. new ArrayList<>()beim Erstellen einer Map<K, List<V>>, bei der das Erstellen einer neuen Liste, wenn der Schlüssel bereits vorhanden ist (wodurch die neue Liste verworfen wird), unnötigen Müll erzeugt.


Unterschied # 2

computeIfAbsent Gibt "den aktuellen (vorhandenen oder berechneten) Wert zurück, der dem angegebenen Schlüssel zugeordnet ist, oder null, wenn der berechnete Wert null ist".

putIfAbsent Gibt "den vorherigen Wert zurück, der dem angegebenen Schlüssel zugeordnet ist, oder null, wenn für den Schlüssel keine Zuordnung vorhanden war".

Wenn der Schlüssel bereits vorhanden ist, geben sie dasselbe zurück. Wenn der Schlüssel jedoch fehlt, wird computeIfAbsentder berechnete Wert zurückgegeben, während putIfAbsentnull zurückgegeben wird.


Unterschied # 3

Beide Methoden definieren "nicht vorhanden" als Schlüssel fehlt oder vorhandener Wert ist null, aber:

computeIfAbsent setzt keinen Nullwert, wenn der Schlüssel fehlt.

putIfAbsent setzt den Wert, wenn der Schlüssel fehlt, auch wenn der Wert null ist.

Es macht keinen Unterschied für zukünftige Anrufe an computeIfAbsent, putIfAbsentund getAnrufe, aber es macht einen Unterschied für Anrufe wie getOrDefaultund containsKey.

Andreas
quelle
3
Du hast gerade meinen Tag gerettet! Vielen Dank.
Franta Kocourek
4
Die unterschiedliche Rückkehrsemantik war ziemlich verwirrend und so bin ich hier gelandet und habe Ihre sehr hilfreiche Antwort gelesen.
Wojtek
Der Unterschied Nr. 2 verursachte einige NPEs, weil ich es return map.putIfAbsent("key", someMethodReturningNotNullObj);innerhalb einer fnFunktion versuchte und mich fragte, warum zum Teufel ich Nullen von meiner fnFunktion bekam. computeIfAbsentist die Art!
Silviu Burcea
Durch die Rückgabe des alten Werts wird putIfAbsent () mit put () konsistent, wodurch auch der alte Wert zurückgegeben wird.
Reggoodwin
@reggoodwin putIfAbsentist nicht konsistent mit put, da putIfAbsentein vorhandener Wert unverändert bleibt, während putder Wert ersetzt wird, daher der Teil "falls nicht vorhanden" des Methodennamens.
Andreas
62

Angenommen, Sie haben eine Map<String,ValueClass>.

map.putIfAbsent("key", new ValueClass());

wird ValueClasstrotzdem eine Instanz erstellen , auch wenn der "Schlüssel" -Schlüssel bereits in der ist Map. Dies würde nur eine unnötige Instanz erstellen.

Auf der anderen Seite

map.computeIfAbsent("key", k -> new ValueClass());

erstellt nur dann eine ValueClassInstanz, wenn sich der Schlüssel "Schlüssel" noch nicht in der befindet Map(oder einem nullWert zugeordnet ist).

Ist computeIfAbsentdaher effizienter.

putIfAbsent ist äquivalent zu:

ValueClass value = new ValueClass();
if (map.get("key") == null) {
    map.put("key",value);
}

while computeIfAbsententspricht:

if (map.get("key") == null) {
    map.put("key",new ValueClass());
}

Ein weiterer kleiner Unterschied zwischen den beiden Methoden besteht darin, dass für einen fehlenden Schlüssel computeIfAbsentkein nullWert angegeben wird. putIfAbsentwerden.

Eran
quelle
7
Eine ziemlich häufige Verwendung für computeIfAbsent ist eine, Map<X,List<Y>>bei der Sie beim ersten Auftreten eines Schlüsselwerts eine Liste erstellen müssen. Ursprünglich hat man get, test, new, put ... Jetzt ist es nur noch dercomputeIfAbsent(..., new ArrayList())
laune
7

Sie können den Unterschied verstehen, indem Sie sich die Methodensignaturen genau ansehen:

  • putIfAbsent Nimmt einen Schlüssel und einen Wert und fügt den Wert in die Karte ein, wenn für diesen Schlüssel kein Wert in der Karte vorhanden ist.
  • computeIfAbsentnimmt einen Schlüssel und a Function. Wenn für diesen Schlüssel in der Karte kein Wert vorhanden ist, wird die Funktion aufgerufen, um den Wert zu erstellen, der dann in die Karte eingefügt wird.

Wenn Sie den Wert bereits haben, verwenden Sie putIfAbsent.

Wenn Sie den Wert noch nicht haben und das Erstellen des Werts eine teure Operation ist (z. B. muss der Wert in einer Datenbank nachgeschlagen werden), verwenden Sie ihn computeIfAbsent, damit die teure Operation für den Fall nicht ausgeführt werden muss Die Karte enthält bereits einen Wert für den angegebenen Schlüssel.

Jesper
quelle
6

Vielleicht können die Standardimplementierungen ein bisschen mehr klarstellen ...

default V putIfAbsent​(K key, V value) Die Standardimplementierung entspricht für diese Karte:

 V v = map.get(key);
  if (v == null)
      v = map.put(key, value);
  return v;

Auf der anderen Seite:

default V computeIfAbsent​(K key,
                          Function<? super K,? extends V> mappingFunction)

ist äquivalent zu:

if (map.get(key) == null) {
     V newValue = mappingFunction.apply(key);
     if (newValue != null)
         map.put(key, newValue);
}
zlakad
quelle
1

V putIfAbsent(K key, V value) - Wenn der angegebene Schlüssel noch keinem Wert zugeordnet ist (oder null zugeordnet ist), wird versucht, seinen Wert mithilfe der angegebenen Zuordnungsfunktion zu berechnen und in diese Zuordnung einzugeben, sofern nicht null.

V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction) - Wenn der angegebene Schlüssel noch keinem Wert zugeordnet ist (oder null zugeordnet ist), wird versucht, seinen Wert mithilfe der angegebenen Zuordnungsfunktion zu berechnen und in diese Zuordnung einzugeben, sofern nicht null.

Das Lesen der Dokumentation kann Ihnen eine offensichtlichere Antwort geben. https://docs.oracle.com/javase/8/docs/api/java/util/Map.html

Minotna
quelle
0

Nehmen Sie dieses einfache Beispiel für putIfAbsent():

Map myMap = new HashMap();
myMap.put(1,"ABC");
myMap.put(2,"XYZ");
myMap.put(3,"GHI");
//Output of map till this point is : 1-> "ABC", 2-> "XYZ", 3-> "GHI"
myMap.putIfAbsent(3,"cx");
//Output of map till this point is : 1-> "ABC", 2-> "XYZ", 3-> "GHI"

cxwird der Wert von sein, 3wenn 3in der Karte noch kein Wert von vorhanden ist .

Jain
quelle
Die Frage war nach dem Unterschied zwischen putIfAbsent()und computeIfAbsentnicht nur nach dem, was putIfAbsent()tut.
Wolfson
Ich denke du vermischst dich keyund valuehier. Siehe Class HashMap<K,V>.
Wolfson
0

Diese Frage wurde bereits beantwortet. Ich habe ein wenig Zeit gebraucht, um zu verstehen ("Eine teure Operation muss nicht durchgeführt werden, falls die Karte bereits einen Wert für den angegebenen Schlüssel in computeIfAbsent enthält"). Hoffe, dass dies für andere hilfreich sein wird:

putIfAbsent()verhält sich einfach so, put()als ob map bereits den Wert für den angegebenen Schlüssel enthält. putIfAbsent()prüft nur, ob der Schlüssel null ist oder nicht. Wenn es nicht null ist, gibt es den Wert zurück und ruft diesen Wert erneut ab.

Es computeIfAbsent()gibt jedoch eine Nullprüfung für Schlüssel und Wert. Wenn es bei der Nullprüfung auf Wert nicht null ist, wird der vorhandene Wert aus dem Kartenobjekt newValue zugewiesen und zurückgegeben. Aus diesem Grund muss der Wert nicht erneut abgerufen werden, da der vorhandene Wert aus der Karte wiederverwendet wird.

Siehe folgendes Programm als Referenz:

public class MapTest1 {
    public static final String AJAY_DEVGAN = "Ajay Devgn";
    public static final String AUTOBIOGRAPHY = "Autobiography";
    
    public static void main(String[] args) {
        MapTest1 mt = new MapTest1();
        mt.testPutCompute();
    }

    private void testPutCompute() {
        Map<String, List<String>> movies = getMovieObject();
        System.out.println("\nCalling putIfAbsent method.....");
        //System.out.println(movies.get(AJAY_DEVGAN));
        //movies.put(AJAY_DEVGAN, getAjayDevgnMovies());
        movies.putIfAbsent(AJAY_DEVGAN, getAjayDevgnMovies());
        
        System.out.println("\nCalling computeIfAbsent method......");
        //System.out.println(movies.get(AUTOBIOGRAPHY));
        movies.computeIfAbsent(AUTOBIOGRAPHY, t -> getAutobiographyMovies());
        
    }

    private Map<String, List<String>> getMovieObject() {
        Map<String, List<String>> movies = new HashMap<>();     

        movies.put(AJAY_DEVGAN, getAjayDevgnMovies());
        movies.put(AUTOBIOGRAPHY, getAutobiographyMovies());
        
        System.out.println(movies);
        return movies;
    }

    private List<String> getAutobiographyMovies() {
        System.out.println("Getting autobiography movies");
        List<String> list = new ArrayList<>();
        list.add("M.S. Dhoni - The Untold Story");
        list.add("Sachin: A Billion Dreams");
        return list;
    }

    private List<String> getAjayDevgnMovies() {
        System.out.println("Getting Ajay Devgn Movies");
        List<String> ajayDevgnMovies = new ArrayList<>();
        ajayDevgnMovies.add("Jigar");
        ajayDevgnMovies.add("Pyar To Hona Hi Tha");
        return ajayDevgnMovies;
    }
}

Siehe folgenden Code für putIfAbsent()und computeIfAbsent()von der SchnittstelleMap.class

public interface Map<K,V> {
.....

 default V putIfAbsent(K key, V value) {
        V v = get(key);
        if (v == null) {
            v = put(key, value);
        }

        return v;
    }
    
 default V computeIfAbsent(K key,
            Function<? super K, ? extends V> mappingFunction) {
        Objects.requireNonNull(mappingFunction);
        V v;
        if ((v = get(key)) == null) {
            V newValue;
            if ((newValue = mappingFunction.apply(key)) != null) {
                put(key, newValue);
                return newValue;
            }
        }

        return v;
    }   
.........
}
S Kumar
quelle