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

Я играю с ленивыми функциональными операциями в Java SE 8 и хочу map индекс i для пары / кортежа (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]. Как я могу получить такой результат?


person necromancer    schedule 20.06.2014    source источник
comment
Уже много лет AbstractMap.SimpleImmutableEntry<K,V>… Но в любом случае, вместо сопоставления i с (i, value[i]) только для фильтрации по value[i] и обратного сопоставления с i: почему бы просто не отфильтровать по value[i] в первую очередь, без сопоставления?   -  person Holger    schedule 20.06.2014
comment
@Holger Мне нужно знать, какие индексы массива содержат значения, соответствующие критериям. Я не могу этого сделать, не сохранив i в потоке. Мне также нужно value[i] для критериев. Вот почему мне нужно (i, value[i])   -  person necromancer    schedule 21.06.2014
comment
@necromancer Помогает ли метод, описанный в ответе dkatzel?   -  person Stuart Marks    schedule 21.06.2014
comment
@StuartMarks да, это хорошая техника; Я прокомментировал это. Это не обобщает, потому что я фактически генерирую значения на лету с помощью операции карты во внешнем потоке. Я подумал, что буду полезен, представив упрощенный вариант использования, и, конечно же, dkatzel решил это хорошо :) Но я все еще застрял. У меня есть двумерная матрица, представляющая ориентированный граф, и я хочу найти, какие вершины имеют нулевые входящие ребра.   -  person necromancer    schedule 21.06.2014
comment
@necromancer Верно, это работает только в том случае, если дешево получить значение из индекса, такого как массив, коллекция с произвольным доступом или недорогая функция. Думаю, проблема в том, что вы хотели представить упрощенный вариант использования, но он был слишком упрощен и, таким образом, уступил место частному случаю.   -  person Stuart Marks    schedule 21.06.2014
comment
@necromancer Я немного отредактировал последний абзац, чтобы прояснить вопрос, который, как мне кажется, вы задаете. Это правильно? Кроме того, это вопрос о направленном (не ациклическом) графе? (Не то чтобы это имело большое значение.) Наконец, должен ли желаемый результат быть [0, 2, 4]?   -  person Stuart Marks    schedule 21.06.2014
comment
@StuartMarks благодарит за правки. Да, речь идет об ориентированном графе, и желаемый результат действительно должен быть [0, 2, 4] (сегодня я такой разбросанный мозг). Я отредактировал это соответствующим образом.   -  person necromancer    schedule 21.06.2014
comment
Я считаю, что правильное решение для исправления этого - иметь кортежи поддержки будущих выпусков Java в качестве возвращаемого типа (как особый случай Object) и иметь возможность использовать лямбда-выражения непосредственно для своих параметров.   -  person Thorbjørn Ravn Andersen    schedule 10.11.2016


Ответы (9)


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


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

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

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

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

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

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

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

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

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

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 во внешних библиотеках, и люди все еще спорили / обсуждали, как исправить класс Java Pair. Другими словами, примерно в том же месте, где мы находимся сегодня.

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

