Статьи

Puzzler: вложенный computeIfAbsent

обзор

В библиотеках Java 8 появился новый метод map, computeIfAbsent. Это очень полезный способ превратить вашу Карту в кеш объектов, связанных с ключом.

Однако есть комбинация, которую вы, возможно, не рассматривали; что произойдет, если вы вызовете computeIfAbsent внутри себя.

1
2
3
4
5
6
map.computeIfAbsent(Key.Hello, s -> {
    map.computeIfAbsent(Key.Hello, t -> 1);
    return 2;
});
 
enum Key {Hello}

Хотя в простых случаях это может показаться странным, но в более сложном коде вы можете сделать это случайно (как я сделал сегодня днем). Так что же происходит? Ну, это зависит от коллекции, которую вы используете.

1
2
3
4
5
6
7
8
9
HashMap: {Hello=2}
WeakHashMap: {Hello=2}
TreeMap: {Hello=2}
IdentityHashMap: {Hello=2}
EnumMap: {Hello=2}
Hashtable: {Hello=2, Hello=1}
LinkedHashMap: {Hello=1, Hello=2}
ConcurrentSkipListMap: {Hello=1}
ConcurrentHashMap:

Примечание: ConcurrentHashMap никогда не возвращается. Это блокировка не кажется повторной.

ConcurrentSkipListMap имеет наиболее разумный результат, сохраняя первую добавленную стоимость. Hello = 2 подходит для этой неопределенной ситуации, если сбивает с толку, поскольку это второе значение, а не первое. Что не имеет особого смысла, так это чтобы дважды появился уникальный неизменяемый ключ.

Наличие тупика ConcurrentHashMap само по себе является неудачным, но, по крайней мере, не очень тонким.

Полный код

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class A {
    public static void main(String[] args) {
        for (Map map : new Map[]{
                new HashMap<>(),
                new WeakHashMap<>(),
                new TreeMap<>(),
                new IdentityHashMap<>(),
                new EnumMap<>(Key.class),
                new Hashtable<>(),
                new LinkedHashMap<>(),
                new ConcurrentSkipListMap<>(),
                new ConcurrentHashMap<>()
        }) {
            System.out.print(map.getClass().getSimpleName() + ": ");
            map.computeIfAbsent(Key.Hello, s -> {
                map.computeIfAbsent(Key.Hello, t -> 1);
                return 2;
            });
            System.out.println(map);
        }
    }
 
    enum Key {Hello}
}

Метод compute () имеет аналогичные результаты

1
2
3
4
5
6
7
8
HashMap: {Hello=null2}
WeakHashMap: {Hello=null2}
TreeMap: {Hello=null2}
IdentityHashMap: {Hello=null2}
EnumMap: {Hello=null2}
Hashtable: {Hello=null2, Hello=1}
LinkedHashMap: {Hello=1, Hello=null2}
ConcurrentSkipListMap: {Hello=12}

ConcurrentHashMap:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class A {
    public static void main(String[] args) {
        for (Map map : new Map[]{
                new HashMap<>(),
                new WeakHashMap<>(),
                new TreeMap<>(),
                new IdentityHashMap<>(),
                new EnumMap<>(Key.class),
                new Hashtable<>(),
                new LinkedHashMap<>(),
                new ConcurrentSkipListMap<>(),
                new ConcurrentHashMap<>()
        }) {
            System.out.print(map.getClass().getSimpleName() + ": ");
            map.compute(Key.Hello, (s, v) -> {
                map.compute(Key.Hello, (s2, v2) -> "1");
                return v + "2";
            });
            System.out.println(map);
        }
    }
 
    enum Key {Hello}
}

Вывод

Вы должны быть осторожны, если вы вкладываете вызовы в карту из лямбды, или вообще избегаете этого. Если вам нужно сделать это, ConcurrentSkipListMap, кажется, ведет себя лучше всего.

Ссылка: Puzzler: вложенный computeIfAbsent от нашего партнера JCG Питера Лори в блоге Vanilla Java .