Статьи

Удобные новые методы по умолчанию для карт в JDK 8

Интерфейс Map предоставляет несколько удобных новых методов в JDK 8 . Поскольку методы Map я расскажу в этом посте, реализованы как методы по умолчанию , все существующие реализации интерфейса Map имеют поведение по умолчанию, определенное в методах по умолчанию, без какого-либо нового кода. В JDK 8 представлены методы Map , описанные в этом посте: getOrDefault (Object, V) , putIfAbsent (K, V) , remove (Object, Object), remove (Object, Object) , replace (K, V) и replace (K). , V, V) .

Пример карты для демонстраций

Я буду использовать объявленную и инициализированную Map как показано в следующем коде, во всех примерах в этом посте. Поле statesAndCapitals является статическим полем на уровне класса. Я специально включил небольшое подмножество из пятидесяти штатов в Соединенных Штатах для ясности чтения и для упрощения демонстрации некоторых из новых методов JDK 8 Map умолчанию.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
private final static Map statesAndCapitals;
 
   static
   {
      statesAndCapitals = new HashMap<>();
      statesAndCapitals.put("Alaska", "Anchorage");
      statesAndCapitals.put("California", "Sacramento");
      statesAndCapitals.put("Colorado", "Denver");
      statesAndCapitals.put("Florida", "Tallahassee");
      statesAndCapitals.put("Nevada", "Las Vegas");
      statesAndCapitals.put("New Mexico", "Sante Fe");
      statesAndCapitals.put("Utah", "Salt Lake City");
      statesAndCapitals.put("Wyoming", "Cheyenne");
   }

Map.getOrDefault (Объект, V)

Новый метод Map getOrDefault (Object, V) позволяет вызывающей стороне указывать в одном операторе значение карты, соответствующее предоставленному ключу, или возвращать предоставленное «значение по умолчанию», если не найдено совпадение для предоставленного ключ.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
/*
 * Demonstrate Map.getOrDefault and compare to pre-JDK 8 approach. The JDK 8
 * addition of Map.getOrDefault requires fewer lines of code than the
 * traditional approach and allows the returned value to be assigned to a
 * "final" variable.
 */
 
// pre-JDK 8 approach
String capitalGeorgia = statesAndCapitals.get("Georgia");
if (capitalGeorgia == null)
{
   capitalGeorgia = "Unknown";
}
 
// JDK 8 approach
final String capitalWisconsin = statesAndCapitals.getOrDefault("Wisconsin", "Unknown");

Класс Apache Commons DefaultsMap обеспечивает функциональность, аналогичную новому Map.getOrDefault(Object, V) . Groovy GDK включает в себя аналогичный метод для Groovy, Map.get (Object, Object) , но его поведение немного отличается, поскольку он не только возвращает предоставленное значение по умолчанию, если «ключ» не найден, но также добавляет ключ с значение по умолчанию для базовой карты.

Map.putIfAbsent (K, V)

Map новом методе карты putIfAbsent (K, V) Javadoc объявляет эквивалент реализации по умолчанию:

1
2
3
4
5
6
7
The default implementation is equivalent to, for this map:
 
 V v = map.get(key);
 if (v == null)
     v = map.put(key, value);
 
 return v;

Это иллюстрируется другим примером кода, который сравнивает подход, предшествующий JDK 8, с подходом JDK 8.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
/*
 * Demonstrate Map.putIfAbsent and compare to pre-JDK 8 approach. The JDK 8
 * addition of Map.putIfAbsent requires fewer lines of code than the
 * traditional approach and allows the returned value to be assigned to a
 * "final" variable.
 */
 
// pre-JDK 8 approach
String capitalMississippi = statesAndCapitals.get("Mississippi");
if (capitalMississippi == null)
{
   capitalMississippi = statesAndCapitals.put("Mississippi", "Jackson");
}
 
// JDK 8 approach
final String capitalNewYork = statesAndCapitals.putIfAbsent("New York", "Albany");

Альтернативные решения в пространстве Java до добавления этого метода putIfAbsent обсуждаются в потоке StackOverflow Java map.get (ключ) — автоматически делать put (ключ) и возвращать, если ключ не существует? , Стоит отметить, что до JDK 8 интерфейс ConcurrentMap (расширяет Map ) уже предоставлял метод putIfAbsent (K, V) .

Map.remove (Объект, Объект)

Новый метод удаления карты (Object, Object) Map выходит за рамки давно доступного метода Map.remove (Object) для удаления записи карты только в том случае, если как предоставленный ключ, так и предоставленное значение соответствуют записи в карте (только ранее доступная версия) искал «ключевой» матч для удаления).

Комментарий Javadoc для этого метода объясняет, как работает реализация метода по умолчанию в терминах эквивалентного кода до JDK 8:
Реализация по умолчанию эквивалентна, для этой карты:

1
2
3
4
5
if (map.containsKey(key) && Objects.equals(map.get(key), value)) {
     map.remove(key);
     return true;
 } else
     return false;

