Статьи

Java 8 Friday Goodies: Улучшения карт

В  Data Geekery мы любим Java. И так как мы действительно  входим в свободный API jOOQ и запросы DSL , мы абсолютно взволнованы тем, что Java 8 принесет в нашу экосистему. Мы  пару раз писали о приятных вкусностях Java 8 , и теперь мы чувствуем, что пришло время начать новую серию блогов,…

Ява 8 Пятница

Каждую пятницу мы показываем вам пару замечательных новых функций Java 8 в виде учебника, которые используют лямбда-выражения, методы расширения и другие замечательные вещи. Вы найдете исходный код на GitHub .чирикать это

Java 8 Goodie: Улучшения карт

В предыдущих постах мы уже имели дело с парой новых функций Streams, например, при  сортировке . Большинство улучшений API действительно являются  частью нового Streams API . Но несколько хороших методов были добавлены java.util.List и, самое главное, к  java.util.Map. Если вы хотите получить краткий обзор дополнений к функциям, перейдите в JDK8 Javadoc и нажмите на новую вкладку «Методы по умолчанию»:

java.util.Map методы по умолчанию

java.util.Map методы по умолчанию

По причинам обратной совместимости все новые методы, добавленные в интерфейсы Java, фактически являются методами по умолчанию. Итак, у нас есть пара интересных новых дополнений!

методы compute ()

Часто мы выбираем значение с карты, производим на нем некоторые вычисления и помещаем его обратно в карту. Это может быть многословно и трудно понять правильно, если задействован параллелизм. С Java 8, мы можем пропускать  BiFunction к новому compute()computeIfAbsent()или  computeIfPresent() методам и имеем  Map осуществление обработку семантики замены значения.

В следующем примере показано, как это работает:

// We'll be using this simple map
// Unfortunately, still no map literals in Java 8..
Map<String, Integer> map = newHashMap<>();
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);
// Compute a new value for the existing key
System.out.println(map.compute("A",
    (k, v) -> v == null? 42: v + 41));
System.out.println(map);
// This will add a new (key, value) pair
System.out.println(map.compute("X",
    (k, v) -> v == null? 42: v + 41));
System.out.println(map);

Вывод вышеуказанной программы такой:

42
{A=42, B=2, C=3}
42
{A=42, B=2, C=3, X=42}

Это действительно полезно для  ConcurrentHashMap, который поставляется со следующей гарантией:

Весь вызов метода выполняется атомарно. Некоторые попытки обновления этой карты другими потоками могут быть заблокированы во время вычислений, поэтому вычисления должны быть короткими и простыми и не должны пытаться обновить какие-либо другие отображения этой Карты.

метод forEach ()

Это действительно хорошая новость, которая позволяет вам передавать ссылку на метод или лямбду для получения пар (ключ, значение) одну за другой. Тривиальный пример будет следующим:

map.forEach((k, v) ->
    System.out.println(k + "="+ v));

Его вывод:

A=1
B=2
C=3

метод слияния ()

Теперь этот действительно не так легко понять. Javadoc использует этот пример здесь:

map.merge(key, msg, String::concat)

Учитывая следующий договор:

Если указанный ключ еще не связан со значением или связан с нулем, связывает его с данным значением. В противном случае заменяет значение результатами данной функции переназначения или удаляет, если результат равен нулю.

Итак, приведенный выше код переводится в следующую элементарную операцию:

String value = map.get(key);
if(value == null)
    map.put(key, msg);
else
    map.put(key, value.concat(msg));

Это, конечно, не повседневная функциональность, и она могла просто просочиться из реализации в API верхнего уровня. Кроме того, если карта уже содержит  null (так что  null значения в порядке) и ваши  remappingFunction возвраты  null, то запись удаляется. Это довольно неожиданно. Рассмотрим следующую программу:

map.put("X", null);
System.out.println(map.merge(
    "X", null, (v1, v2) -> null));
System.out.println(map);

Его вывод:

null
{A=1, B=2, C=3}

Обновление: сначала я написал приведенный выше код в JDK 8, сборка 116. В сборке 129 все снова полностью изменилось. Во-первых, передаваемое значение  merge() не может быть  null. Во- вторых. null значения обрабатываются  merge() как отсутствующие значения. Для получения того же результата мы напишем:

map.put("X", 1);
System.out.println(map.merge(
    "X", 1, (v1, v2) -> null));
System.out.println(map);

Таким merge() образом, эта  операция удалила значение с карты. Это , вероятно , хорошо , потому что семантика «слияние» часто является комбинация INSERTUPDATEи  DELETE если мы используем SQL-Speak. И несколько разумный способ указать, что значение должно быть удалено, — это возврат null из такой функции.

Но карта может содержать  null значения, которые никогда не могут быть вставлены в карту с помощью  merge().чирикать это

getOrDefault ()

Это ежу понятно. Правильно? Правильно! Неправильно!

К сожалению, есть два типа карт. Те, кто поддерживает  null ключи и / или значения, и те, кто не поддерживает  nulls. Хотя предыдущий merge() метод не различал карту, не содержащую ключ, и карту, содержащую ключ со  null значением, этот новый  getOrDefault() возвращает значение по умолчанию, только если ключ не содержится. Это не защитит вас от  NullPointerException:

map.put("X", null);
try{
    System.out.println(map.getOrDefault("X", 21) + 21);
}
catch(NullPointerException nope) {
    nope.printStackTrace();
}

Это довольно облом. В общем, можно сказать, что API Map стал еще более сложным по отношению к нулям.чирикать это

Тривиальные дополнения

Есть еще несколько способов, как  putIfAbsent() (извлеченных из ConcurrentHashMapremove() (с ключом и аргументами значения), replace().

Вывод

В общем, можно сказать, что многие атомарные операции перешли на API-интерфейс верхнего уровня, что хорошо. Но опять же, ранее существовавшая путаница, связанная с семантикой  null в картах, углубилась. Термины «присутствует» или «отсутствует», «содержит», «по умолчанию» не обязательно помогают прояснить эти вещи, что на удивление противоречит правилам поддержания  API согласованным и, что наиболее важно, регулярным . Таким образом, в качестве потребителя этого API, в идеале, вы должны хранить  null карты как ключи, так и значения!

На следующей неделе в этой серии блогов мы рассмотрим, как Java 8 позволит вам очень легко определять локальную область транзакций, так что следите за обновлениями!