Java SE 8にはペアまたはタプルがありますか?


185

Java SE 8で遅延関数操作をいじってmapいます。iペア/タプルへのインデックスを作成し(i, value[i])、次にfilter2番目のvalue[i]要素に基づいて、最後にインデックスのみを出力します。

私はまだこれに苦しむ必要があります:JavaのC ++ Pair <L、R>に相当するものは何ですか?ラムダとストリームの大胆な新時代に?

更新:かなり単純化した例を紹介しました。これには、@ 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))
    );
  }

}

これにより、すべての3つの列のカウントに対応する誤った出力が生成されます。私が必要なのは、これらの3つの列のインデックスです。正しい出力はです。どうすればこの結果を得ることができますか?[0, 0, 0]false[0, 2, 4]


2
そこすでにますAbstractMap.SimpleImmutableEntry<K,V>代わりに、マッピングの、とにかく年のため...しかしi(i, value[i])だけでフィルタリングするためvalue[i]にマッピングバックiによって理由だけではないフィルタvalue[i]マッピングせずに、最初の場所で?
Holger、

@Holger配列のどのインデックスが基準に一致する値を含むかを知る必要があります。iストリームで保存せずにそれを行うことはできません。value[i]基準も必要です。それが私が必要な理由です(i, value[i])
ネクロマンサー14年

1
@necromancerそうですね、配列、ランダムアクセスコレクション、または安価な関数など、インデックスから値を取得するのが安価な場合にのみ機能します。問題は、単純化されたユースケースを提示したかったのですが、それは非常に単純化されていたため、特別なケースに屈しました。
スチュアートマークス

1
@necromancer最後の段落を少し編集して、あなたが尋ねていると思う質問を明確にしました。正しいですか?また、これは有向(非循環)グラフに関する質問ですか?(それほど重要ではありません。)最後に、必要な出力は[0, 2, 4]
スチュアートマークス

1
これを修正する適切な解決策は、将来のJavaリリースでタプルを(オブジェクトの特殊なケースとして)戻り値の型としてサポートし、ラムダ式でそのタプルをパラメーターに直接使用できるようにすることです。
–ThorbjørnRavn Andersen 2016

回答:


206

更新:この回答は元の質問に対する回答です。JavaSE 8にはペアまたはタプルがありますか?(そして、そうでない場合は暗黙的に、なぜそうでないのですか?)OPは質問をより完全な例で更新しましたが、ペアの構造を使用しなくても解決できるようです。[OPからのメモ:他の正解はこちらです。]


短い答えはノーです。自分でロールするか、それを実装するいくつかのライブラリの1つを取り込む必要があります。

PairJava SEにクラスを持つことが提案され、少なくとも1回は拒否されました。OpenJDKメーリングリストの1つでこのディスカッションスレッドを参照してください。トレードオフは明白ではありません。一方で、他のライブラリやアプリケーションコードには、多くのPair実装があります。これは必要性を示しており、そのようなクラスをJava SEに追加すると、再利用と共有が増加します。一方、Pairクラスを使用すると、必要な型と抽象化を作成せずに、Pairとコレクションから複雑なデータ構造を作成するという誘惑が高まります。(これは、そのスレッドからのケビンボリリオンのメッセージの言い換えです。)

私は誰もがそのメールスレッド全体を読むことをお勧めします。それは非常に洞察に富んでおり、飾り気はありません。それはかなり説得力があります。それが始まったとき、「うん、Java SEにはPairクラスがあるはずだ」と思ったのですが、スレッドが最後に到達する頃には、気が変わっていました。

ただし、JavaFXにはjavafx.util.Pairクラスがあることに注意してください。JavaFXのAPIは、Java SE APIとは別に進化しました。

リンクされた質問からわかるように、JavaのC ++ペアに相当するものは何ですか?明らかにそのような単純なAPIを取り巻く非常に大きな設計スペースがあります。オブジェクトは不変でなければなりませんか?それらはシリアライズ可能である必要がありますか?それらは比較可能でしょうか?クラスは最終的なものかどうか。2つの要素を注文する必要がありますか?それはインターフェースかクラスか?なぜペアで停止するのですか?なぜトリプル、クワッド、またはNタプルではないのですか?

そしてもちろん、要素には必然的に名前のバイクシェッドがあります:

  • (a、b)
  • (1番目、2番目)
  • (左右)
  • (車、CDR)
  • (フー、バー)

