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

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

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

11 ответов

Решение

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

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

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

  • AbstractMap.SimpleEntry
  • AbstractMap.SimpleImmutableEntry

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

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

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

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

Похоже, что полный пример можно решить без использования какой-либо структуры 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] что я думаю, правильный результат, запрошенный ОП.

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

Vavr (ранее назывался 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 не имел простых кортежей до сих пор, для меня загадка. Написание классов-обёрток кажется повседневным делом.

Да.

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

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

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

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

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

     int[] value =  ...


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

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

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

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

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

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

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

Многие сторонние библиотеки поддерживают кортежи. Например, jOOλ, поддерживает кортежи от степени 0 к 16, например:

      // Assuming this static import
import static org.jooq.lambda.tuple.Tuple.*;

// Write:
var t = tuple(1, "a", 2L);
Integer i = t.v1;
String s = t.v2;
Long l = t.v3;

Другие библиотеки, в которых тоже есть кортежи, например:

Я не эксперт в 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));
}

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

Другие вопросы по тегам