У Java SE 8 є пари або кортежі?


185

Я граю з ледачими функціональними операціями в 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]. Як я можу отримати цей результат?


2
Там уже в AbstractMap.SimpleImmutableEntry<K,V>протягом багатьох років ... Але в будь-якому випадку, замість відображення , iщоб (i, value[i])тільки для фільтрації по value[i]і відображенню назад i: чому не тільки фільтр, value[i]в першу чергу, без відображення?
Холгер

@Holger Мені потрібно знати, які показники масиву містять значення, що відповідають критеріям. Я не можу це зробити без збереження iв потоці. Мені також потрібні value[i]критерії. Ось чому мені потрібно(i, value[i])
некромант

1
@necromancer Право, це працює лише в тому випадку, якщо дешево дістатись до значення з індексу, такого як масив, колекція випадкового доступу або недорога функція. Я думаю, проблема полягає в тому, що ви хотіли представити спрощений випадок використання, але це було надто спрощено і, таким чином, піддалося окремому випадку.
Стюарт Маркс

1
@necromancer Я трохи відредагував останній абзац, щоб уточнити питання, яке, на мою думку, ви задаєте. Це право? Також це питання про спрямований (не ациклічний) графік? (Не те, що це має велике значення.) Нарешті, чи повинен бути бажаний результат [0, 2, 4]?
Стюарт Маркс

1
Я вважаю, що правильне рішення для виправлення цього полягає в тому, щоб майбутні додаткові кортежі випуску Java випускати як тип повернення (як особливий випадок Object) і лямбда-вирази зможуть використовувати такий кортеж безпосередньо для його параметрів.
Thorbjørn Ravn Andersen

Відповіді:


206

ОНОВЛЕННЯ: Ця відповідь відповідає на оригінальне запитання, чи є у Java SE 8 пари чи кортежі? (І неявно, якщо ні, то чому б ні?) ОП оновило питання більш повним прикладом, але, здається, воно може бути вирішене без використання якоїсь структури пари. [Примітка від ОП: ось інша правильна відповідь .]


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

Навчання Pairв Java SE було запропоновано та відхилено хоча б один раз. Дивіться цю тему для обговорення в одному зі списків розсилки OpenJDK. Компроміси не очевидні. З одного боку, є багато реалізацій пари в інших бібліотеках та в коді програми. Це демонструє потребу, і додавання такого класу до Java SE збільшить повторне використання та обмін. З іншого боку, наявність класу Pair додає спокусі створити складні структури даних з пар та колекцій без створення необхідних типів та абстракцій. (Це парафраза повідомлення Кевіна Бурліона з цієї теми .)

Я рекомендую всім прочитати цілу нитку електронної пошти. Він надзвичайно проникливий і не має полум'я. Це досить переконливо. Коли це почалося, я подумав: "Так, у Java SE повинен бути клас пари", але до того моменту, як нитка досягла свого кінця, я передумав.

Зауважте, що JavaFX має клас javafx.util.Pair . API JavaFX еволюціонували окремо від API Java SE.

Як видно із пов'язаного питання Що таке еквівалент пари C ++ на Java? існує досить великий простір дизайну, що оточує те, що, мабуть, такий простий API. Чи повинні об'єкти бути незмінними? Чи повинні вони бути серійними? Чи повинні вони бути порівнянними? Чи повинен бути клас остаточним чи ні? Чи слід замовляти два елементи? Це повинен бути інтерфейс чи клас? Навіщо зупинятися на парах? Чому б не трійки, квадроцикли чи N-кортежі?

І звичайно, існує неминуче називання велосипедів для елементів:

  • (а, б)
  • (перша секунда)
  • (ліво право)
  • (машина, компакт-диск)
  • (колонтитул, бар)
  • тощо.

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