ほとんど言及されていない1つの大きな問題は、ペアとプリミティブの関係です。(int x, int y)2Dスペース内のポイントを表すデータがある場合、これを表すと、2つの32ビットワードではなく3つのオブジェクトがPair<Integer, Integer>消費されます。さらに、これらのオブジェクトはヒープ上に存在する必要があり、GCオーバーヘッドが発生します。

Streamsと同様に、Pairの原始的な特殊化が不可欠であることは明らかだと思われます。見たいですか:

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

でも、IntIntPairヒープ上に1つのオブジェクトが必要です。

もちろん、これらはjava.util.functionJava SE 8 でのパッケージ内の機能的なインターフェースの急増を思い起こさせます。肥大化したAPIが必要ない場合は、どれを除外しますか?また、これだけでは十分ではなく、たとえばの専門分野Booleanも追加する必要があると主張することもできます。

私が感じたのは、JavaがPairクラスをずっと前に追加していたとしたら、それは単純であるか、または単純化されさえしていたでしょう。ペアがJDK 1.0の時間枠に追加されていた場合、おそらく変更可能だったと考えてください。(java.util.Dateをご覧ください。)人々はそれに満足していましたか?私の推測では、JavaにPairクラスがあった場合、それはちょっと並べ替えはそれほど役に立ちませんし、誰もが自分のニーズを満たすために独自にロールし続け、外部ライブラリにさまざまなPairおよびTuple実装があるでしょう。そして人々はまだJavaのPairクラスをどのように修正するかについて議論/議論しているでしょう。つまり、今日と同じ場所です。

一方、根本的な問題に対処するためにいくつかの作業が行われています。これは、JVM(および最終的にはJava言語)での値型のサポートの向上です。このState of the Valuesドキュメント参照してください。これは予備的な投機的な作業であり、JVMの観点からの問題のみをカバーしていますが、その背後にはすでにかなりの考えがあります。もちろん、これがJava 9に入るという保証はなく、どこにでも入るという保証はありませんが、このトピックに関する現在の考え方を示しています。


3
プリミティブを含む@necromancer Factoryメソッドは、には役立ちませんPair<T,U>。ジェネリックスは参照型でなければならないため。プリミティブは保存時にボックス化されます。プリミティブを保存するには、本当に別のクラスが必要です。
スチュアートマークス2014年

3
@necromancerそして、振り返ってみると、ボックス化されたプリミティブコンストラクターは公開されるべきではなくvalueOf、ボックス化されたインスタンスを取得する唯一の方法であったはずです。しかし、それらはJava 1.0以降に存在しており、おそらくこの時点で変更を試みる価値はありません。
スチュアートマークス2014年

3
明らかに、バックグラウンドで透過的に必要な特殊化クラス(最適化されたストレージ)を作成するファクトリメソッドを持つpublic またはクラスは1つだけです。結局、ラムダはそれを正確に行います:彼らは任意の型の任意の数の変数をキャプチャするかもしれません。トリガ実行時に適切なタプルのクラスを作成することを可能にすると今の画像言語サポート命令...PairTupleinvokedynamic
ホルガー

3
@Holger値タイプを既存のJVMに後付けした場合、そのようなことが機能する可能性がありますが、値タイプの提案(現在は「プロジェクトヴァルハラ」)ははるかに根本的です。特に、その値型は必ずしもヒープに割り当てられるとは限りません。また、現在のオブジェクトとは異なり、現在のプリミティブと同様に、値にはアイデンティティがありません。
スチュアートマークス2014年

2
@Stuart Marks:私が説明したタイプは、そのような値タイプの「ボックス」タイプである可能性があるため、それは干渉しません。invokedynamicラムダの作成に似たベースのファクトリーでは、このような後からの改造は問題ありません。ちなみに、ラムダにもアイデンティティはありません。明示的に述べたように、今日あなたが認識するかもしれないアイデンティティは、現在の実装の成果物です。
Holger

46

これらの組み込みクラスを確認できます。


3
ペアの組み込み機能に関しては、これが正解です。にSimpleImmutableEntry格納されている参照がEntry変更されないことを保証するだけであり、リンクさkeyれたvalueオブジェクトとオブジェクト(またはそれらがリンクするオブジェクトのフィールド)のフィールドが変更されないことを保証します。
ルークハッチソン2018年

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の回答の1つにより、タプルなしで解決できましたが、一般化されていないようなので、おそらく最終的には必要になるでしょう。
ネクロマンサー2014年