person Stuart Marks    schedule 20.06.2014
comment
Что касается примитивных специализаций, могут ли перегруженные фабричные методы вместо конструкторов решить проблему: Pair.valueOf(...)? касательная: почему Java не сделала конструктор для Integer закрытым? Я думаю, что Integer.valueOf - единственный способ получить доступ к Integer объектам? - person necromancer; 21.06.2014
comment
@necromancer Фабричные методы с примитивами не помогают Pair<T,U>. Поскольку дженерики должны быть ссылочного типа. Любые примитивы будут упакованы при хранении. Для хранения примитивов вам действительно нужен другой класс. - person Stuart Marks; 21.06.2014
comment
@necromancer И да, в ретроспективе примитивные конструкторы в штучной упаковке не должны были быть общедоступными, и valueOf должен был быть единственным способом получить экземпляр в штучной упаковке. Но они были там с Java 1.0, и, вероятно, не стоит пытаться менять на этом этапе. - person Stuart Marks; 21.06.2014
comment
Спасибо за понимание. Я надеюсь, что предложение «Состояние ценностей» продвигается вперед; пора! (также там есть ссылки на Джеймса Гослинга по теме эффективных классов). - person necromancer; 21.06.2014
comment
Очевидно, что должен быть только один общедоступный Pair или Tuple класс с фабричным методом, создающим необходимые классы специализации (с оптимизированным хранилищем) прозрачно в фоновом режиме. В конце концов, лямбды делают именно это: они могут захватывать произвольное количество переменных произвольного типа. А теперь представьте языковую поддержку, позволяющую создать соответствующий класс кортежа во время выполнения, инициированный инструкцией invokedynamic - person Holger; 23.06.2014
comment
@Holger Что-то подобное могло бы сработать, если бы можно было модифицировать типы значений на существующей JVM, но предложение типов значений (теперь Project Valhalla) намного радикальнее. В частности, его типы значений не обязательно будут размещаться в куче. Кроме того, в отличие от современных объектов и современных примитивов, значения не будут иметь идентичности. - person Stuart Marks; 23.06.2014
comment
@Stuart Marks: это не помешает, поскольку описанный мной тип может быть «упакованным» типом для такого типа значения. С фабрикой на основе invokedynamic, подобной созданию лямбда, такая последующая модернизация не будет проблемой. Кстати, лямбды тоже не идентичны. Как явно указано, идентичность, которую вы можете воспринимать сегодня, является артефактом текущей реализации. - person Holger; 24.06.2014
comment
›Зачем останавливаться на парах? Почему не тройки, квадраты или N-кортежи? Это должен быть аргумент против кортежей? Потому что N-кортежи были бы действительно полезны. - person weberc2; 16.01.2016
comment
У нас есть Map.Entry, который всегда можно использовать, когда Pair понадобится. Поскольку это случается редко, это указывает на то, что объект недостаточно востребован, чтобы оправдать использование для этого существующего класса. - person Thorbjørn Ravn Andersen; 10.11.2016
comment
Тот факт, что присутствие Pair поощряет плохое поведение программистов, не означает, что их отсутствие способствует лучшему поведению программистов: вместо того, чтобы возвращать Pair ‹A, B›, разработчики обращаются к Object [], к List ‹Object›, к Map.Entry ‹A, B ›, Их собственной непродуманной реализации или одной из многих других нестандартных реализаций. Все это хуже, чем стандартизированная библиотека кортежей, поэтому другие хорошо спроектированные языки включают библиотеки кортежей. - person drew; 31.01.2017
comment
И Scala, и Spark (даже Java Spark API) показывают, насколько мощной может быть концепция пары или кортежа. Дело не в том, чтобы это было упрощенной абстракцией ваших потребностей в данных, а в том, чтобы API Stream / RDD поверх него были обобщены на основе этих пар, особенно таких вещей, как groupBy reduceBy. RDD<Pair<K,V>> - ›groupByKey -› RDD<Pair<K,List<V>>. То, что можно имитировать в Java 8, но с большими затратами на реализацию. Во всех наших потоках Java 8 отсутствует необходимая обработка K / V. - person YoYo; 26.10.2018
comment
@StuartMarks, мы снова обсуждаем, как исправить Java; Но это данность. должна ли Java перестать что-либо поставлять? - person Pacerier; 16.05.2020

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

person senerh    schedule 20.06.2017
comment
Это правильный ответ, что касается встроенного функционала для пар. Обратите внимание, что SimpleImmutableEntry гарантирует только то, что ссылки, хранящиеся в Entry, не изменяются, а не то, что поля связанных объектов key и value (или поля объектов, на которые они ссылаются) не изменяются. - person Luke Hutchison; 03.01.2018

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


ОБНОВЛЕНИЕ: JDK 14 представляет записи в качестве функции предварительного просмотра. Это не кортежи, но их можно использовать для решения многих из тех же проблем. В вашем конкретном примере сверху это может выглядеть примерно так:

public class Jdk14Example {
    record CountForIndex(int index, long count) {}

    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()
                        .mapToObj(i -> {
                            long count = IntStream.range(0, directed_acyclic_graph[i].length)
                                            .filter(j -> directed_acyclic_graph[j][i])
                                            .count();
                            return new CountForIndex(i, count);
                        }
                        )
                        .filter(n -> n.count == 0)
                        .collect(() -> new ArrayList<CountForIndex>(), (c, e) -> c.add(e), (c1, c2) -> c1.addAll(c2))
        );
    }
}

При компиляции и запуске с JDK 14 (на момент написания это сборка раннего доступа) с использованием --enable-preview, вы получите следующий результат:

[CountForIndex[index=0, count=0], CountForIndex[index=2, count=0], CountForIndex[index=4, count=0]]
person blalasaadri    schedule 20.06.2014
comment
На самом деле один из ответов @StuartMarks позволил мне решить его без кортежей, но, поскольку он не кажется обобщающим, мне, вероятно, он понадобится в конечном итоге. - person necromancer; 21.06.2014
comment
@necromancer Да, это очень хороший ответ. Библиотека apache иногда может пригодиться, но все сводится к дизайну языка Javas. По сути, кортежи должны быть примитивами (или подобными), чтобы работать так же, как на других языках. - person blalasaadri; 21.06.2014
comment
Если вы этого не заметили, ответ содержал чрезвычайно информативную ссылку: cr.openjdk.java.net/~jrose/values/values-0.html о необходимости и перспективах таких примитивов, включая кортежи. - person necromancer; 21.06.2014

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

person Stuart Marks    schedule 21.06.2014
comment
+1 туз в рукаве :) Это все равно не обобщает, правда? Это был более существенный аспект вопроса, потому что я ожидаю столкнуться с другими ситуациями, когда такая схема не будет работать (например, столбцы с не более чем 3 значениями true). Соответственно, я приму второй ваш ответ как правильный, но укажу и на этот! Большое спасибо :) - person necromancer; 21.06.2014
comment
Это правильно, но я принимаю другой ответ того же пользователя. (см. комментарии выше и в другом месте.) - person necromancer; 21.06.2014
comment
@necromancer Правильно, этот метод не является полностью универсальным в тех случаях, когда вам нужен индекс, но элемент данных не может быть получен или вычислен с использованием индекса. (По крайней мере, не легко.) Например, рассмотрим проблему, когда вы читаете строки текста из сетевого подключения и хотите найти номер строки N-й строки, которая соответствует некоторому шаблону. Самый простой способ - отобразить каждую строку в пару или какую-либо составную структуру данных для нумерации строк. Однако, вероятно, есть хакерский побочный способ сделать это без новой структуры данных. - person Stuart Marks; 22.06.2014
comment
@StuartMarks, пара - ‹T, U›. тройка ‹T, U, V›. и т.д. Ваш пример - список, а не пара. - person Pacerier; 16.05.2020

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

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

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

person ZhekaKozlov    schedule 15.06.2019

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

person wumpz    schedule 28.07.2017
comment
В некоторых версиях вавра использовались хитрые броски под капотом. Будьте осторожны, не используйте их. - person Thorbjørn Ravn Andersen; 12.02.2018

да.

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

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

А пока от этого мало пользы.

РЕДАКТИРОВАТЬ 2021-05-10: Java 16 принесла записи, что является очень хорошим решением этой и других проблем. Очень веская причина для таргетинга на Java 17 LTS в ближайшее время

person Thorbjørn Ravn Andersen    schedule 03.01.2017

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

     int[] value =  ...


IntStream.range(0, value.length)
            .filter(i -> value[i] > 30)  //or whatever filter you want
            .forEach(i -> System.out.println(i));
person dkatzel    schedule 20.06.2014
comment
+1 за отличное практическое решение. Однако я не уверен, распространяется ли это на мою ситуацию, когда я генерирую значения на лету. Я сформулировал свой вопрос как массив, чтобы предложить простой случай для размышления, и вы пришли к отличному решению. - person necromancer; 21.06.2014

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

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

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

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

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

Примечание: я являюсь приверженцем коллекций Eclipse.

person Donald Raab    schedule 27.11.2017