Здавалося б, зрозуміло, що, як і потоки, важливо, щоб існували примітивні спеціалізації для пар. Ми хочемо побачити:

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 давно додала клас пари, це було б просто або навіть спрощено, і це не задовольнило б багатьох випадків використання, які ми зараз передбачаємо. Подумайте, що якби пара була додана у часові рамки JDK 1.0, вона, ймовірно, була б зміною! (Погляньте на java.util.Date.) Чи були б люди із цим задоволені? Я здогадуюсь, що якби на Java існував клас «Пара», це було б якось не так-то не дуже корисно, і всі все ще будуть прокручувати свої власні потреби, щоб задовольнити свої потреби, у зовнішніх бібліотеках були б різні реалізації Pair і Tuple, і люди все ще сперечаються / обговорюють питання про те, як виправити клас Java Pair. Іншими словами, наче там, де ми сьогодні.

Тим часом триває деяка робота над вирішенням фундаментального питання, а саме - кращою підтримкою в JVM (і, зрештою, мовою Java) для типів значень . Дивіться цей документ про стан цінностей . Це попередня, спекулятивна робота, і вона охоплює лише питання з точки зору JVM, але вона вже має досить багато думок. Звичайно, немає гарантій, що це потрапить у Java 9 або коли-небудь потрапить куди завгодно, але це дійсно показує поточний напрямок мислення на цю тему.


3
@necromancer Фабричні методи з примітивами не допомагають Pair<T,U>. Оскільки дженерики повинні бути референтного типу. Будь-які примітиви будуть розміщені в коробці під час їх зберігання. Для зберігання примітивів вам справді потрібен інший клас.
Стюарт Маркс

3
@necromancer І так, в ретроспективі, примітивні конструктори, коробчасті в коробці, не повинні були бути загальнодоступними, і він valueOfповинен був бути єдиним способом отримати примірник із коробкою. Але вони існують з Java 1.0 і, мабуть, не варто намагатися змінити їх на даний момент.
Стюарт Маркс

3
Очевидно, що у фоновому режимі має бути лише одна публікація Pairчи Tupleклас із фабричним методом, що створює необхідні класи спеціалізації (з оптимізованим сховищем). Зрештою, лямбди роблять саме це: вони можуть захоплювати довільну кількість змінних довільного типу. А тепер зображіть мовну підтримку, що дозволяє створити відповідний клас кортежів під час виконання, запускається invokedynamicінструкцією…
Холгер,

3
@Holger Щось подібне може спрацювати, якби переобладнати типи значень на існуючий JVM, але пропозиція про типи цінностей (зараз "Project Valhalla" ) набагато радикальніша. Зокрема, типи її значень не обов'язково повинні розподілятися у купу. Крім того, на відміну від об'єктів сьогодні, як і примітивів сьогодні, значення не мали б тотожності.
Стюарт Маркс

2
@Stuart Marks: Це не завадить, оскільки тип, який я описав, може бути типом "boxed" для такого типу значень. З invokedynamicбазовим заводом, подібним до створення лямбда, таке переоснащення пізніше не буде проблемою. До речі, лямбди також не мають ідентичності. Як прямо сказано, особистість, яку ви могли б сприймати сьогодні, є артефактом поточної реалізації.
Холгер

46

Ви можете ознайомитися з цими вбудованими класами:


3
Це правильна відповідь, що стосується вбудованої функціональності для пар. Зауважте, що SimpleImmutableEntryлише гарантії того, що посилання, що зберігаються в Entry, не змінюються, не те, що поля пов'язаних keyта valueоб'єктів (або ті об'єкти, на які вони посилаються) не змінюються.
Люк Хатчісон

22

На жаль, 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]]

Насправді одна з відповідей @StuartMarks дозволила мені вирішити це без кортежів, але оскільки це, схоже, не узагальнено, мені, мабуть, знадобиться це врешті-решт.
некроман

@necromancer Так, це дуже хороша відповідь. Бібліотека apache все ще може стати в нагоді часом, але все зводиться до дизайну мови Javas. В основному, кортежі повинні були бути примітивними (або подібними), щоб працювати, як це робиться в інших мовах.
blalasaadri

