Статьи

Давайте потоковую карту в Java 8 с jOOλ

Я хотел найти простой способ для потоковой передачи карты в Java 8. Угадайте, что? Там нет!

Для удобства я бы ожидал следующий метод:

1
2
3
4
5
6
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">public interface Map<K, V> {</span> общедоступный интерфейс Map <K, V> {</span>
 
    <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">default Stream<Entry<K, V>> stream() {</span> поток по умолчанию <Entry <K, V >> stream () {</span>
        <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">return entrySet().stream();</span> return entrySet (). stream ();</span>
    <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">}</span> }</span>   
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">}</span> }</span>

Но такого метода нет. Вероятно, существует множество причин, по которым такой метод не должен существовать, например:

  • Нет «четкого» предпочтения для entrySet() который выбирается keySet() или values() в качестве источника
  • Map самом деле не коллекция. Это даже не Iterable
  • Это не было целью дизайна
  • ЭГ не хватило времени

Что ж, есть очень веская причина для того, чтобы Map был модернизирован для предоставления entrySet().stream() и, наконец, для реализации Iterable<Entry<K, V>> . И эта причина в том, что теперь у нас есть Map.forEach() :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">default void forEach(</span> по умолчанию void forEach (</span>
        <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">BiConsumer<?</span> BiConsumer <?</span> <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">super K, ?</span> супер к,?</span> <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">super V> action) {</span> супер V> действие) {</span>
    <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">Objects.requireNonNull(action);</span> Objects.requireNonNull (действие);</span>
    <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">for (Map.Entry<K, V> entry : entrySet()) {</span> для (Map.Entry <K, V> entry: entrySet ()) {</span>
        <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">K k;</span> К к;</span>
        <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">V v;</span> V v;</span>
        <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">try {</span> пытаться {</span>
            <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">k = entry.getKey();</span> k = entry.getKey ();</span>
            <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">v = entry.getValue();</span> v = entry.getValue ();</span>
        <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">} catch(IllegalStateException ise) {</span> } catch (IllegalStateException ise) {</span>
            <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">// this usually means the entry is no longer in the map.</span> // это обычно означает, что записи больше нет на карте.</span>
            <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">throw new ConcurrentModificationException(ise);</span> бросить новое ConcurrentModificationException (ise);</span>
        <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">}</span> }</span>
        <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">action.accept(k, v);</span> action.accept (k, v);</span>
    <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">}</span> }</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">}</span> }</span>

forEach() в этом случае принимает BiConsumer который действительно потребляет записи на карте. Если вы выполняете поиск по исходному коду JDK, на самом деле очень мало ссылок на тип BiConsumer за пределами Map.forEach() и, возможно, несколько методов CompletableFuture и несколько методов сбора потоков.

Таким образом, можно почти предположить, что BiConsumer был в значительной степени обусловлен потребностями этого метода forEach() , который был бы веским аргументом в пользу того, чтобы сделать Map.Entry более важным типом в API коллекций (мы бы предпочли тип Tuple2, курс).

Давайте продолжим эту линию мысли. Также есть Iterable.forEach() :

1
2
3
4
5
6
7
8
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">public interface Iterable<T> {</span> открытый интерфейс Iterable <T> {</span>
    <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">default void forEach(Consumer<? super T> action) {</span> по умолчанию void forEach (действие Consumer <? super T>) {</span>
        <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">Objects.requireNonNull(action);</span> Objects.requireNonNull (действие);</span>
        <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">for (T t : this) {</span> для (T t: это) {</span>
            <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">action.accept(t);</span> action.accept (т);</span>
        <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">}</span> }</span>
    <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">}</span> }</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">}</span> }</span>

И Map.forEach() и Iterable.forEach() интуитивно Map.forEach() итерацию «записей» соответствующей модели коллекции, хотя есть небольшая разница:

  • Iterable.forEach() ожидает, что Consumer принимает одно значение
  • Map.forEach() ожидает, что BiConsumer принимает два значения: ключ и значение ( НЕ Map.Entry !)

Подумайте об этом таким образом:

Это делает эти два метода несовместимыми в «смысле печатания утки», что делает эти два типа еще более разными

Облом!

Улучшение карты с помощью jOOλ

Мы находим это странным и нелогичным. forEach() действительно не единственный вариант использования обхода и преобразования Map . Мы хотели бы иметь Stream<Entry<K, V>> или, что еще лучше, Stream<Tuple2<T1, T2>> . Поэтому мы реализовали это в jOOλ , библиотеке, которую мы разработали для наших интеграционных тестов в jOOQ . С помощью jOOλ теперь вы можете обернуть Map в тип Seq («Seq» для последовательного потока, потока с множеством других функциональных возможностей):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">Map<Integer, String> map = new LinkedHashMap<>();</span> Map <Integer, String> map = new LinkedHashMap <> ();</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">map.put(1, "a");</span> map.put (1, "a");</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">map.put(2, "b");</span> map.put (2, "b");</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">map.put(3, "c");</span> map.put (3, "c");</span>
 
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">assertEquals(</span> assertEquals (</span>
  <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">Arrays.asList(</span> Arrays.asList (</span>
    <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">tuple(1, "a"),</span> кортеж (1, «а»),</span>
    <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">tuple(2, "b"),</span> кортеж (2, "b"),</span>
    <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">tuple(3, "c")</span> кортеж (3, "с")</span>
  <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">),</span> ),</span>
 
  <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">Seq.seq(map).toList()</span> Seq.seq (карта) .toList ()</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">);</span> );</span>

Что вы можете с этим сделать? Как насчет создания новой Map , замены ключей и значений за один раз:

01
02
03
04
05
06
07
08
09
10
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">System.out.println(</span> System.out.println (</span>
  <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">Seq.seq(map)</span> Seq.seq (карта)</span>
     <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">.map(Tuple2::swap)</span> .map (Tuple2 :: своп)</span>
     <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">.toMap(Tuple2::v1, Tuple2::v2)</span> .toMap (Tuple2 :: v1, Tuple2 :: v2)</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">);</span> );</span>
 
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">System.out.println(</span> System.out.println (</span>
  <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">Seq.seq(map)</span> Seq.seq (карта)</span>
     <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">.toMap(Tuple2::v2, Tuple2::v1)</span> .toMap (Tuple2 :: v2, Tuple2 :: v1)</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">);</span> );</span>

Оба из вышеперечисленного приведут к:

1
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">{a=1, b=2, c=3}</span> {a = 1, b = 2, c = 3}</span>

Для справки вот как поменять местами ключи и значения со стандартным API JDK:

1
2
3
4
5
6
7
8
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">System.out.println(</span> System.out.println (</span>
  <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">map.entrySet()</span> map.entrySet ()</span>
     <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">.stream()</span> .ручей()</span>
     <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">.collect(Collectors.toMap(</span> .collect (Collectors.toMap (</span>
         <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">Map.Entry::getValue,</span> Map.Entry :: ПолучитьЗначение,</span>
         <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">Map.Entry::getKey</span> Map.Entry :: GETKEY</span>
     <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">))</span> ))</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">);</span> );</span>

Это может быть сделано, но ежедневная многословность стандартного Java API затрудняет чтение / запись.

Ссылка: Давайте напишем карту в Java 8 с jOOλ от нашего партнера по JCG Лукаса Эдера из блога JAVA, SQL и JOOQ .