it-swarm.com.ru

Java SE 8 имеет пары или кортежи?

Я играю с ленивыми функциональными операциями в Java SE 8, и я хочу map индекс i для пары/Tuple (i, value[i]), затем filter, основанный на втором элементе value[i], и, наконец, вывести только индексы.

Должен ли я все еще страдать так: Что эквивалентно паре C++ <L, R> в Java? в смелую новую эпоху лямбд и потоков?

Обновление: Я представил довольно упрощенный пример, в котором есть аккуратное решение, предложенное @dkatzel в одном из ответов ниже. Тем не менее, это не обобщает. Поэтому позвольте мне добавить более общий пример:

package com.example.test;

import Java.util.ArrayList;
import Java.util.stream.IntStream;

public class Main {

  public static void main(String[] args) {
    boolean [][] directed_acyclic_graph = new boolean[][]{
        {false,  true, false,  true, false,  true},
        {false, false, false,  true, false,  true},
        {false, false, false,  true, false,  true},
        {false, false, false, false, false,  true},
        {false, false, false, false, false,  true},
        {false, false, false, false, false, false}
    };

    System.out.println(
        IntStream.range(0, directed_acyclic_graph.length)
        .parallel()
        .mapToLong(i -> IntStream.range(0, directed_acyclic_graph[i].length)
            .filter(j -> directed_acyclic_graph[j][i])
            .count()
        )
        .filter(n -> n == 0)
        .collect(() -> new ArrayList<Long>(), (c, e) -> c.add(e), (c1, c2) -> c1.addAll(c2))
    );
  }

}

Это дает неправильный вывод [0, 0, 0], который соответствует счетам для трех столбцов, которые все являются false. Что мне нужно, это индексы этих трех столбцов. Правильный вывод должен быть [0, 2, 4]. Как я могу получить этот результат?

163
necromancer

ОБНОВЛЕНИЕ: Этот ответ является ответом на исходный вопрос, Имеет ли Java SE 8 пары или Кортежи? (И неявным образом, если нет, почему бы и нет?) ОП обновил вопрос более полным примером, но, похоже, его можно решить без использования какой-либо структуры Pair. [Примечание от ОП: здесь другой правильный ответ .]


Краткий ответ: нет. Вы должны либо свернуть свою собственную, либо ввести одну из нескольких библиотек, которая ее реализует.

Наличие класса Pair в Java SE было предложено и отклонено как минимум один раз. Смотрите эту ветку обсуждения в одном из списков рассылки OpenJDK. Компромиссы не очевидны. С одной стороны, существует много реализаций Pair в других библиотеках и в коде приложения. Это демонстрирует необходимость, и добавление такого класса в Java SE увеличит повторное использование и совместное использование. С другой стороны, наличие класса Pair увеличивает соблазн создания сложных структур данных из пар и коллекций без создания необходимых типов и абстракций. (Это перефразировать сообщение Кевина Буриллиона из этой ветки.)

Я рекомендую всем прочитать всю эту электронную почту. Это удивительно проницательно и не имеет никакого пламени. Это довольно убедительно. Когда он начался, я подумал: "Да, в Java SE должен быть класс Pair", но к тому времени, когда поток достиг конца, я передумал.

Обратите внимание, что JavaFX имеет класс javafx.util.Pair . API-интерфейсы JavaFX развивались отдельно от API-интерфейсов Java SE.

Как можно видеть из связанного вопроса Что является эквивалентом пары C++ в Java? , существует довольно большое пространство разработки, окружающее, по-видимому, такой простой API. Должны ли объекты быть неизменными? Должны ли они быть сериализуемыми? Должны ли они быть сопоставимы? Класс должен быть окончательным или нет? Стоит ли заказывать два элемента? Должен ли это быть интерфейс или класс? Зачем останавливаться на парах? Почему не тройки, четверки или N-кортежи?

И, конечно же, существует неизбежная система именования элементов:

  • (а, б)
  • (первая секунда)
  • (лево право)
  • (автомобиль, CDR)
  • (фу, бар)
  • и т.п.

Одна большая проблема, которая едва упоминалась, - это отношение пар к примитивам. Если у вас есть элемент (int x, int y), представляющий точку в двухмерном пространстве, представляющий ее как Pair<Integer, Integer>, использует три объекта вместо двух 32-битных слов. Кроме того, эти объекты должны находиться в куче и подвергаться GC-нагрузке.

Казалось бы, ясно, что, как и в Streams, важно, чтобы существовали примитивные специализации для пар. Хотим ли мы увидеть:

Pair
ObjIntPair
ObjLongPair
ObjDoublePair
IntObjPair
IntIntPair
IntLongPair
IntDoublePair
LongObjPair
LongIntPair
LongLongPair
LongDoublePair
DoubleObjPair
DoubleIntPair
DoubleLongPair
DoubleDoublePair

Даже IntIntPair по-прежнему требует одного объекта в куче.

Это, конечно, напоминает распространение функциональных интерфейсов в пакете Java.util.function в Java SE 8. Если вы не хотите раздутого API, какие из них вы бы оставили? Вы также можете утверждать, что этого недостаточно, и что также следует добавить специализации, скажем, для Boolean.

