Есть ли в Java SE 8 пары или кортежи?

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

Должен ли я по-прежнему страдать от этого: Что эквивалентно С++ Pair < 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]. Как я могу получить этот результат?

Ответ 1

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


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

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

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

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

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

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

  • (a, b)
  • (первый, второй)
  • (слева, справа)
  • (автомобиль, cdr)
  • (foo, bar)
  • и др.

Одна большая проблема, о которой почти не упоминалось, - это отношение Пары к примитивам. Если у вас есть привязка (int x, int y), которая представляет точку в 2D пространстве, представляя ее как 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, это было бы просто или даже упрощенно, и это не удовлетворило бы многие из вариантов использования, которые мы сейчас представляем. Учтите, что если бы пара была добавлена ​​в JDK 1.0, это, вероятно, было бы изменчивым! (Посмотрите на java.util.Date.) Разве люди были бы рады этому? Я предполагаю, что если бы на Java был класс Pair, он был бы добрым-не-полезным, и все все равно будут кататься самостоятельно, чтобы удовлетворить их потребности, были бы различные реализации Pair и Tuple во внешних библиотеках, и люди все равно будут спорить/обсуждать, как исправить класс Java Pair. Другими словами, вид в том же месте, в котором мы находимся сегодня.

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

Ответ 3

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

Ответ 4

Похоже, что полный пример может быть решён без использования какой-либо структуры пар. Ключ должен фильтровать индексы столбцов с предикатом, проверяющим весь столбец, вместо того, чтобы сопоставлять индексы столбцов с числом записей 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() вместо этого, чтобы выписать функции коллектора, которые сами делают бокс.

Ответ 5

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

     int[] value =  ...


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

Ответ 6

Да.

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

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

До тех пор мало пользы.

EDIT 2018-02-12: Во время работы над проектом я написал вспомогательный класс, который помогает обрабатывать особый случай наличия идентификатора ранее в потоке, который вам нужен в более позднее время, но часть потока между ними не знает об этом. Пока я не выпущу его самостоятельно, он доступен на IdValue.java с модульным тестом на IdValueTest.java

Ответ 7

Вавр (ранее называемый Javaslang) (http://www.vavr.io) предоставляет кортежи (с размером 8), как Что ж. Вот javadoc: 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 не пришел с простыми типами кортежей, и теперь для меня это тайна. Написание классов-оболочек - это ежедневный бизнес.

Ответ 8

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

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

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

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

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

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

Ответ 9

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

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

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

Ответ 10

Я не эксперт в Java, просто студент, но я сделал обходной путь для имитации кортежей, как в Python:

class C1 { 
    String name; int X,Y;
    C1(List l){name = ""+l.get(0); X=(int)l.get(1); Y=(int)l.get(2);}
}

class MyClass{
    List l2= List.of(List.of("a",7,9), List.of("b",8,4), List.of("c",3,6)); //here
    List l3=new ArrayList();
    for (Object li: l2) l3.add(new C1((List)li));
}

Надеюсь это поможет.