У чому різниця між putIfAbsent та computeIfAbsent у Java 8 Map?


79

Читаючи цікаві статті, хлопці стверджують, що різниця між цими двома функціями:

Обидві функції прагнуть додати елемент, якщо вказаний Ключ ще не присутній у Map.

putIfAbsent додає елемент із заданим значенням, тоді як computeIfAbsent додає елемент зі значенням, обчисленим за допомогою ключа. http://www.buggybread.com/2014/10/java-8-difference-between-map.html

І

Ми бачили, що putIfAbsent усуває обов’язковий спосіб визначення оператора if, але що, якщо отримання статей Java насправді шкодить нашій продуктивності?

Щоб оптимізувати це, ми не хочемо отримувати статті, поки ми справді не впевнені, що вони нам потрібні - це означає, що перед тим, як завантажувати статті, нам потрібно знати, чи немає ключа. http://www.deadcoderising.com/2017-02-14-java-8-declarative-ways-of-modifying-a-map-using-compute-merge-and-replace/

Я не готовий зрозуміти, в чому різниця, чи можете ви, детальніше, детальніше розглянути ці дві функції?


1
Мати чи не мати. Якщо у вас є значення, ви можете вказати його; якщо у вас його немає, вам потрібно обчислити, а потім поставити.
laune

1
див. цей stackoverflow.com/questions/10743622/… , також схожий на дублікат
Євген

Відповіді:


151

Різниця No1

computeIfAbsent приймає функцію відображення, яка викликається для отримання значення, якщо ключ відсутній.

putIfAbsent приймає значення безпосередньо.

Якщо значення дороге для отримання, тоді це putIfAbsentвитрачає, якщо ключ уже існує.

Загальноприйнятим "дорогим" значенням є, наприклад, new ArrayList<>()коли ви створюєте Map<K, List<V>>, де створення нового списку, коли ключ уже існує (який потім відкидає новий список), генерує непотрібне сміття.


Різниця No2

computeIfAbsent повертає "поточне (існуюче або обчислене) значення, пов'язане із зазначеним ключем, або нуль, якщо обчислене значення дорівнює нулю".

putIfAbsent повертає "попереднє значення, пов'язане із зазначеним ключем, або нуль, якщо для ключа не було зіставлення".

Отже, якщо ключ уже існує, вони повертають те саме, але якщо ключ відсутній, computeIfAbsentповертає обчислене значення, а putIfAbsentповертає нуль.


Різниця No3

Обидва методи визначають "відсутній" як відсутній ключ або існуюче значення є нульовим, але:

computeIfAbsent не буде ставити нульове значення, якщо ключ відсутній.

putIfAbsent додасть значення, якщо ключ відсутній, навіть якщо значення є нульовим.

Це робить ніякої різниці в майбутньому не дзвонить computeIfAbsent, putIfAbsentі getдзвінки, але він робить різницю викликів , як getOrDefaultі containsKey.


4
Ви щойно врятували мій день! Дякую.
franta kocourek

4
Різна семантика повернення була досить заплутаною, і ось я опинився тут, прочитавши вашу дуже корисну відповідь.
Войтек,

Різниця No2 спричинила деякі NPE, тому що я намагався return map.putIfAbsent("key", someMethodReturningNotNullObj);всередині fnфункції, і мені було цікаво, чому, на біса, я отримував нульові значення від своєї fnфункції. computeIfAbsentце шлях!
Silviu Burcea

Повернення старого значення робить putIfAbsent () узгодженим з put (), який також повертає старе значення.
reggoodwin

@reggoodwin putIfAbsentне узгоджується з тим put, що putIfAbsentзалишає існуюче значення незмінним, а putзамінює значення, отже, частина "якщо відсутня" в назві методу.
Андреас,

62

Припустимо, у вас є Map<String,ValueClass>.

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

створить ValueClassекземпляр у будь-якому випадку, навіть якщо ключ "ключ" вже є в Map. Це просто створило б непотрібний екземпляр.

З іншої сторони

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

створить ValueClassекземпляр лише в тому випадку, якщо ключ "ключ" ще не входить Map(або зіставляється зі nullзначенням).

Тому computeIfAbsentє більш ефективним.

putIfAbsent еквівалентно:

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

while computeIfAbsentеквівалентно:

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

Ще одна невелика різниця між двома методами полягає в тому computeIfAbsent, що nullзначення відсутнього ключа не буде встановлено. putIfAbsentбуде.


7
Досить частим використанням методу computeIfAbsent було б Map<X,List<Y>>де ви повинні створити Список при першій зустрічі значення ключа. Спочатку один робив тестування, тестування, нове, computeIfAbsent(..., new ArrayList())
ставлення

7

Ви можете зрозуміти різницю, уважно розглянувши підписи методу:

  • putIfAbsent приймає ключ і значення і поміщає значення на карту, якщо для неї немає значення для цього ключа.
  • computeIfAbsent бере ключ і a Function . Якщо для цього ключа на карті немає значення, функція викликається для створення значення, яке потім поміщається на карту.

Якщо у вас вже є значення, використовуйте putIfAbsent .

Якщо у вас ще немає значення, а створення значення - дорога операція (наприклад, значення потрібно шукати в базі даних), тоді використовуйте computeIfAbsent, щоб дорогу операцію не потрібно було виконувати у випадку карта вже містить значення для вказаного ключа.


6

Можливо, реалізації за замовчуванням можуть пояснити трохи більше ....

default V putIfAbsent​(K key, V value) Реалізація за замовчуванням еквівалентна для цієї карти:

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

З іншої сторони:

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

еквівалентно:

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

1

V putIfAbsent(K key, V value) - Якщо вказаний ключ ще не пов'язаний зі значенням (або зіставлений з нулем), намагається обчислити його значення за допомогою заданої функції зіставлення та вводить його в цю карту, якщо не має значення null.

V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction) - Якщо вказаний ключ ще не пов'язаний зі значенням (або зіставлений з нулем), намагається обчислити його значення за допомогою заданої функції зіставлення та вводить його в цю карту, якщо не має значення null.

Читання документації може дати вам більш очевидну відповідь. https://docs.oracle.com/javase/8/docs/api/java/util/Map.html


0

Візьмемо цей простий приклад для 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"

cxбуде значенням, 3якщо 3на карті вже немає Значення з .


питання полягало в різниці між putIfAbsent()і computeIfAbsent, а не лише в тому, що putIfAbsent()робить.
Вольфсон

Я думаю, ви змішали keyі valueтут. Див Class HashMap<K,V>.
Wolfson

0

На це питання вже відповіли. Я зайняв трохи часу, щоб зрозуміти ("дорогу операцію не потрібно виконувати на випадок, якщо карта вже містить значення для зазначеного ключа в computeIfAbsent"), я вкладаю своє розуміння тут. Сподіваюся, це буде корисно для інших:

putIfAbsent()поводиться просто так, put()коли карта вже містить значення для вказаного ключа. putIfAbsent()лише перевіряє, чи є ключ нульовим чи ні. Якщо воно не є нульовим, воно повертає значення, а потім знову отримує це значення.

Однак computeIfAbsent()там є нульова перевірка як ключа, так і значення. Під час перевірки значення NULL на значення, якщо воно не має значення NULL, тоді існуюче значення з об'єкта карти призначається newValue і воно повертається Ось чому немає необхідності знову отримувати значення, оскільки існуюче значення з карти використовується повторно.

Зверніться до наступної програми для довідки:

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

Зверніться до наступного коду для putIfAbsent()та computeIfAbsent()з інтерфейсуMap.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;
    }   
.........
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.