@necromancerはい、それは非常に良い答えです。apacheライブラリは依然として便利な場合がありますが、すべてJava言語の設計にかかっています。基本的に、タプルは他の言語と同じように機能するためには、プリミティブ(または類似)でなければなりません。
blalasaadri 14年

1
あなたがそれに気づかなかった場合のために、答えはこの非常に有益なリンクを含んでいました:タプルを含むそのようなプリミティブの必要性と展望についてのcr.openjdk.java.net/~jrose/values/values-0.html
ネクロマンサー2014年

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]により、OPから要求された正しい結果が出力されると思います。

値をオブジェクトにboxed()ボックス化する操作にも注意してください。これにより、ボクシング自体を行うコレクター関数を作成する代わりに、既存のコレクターを使用できます。intIntegertoList()


1
+1あなたの袖をエース:)これはまだ一般化していませんよね?このようなスキームが機能しない他の状況(たとえば、3つ以下の値を持つ列true)に直面することが予想されるため、それが質問のより重要な側面でした。したがって、私はあなたの他の答えを正しいものとして受け入れますが、これも指摘します!どうもありがとう:)
ネクロマンサー

これは正しいですが、同じユーザーによる他の回答を受け入れます。(上記および他の場所のコメントを参照してください。)
ネクロマンサー2014年

1
@necromancerそうですね、この手法は、インデックスが必要な場合には完全には一般的ではありませんが、データ要素をインデックスを使用して取得または計算することはできません。(少なくとも簡単にはできません。)たとえば、ネットワーク接続からテキストの行を読み取っていて、あるパターンに一致するN番目の行の行番号を見つけたいという問題を考えます。最も簡単な方法は、各行をペアまたはいくつかの複合データ構造にマップして、行に番号を付けることです。新しいデータ構造なしでこれを行うには、おそらくハックで副作用のある方法があるでしょう。
スチュアートマークス

@StuartMarks、ペアは<T、U>です。トリプル<T、U、V>。など。あなたの例はリストではなく、ペアです。
Pacerier

7

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自体に単純なタプルが付属していなかった理由は、私には謎です。ラッパークラスを書くことは日常業務のようです。


vavrの一部のバージョンは、内部で卑劣なスローを使用していました。それらを使用しないように注意してください。
するThorbjörnRavnアンデルセン

7

Java 9以降Map.Entry、以前よりも簡単なインスタンスを作成できます。

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

Map.entry変更不可能な値を返し、Entrynullを禁止します。


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言語は単一の値(オブジェクトまたはプリミティブ型)しか返せないという問題があるため、Java 8ストリームには役立ちません。これは、ストリームがある場合は常に、前の操作から単一のオブジェクトが渡されることを意味します。これはJava言語の欠如です。なぜなら、複数の戻り値がサポートされていて、ストリームがそれらをサポートしている場合、ストリームにより、より優れた重要なタスクが実行される可能性があるためです。

それまではほとんど使われません。

編集2018-02-12:プロジェクトで作業しているときに、後で必要になるストリームの前に識別子があるという特別なケースを処理するのに役立つヘルパークラスを作成しましたが、その間のストリーム部分はそれを認識していません。私は自分自身でそれを解放するために周りを取得するまで、それはで利用可能ですIdValue.javaでのユニットテストでIdValueTest.java


2

EclipseコレクションにはPair、プリミティブとオブジェクトのペア(および8つのプリミティブすべて)のすべての組み合わせがあります。

Tuples工場でのインスタンスを作成することができPair、及びPrimitiveTuples工場は、プリミティブ/オブジェクトのペアのすべての組み合わせを作成するために使用することができます。

Java 8がリリースされる前にこれらを追加しました。これらは、プリミティブマップのキー/値イテレーターを実装するのに役立ちました。これは、すべてのプリミティブ/オブジェクトの組み合わせでもサポートされます。

ライブラリのオーバーヘッドを追加したい場合は、Stuartの承認済みソリューションを使用して結果をプリミティブIntListに収集し、ボクシングを回避できます。Eclipseコレクション9.0に新しいメソッドを追加してInt/Long/DoubleInt/Long/DoubleStreams からコレクションを作成できるようにしました。

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

注:私はEclipseコレクションのコミッターです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.