Конкретное сравнение нового подхода к подходу до JDK 8 показано в следующем листинге кода.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
/*
 * Demonstrate Map.remove(Object, Object) and compare to pre-JDK 8 approach.
 * The JDK 8 addition of Map.remove(Object, Object) requires fewer lines of
 * code than the traditional approach and allows the returned value to be
 * assigned to a "final" variable.
 */
 
// pre-JDK 8 approach
boolean removed = false;
if (   statesAndCapitals.containsKey("New Mexico")
    && Objects.equals(statesAndCapitals.get("New Mexico"), "Sante Fe"))
{
   statesAndCapitals.remove("New Mexico", "Sante Fe");
   removed = true;
}
 
// JDK 8 approach
final boolean removedJdk8 = statesAndCapitals.remove("California", "Sacramento");

Map.replace (K, V)

Первый из двух новых методов Map «replace» устанавливает соответствие указанного значения указанному ключу, только если указанный ключ уже существует с некоторым отображенным значением. Комментарий Javadoc объясняет Java-эквивалент реализации этого метода по умолчанию:
Реализация по умолчанию эквивалентна, для этой карты:

1
2
3
4
if (map.containsKey(key)) {
     return map.put(key, value);
 } else
     return null;

Сравнение этого нового подхода с подходом до JDK 8 показано далее.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
/*
 * Demonstrate Map.replace(K, V) and compare to pre-JDK 8 approach. The JDK 8
 * addition of replace(K, V) requires fewer lines of code than the traditional
 * approach and allows the returned value to be assigned to a "final"
 * variable.
 */
 
// pre-JDK 8 approach
String replacedCapitalCity;
if (statesAndCapitals.containsKey("Alaska"))
{
   replacedCapitalCity = statesAndCapitals.put("Alaska", "Juneau");
}
 
// JDK 8 approach
final String replacedJdk8City = statesAndCapitals.replace("Alaska", "Juneau");

Map.replace (K, V, V)

Второй недавно добавленный метод «замены» Map более узок в своей интерпретации того, какие существующие значения заменяются. В то время как только что описанный метод заменяет любое значение в значении, доступном для указанного ключа в отображении, этот метод «замены», который принимает дополнительный ( третий ) аргумент, заменит только значение сопоставленной записи, которая имеет как соответствующий ключ, так и совпадающее значение Комментарий Javadoc показывает реализацию метода по умолчанию:

1
2
3
4
5
6
7
The default implementation is equivalent to, for this map:
 
 if (map.containsKey(key) && Objects.equals(map.get(key), value)) {
     map.put(key, newValue);
     return true;
 } else
     return false;

Мое сравнение этого подхода с подходом до JDK 8 показано в следующем листинге кода.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
/*
 * Demonstrate Map.replace(K, V, V) and compare to pre-JDK 8 approach. The
 * JDK 8 addition of replace(K, V, V) requires fewer lines of code than the
 * traditional approach and allows the returned value to be assigned to a
 * "final" variable.
 */
 
// pre-JDK 8 approach
boolean replaced = false;
if (   statesAndCapitals.containsKey("Nevada")
    && Objects.equals(statesAndCapitals.get("Nevada"), "Las Vegas"))
{
   statesAndCapitals.put("Nevada", "Carson City");
   replaced = true;
}
 
// JDK 8 approach
final boolean replacedJdk8 = statesAndCapitals.replace("Nevada", "Las Vegas", "Carson City");

Наблюдения и Выводы

Из этого поста можно сделать несколько замечаний.

  • Методы Javadoc для этих новых методов JDK 8 Map очень полезны, особенно с точки зрения описания поведения новых методов с точки зрения кода, предшествующего JDK 8. Я обсуждал Javadoc этих методов в более общем обсуждении документации JDK 8 на основе Javadoc API .
  • Как указывает эквивалентный Java-код в комментариях Javadoc этих методов, эти новые методы обычно не проверяют наличие нуля перед доступом к ключам и значениям карты. Следовательно, при использовании этих методов можно ожидать те же проблемы с нулями, что и при использовании «эквивалентного» кода, как показано в комментариях Javadoc. Фактически, комментарии Javadoc обычно предупреждают о потенциальной возможности исключения NullPointerException и проблемах, связанных с некоторыми реализациями Map допускающими нулевое значение, а некоторые — не для ключей и значений.
  • Новые методы Map обсуждаемые в этом посте, являются «методами по умолчанию», то есть реализации Map «наследуют» эти реализации автоматически.
  • Новые методы Map обсуждаемые в этом посте, позволяют создавать более чистый и лаконичный код В большинстве моих примеров они позволили преобразовать клиентский код из нескольких операторов, влияющих на состояние, в один оператор, который может установить локальную переменную раз и навсегда.

Новые методы Map описанные в этом посте, не являются новаторскими или потрясающими, но они являются удобством, для которого многие разработчики Java ранее реализовали более подробный код, написали свои собственные аналогичные методы или использовали стороннюю библиотеку. JDK 8 переносит эти стандартизированные методы в массы Java без необходимости пользовательской реализации или сторонних сред. Поскольку методы по умолчанию являются механизмом реализации, даже реализации Map которые появились довольно давно, внезапно и автоматически получают доступ к этим новым методам без каких-либо изменений кода в реализациях.