Мне кажется, что если бы Java давно добавил класс Pair, он был бы простым или даже упрощенным, и он не удовлетворил бы многие варианты использования, которые мы сейчас планируем. Учтите, что если бы Pair был добавлен во временные рамки JDK 1.0, он, вероятно, был бы изменчивым! (Посмотрите на Java.util.Date.) Будут ли люди довольны этим? Я предполагаю, что если бы в Java существовал класс Pair, он был бы своего рода не очень полезным, и каждый по-прежнему будет крутить свои собственные, чтобы удовлетворить свои потребности, во внешних библиотеках были бы различные реализации Pair и Tuple, и люди все еще будут спорить/обсуждать, как исправить класс Pair в Java. Другими словами, вроде в том же месте, в котором мы находимся сегодня.

Между тем, продолжается работа по решению фундаментальной проблемы, которая заключается в лучшей поддержке в JVM (и в конечном итоге в языке Java) для типов значений . Смотрите этот Состояние ценностей документ. Это предварительная, умозрительная работа, и она охватывает только вопросы с точки зрения JVM, но за ней уже стоит немало идей. Конечно, нет никаких гарантий, что это попадет в Java 9 или когда-либо попадет куда-либо, но это действительно показывает текущее направление мышления по этой теме.

182
Stuart Marks

Вы можете взглянуть на эти встроенные классы:

32
senerh

К сожалению, в Java 8 не было пар или кортежей. Вы всегда можете использовать org.Apache.commons.lang3.Tuple конечно (что лично я использую в сочетании с Java 8) или вы можете создавать свои собственные оболочки. Или используйте Карты. Или что-то в этом роде, как объясняется в принятый ответ на вопрос, на который вы ссылаетесь.

17
blalasaadri

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

Код, который делает это здесь:

    System.out.println(
        IntStream.range(0, acyclic_graph.length)
            .filter(i -> IntStream.range(0, acyclic_graph.length)
                                  .noneMatch(j -> acyclic_graph[j][i]))
            .boxed()
            .collect(toList()));

Это приводит к выводу [0, 2, 4], который, я думаю, является правильным результатом, запрошенным OP.

Также обратите внимание на операцию boxed(), которая объединяет значения int в объекты Integer. Это позволяет использовать ранее существовавший коллектор toList() вместо того, чтобы выписывать коллекторные функции, которые сами выполняют упаковку.

14
Stuart Marks

Так как вы заботитесь только об индексах, вам вообще не нужно отображать кортежи. Почему бы просто не написать фильтр, который использует элементы поиска в вашем массиве?

     int[] value =  ...


IntStream.range(0, value.length)
            .filter(i -> value[i] > 30)  //or whatever filter you want
            .forEach(i -> System.out.println(i));
6
dkatzel

Да.

Map.Entry может использоваться как Pair.

К сожалению, это не помогает с потоками Java 8, так как проблема в том, что, хотя лямбда-выражения могут принимать несколько аргументов, язык Java позволяет возвращать только одно значение (объект или тип примитива) , Это подразумевает, что всякий раз, когда у вас есть поток, вы в конечном итоге получаете один объект из предыдущей операции. Это недостаток языка Java, потому что, если поддерживаются несколько возвращаемых значений И поддерживаются потоки, у нас могут быть гораздо более приятные нетривиальные задачи, выполняемые потоками.

До тех пор, есть только небольшая польза.

РЕДАКТИРОВАТЬ 2018-02-12: Работая над проектом, я написал вспомогательный класс, который помогает обрабатывать особый случай наличия идентификатора ранее в потоке, который вам нужен позже, но промежуточная часть потока не знает об этом. Пока я не смогу выпустить его самостоятельно, он доступен в IdValue.Java с модульным тестом в IdValueTest.Java

5
Thorbjørn Ravn Andersen

Vavr (ранее назывался Javaslang) ( http://www.vavr.io ) предоставляет кортежи (размером до 8) также. Вот этот код: https://static.javadoc.io/io.vavr/vavr/0.9.0/io/vavr/Tuple.html .

Это простой пример:

Tuple2<Integer, String> entry = Tuple.of(1, "A");

Integer key = entry._1;
String value = entry._2;

Почему сам JDK не имел простых кортежей до сих пор, для меня загадка. Написание классов-обёрток кажется повседневным делом.

2
wumpz

Eclipse Collections имеет Pair и все комбинации пар примитивов/объектов (для всех восьми примитивов).

Фабрика Tuples может создавать экземпляры Pair, а фабрика PrimitiveTuples может использоваться для создания всех комбинаций пар примитив/объект.

Мы добавили их до выпуска Java 8. Они были полезны для реализации итераторов ключ/значение для наших примитивных карт, которые мы также поддерживаем во всех комбинациях примитивов/объектов.

Если вы хотите добавить дополнительные накладные расходы на библиотеку, вы можете использовать принятое решение Стюарта и собрать результаты в примитив IntList, чтобы избежать упаковки. Мы добавили новые методы в Eclipse Collections 9. , чтобы можно было создавать коллекции Int/Long/Double из потоков Int/Long/Double.

IntList list = IntLists.mutable.withAll(intStream);

Примечание: я являюсь коммиттером для Eclipse Collections.

2
Donald Raab

Поскольку Java 9, вы можете создавать экземпляры Map.Entry проще, чем раньше:

Entry<Integer, String> pair = Map.entry(1, "a");

Map.entry возвращает неизменяемое Entry и запрещает нули.

1
ZhekaKozlov