1
Якщо ви цього не помітили, відповідь містила це надзвичайно інформативне посилання: cr.openjdk.java.net/~jrose/values/values-0.html про необхідність та перспективи таких примітивів, включаючи кортежі.
некроман

17

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


1
+1 туз у рукав :) Це все ще не узагальнює, правда? Це було більш суттєвим аспектом питання, тому що я очікую зіткнутися з іншими ситуаціями, коли така схема, як ця, не буде працювати (наприклад, стовпці з не більше ніж 3 значеннями true). Відповідно, я прийму вашу іншу відповідь як правильну, але також вкажу на цю! Велике спасибі :)
некромант

Це правильно, але приймаючи іншу відповідь того ж користувача. (див. коментарі вище та в інших місцях.)
некромант

1
@necromancer Правильно, ця методика не є цілком загальною у випадках, коли потрібен індекс, але елемент даних неможливо отримати або обчислити за допомогою індексу. (Принаймні, не легко.) Наприклад, розгляньте проблему, коли ви читаєте рядки тексту з мережевого з'єднання, і ви хочете знайти номер рядка N-го рядка, який відповідає деякому шаблону. Найпростіший спосіб - зіставити кожен рядок у пару чи якусь складову структуру даних для нумерації рядків. Мабуть, існує шалений, побічний спосіб зробити це без нової структури даних.
Стюарт Маркс

@StuartMarks, Пара <T, U>. потрійний <T, U, V>. тощо. Ваш приклад - це список, а не пара.
Печер'є

7

Vavr (раніше називався Javaslang) ( http://www.vavr.io ) також забезпечує кортежі (розмір til 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 сама не прийшла з простим видом кортежів дотепер, для мене загадка. Навчання занять обгорткою, здається, є щоденною справою.


Деякі версії vavr використовували підлі підкидання під капотом. Будьте обережні, щоб не використовувати їх.
Thorbjørn Ravn Andersen

7

Оскільки Java 9, ви можете створювати екземпляри Map.Entryпростіше, ніж раніше:

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

Map.entryповертає незмінювані Entryі забороняє нулі.


6

Оскільки ви дбаєте лише про індекси, вам взагалі не потрібно збиратися в кортежі. Чому б просто не написати фільтр, який використовує елементи пошуку у своєму масиві?

     int[] value =  ...


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

+1 для чудового, практичного рішення. Однак я не впевнений, чи це узагальнює мою ситуацію, коли я генерую цінність на льоту. Я поставив своє запитання як масив, щоб запропонувати простий випадок подумати, і ви придумали чудове рішення.
некроман

5

Так.

Map.Entryможе використовуватися як Pair.

На жаль, це не допомагає в потоках Java 8, оскільки проблема полягає в тому, що навіть незважаючи на те, що лямбдати можуть приймати кілька аргументів, мова Java дозволяє лише повернути одне значення (об'єкт або примітивний тип). Це означає, що щоразу, коли у вас є потік, вам в кінцевому підсумку передається один об'єкт з попередньої операції. Цього не вистачає в мові Java, тому що якби підтримано кілька повернених значень І потоки їх підтримували, ми могли б мати набагато приємніші нетривіальні завдання, виконані потоками.

До цього часу користі мало.

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


2

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

TuplesЗавод може створювати екземпляри Pair, і PrimitiveTuplesзавод може бути використаний для створення всіх комбінацій примітивного пара / об'єктів.

Ми додали їх до виходу Java 8. Вони були корисні для впровадження ітераторів ключа / значення для наших примітивних карт, які ми також підтримуємо у всіх примітивних / об'єктних поєднаннях.

Якщо ви готові додати додаткову накладну бібліотеку, ви можете використовувати прийняте рішення Stuart і збирати результати в примітив, IntListщоб уникнути боксу. Ми додали нові методи в Eclipse Collections 9.0, щоб дозволити Int/Long/Doubleстворювати колекції з Int/Long/Doubleпотоків.

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

Примітка. Я є членом колекції Eclipse.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.