Ich spiele mit faulen Funktionsoperationen in Java SE 8 herum und möchte map
einen Index i
für ein Paar / Tupel erstellen (i, value[i])
, dann filter
basierend auf dem zweiten value[i]
Element, und schließlich nur die Indizes ausgeben.
Muss ich noch darunter leiden: Was entspricht dem C ++ - Paar <L, R> in Java? in der kühnen neuen Ära der Lambdas und Bäche?
Update: Ich habe ein ziemlich vereinfachtes Beispiel vorgestellt, das eine nette Lösung von @dkatzel in einer der folgenden Antworten enthält. Dies ist jedoch nicht der Fall verallgemeinert . Lassen Sie mich daher ein allgemeineres Beispiel hinzufügen:
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))
);
}
}
Dies ergibt eine falsche Ausgabe, [0, 0, 0]
die der Anzahl für die drei Spalten entspricht, die alle sind false
. Was ich brauche, sind die Indizes dieser drei Spalten. Die richtige Ausgabe sollte sein [0, 2, 4]
. Wie kann ich dieses Ergebnis erhalten?
quelle
AbstractMap.SimpleImmutableEntry<K,V>
seit Jahren ... Aber wie auch immer, statt Zuordnungi
zu(i, value[i])
nur zum Filtern vonvalue[i]
und Mapping zurück zui
: warum Filter nicht nur vonvalue[i]
in erster Linie, ohne die Zuordnung?i
im Stream zu bleiben. Ich brauche auchvalue[i]
für die Kriterien. Deshalb brauche ich(i, value[i])
[0, 2, 4]
?Antworten:
UPDATE: Diese Antwort ist eine Antwort auf die ursprüngliche Frage: Hat Java SE 8 Paare oder Tupel? (Und implizit, wenn nicht, warum nicht?) Das OP hat die Frage mit einem vollständigeren Beispiel aktualisiert, aber es scheint, dass sie ohne Verwendung einer Paarstruktur gelöst werden kann. [Anmerkung von OP: Hier ist die andere richtige Antwort .]
Die kurze Antwort lautet nein. Sie müssen entweder Ihre eigenen rollen oder eine der mehreren Bibliotheken einbinden, die sie implementieren.
Eine
Pair
Klasse in Java SE zu haben, wurde mindestens einmal vorgeschlagen und abgelehnt. Siehe diesen Diskussionsthread auf einer der OpenJDK-Mailinglisten. Die Kompromisse sind nicht offensichtlich. Einerseits gibt es viele Paarimplementierungen in anderen Bibliotheken und im Anwendungscode. Dies zeigt einen Bedarf, und das Hinzufügen einer solchen Klasse zu Java SE erhöht die Wiederverwendung und Freigabe. Andererseits erhöht die Verwendung einer Pair-Klasse die Versuchung, komplizierte Datenstrukturen aus Paaren und Sammlungen zu erstellen, ohne die erforderlichen Typen und Abstraktionen zu erstellen. (Das ist eine Umschreibung von Kevin Bourillions Botschaft aus diesem Thread.)Ich empfehle jedem, den gesamten E-Mail-Thread zu lesen. Es ist bemerkenswert aufschlussreich und hat keine Flammen. Es ist ziemlich überzeugend. Als es anfing, dachte ich: "Ja, es sollte eine Pair-Klasse in Java SE geben", aber als der Thread sein Ende erreichte, hatte ich meine Meinung geändert.
Beachten Sie jedoch, dass JavaFX das javafx.util.Pair hat Klasse . Die APIs von JavaFX wurden getrennt von den Java SE-APIs entwickelt.
Wie aus der verknüpften Frage hervorgeht, entspricht das C ++ - Paar in Java?Es gibt einen ziemlich großen Designraum, der eine scheinbar so einfache API umgibt. Sollten die Objekte unveränderlich sein? Sollten sie serialisierbar sein? Sollten sie vergleichbar sein? Sollte die Klasse endgültig sein oder nicht? Sollten die beiden Elemente bestellt werden? Sollte es eine Schnittstelle oder eine Klasse sein? Warum bei Paaren anhalten? Warum nicht Tripel, Quads oder N-Tupel?
Und natürlich gibt es die unvermeidliche Namensgebung für die Elemente:
Ein großes Problem, das kaum erwähnt wurde, ist das Verhältnis von Paaren zu Primitiven. Wenn Sie ein
(int x, int y)
Datum haben, das einen Punkt im 2D-Raum darstellt,Pair<Integer, Integer>
verbraucht dies drei Objekte anstelle von zwei 32-Bit-Wörtern. Darüber hinaus müssen sich diese Objekte auf dem Heap befinden und verursachen GC-Overhead.Es scheint klar zu sein, dass es wie bei Streams wesentlich ist, dass es primitive Spezialisierungen für Paare gibt. Wollen wir sehen:
Sogar ein
IntIntPair
würde noch ein Objekt auf dem Haufen benötigen.Diese erinnern natürlich an die Verbreitung funktionaler Schnittstellen im
java.util.function
Paket in Java SE 8. Wenn Sie keine aufgeblähte API möchten, welche würden Sie weglassen? Sie könnten auch argumentieren, dass dies nicht ausreicht und dass beispielsweise auch SpezialisierungenBoolean
hinzugefügt werden sollten.Ich habe das Gefühl, dass Java, wenn es vor langer Zeit eine Pair-Klasse hinzugefügt hätte, einfach oder sogar simpel gewesen wäre und viele der Anwendungsfälle, die wir uns jetzt vorstellen, nicht erfüllt hätte. Wenn Pair im Zeitrahmen von JDK 1.0 hinzugefügt worden wäre, wäre es wahrscheinlich veränderlich gewesen! (Schauen Sie sich java.util.Date an.) Wären die Leute damit zufrieden gewesen? Ich vermute, wenn es eine Pair-Klasse in Java gäbe, wäre sie irgendwie nicht wirklich nützlich, und jeder wird immer noch seine eigenen rollen, um seine Anforderungen zu erfüllen. Es würde verschiedene Pair- und Tuple-Implementierungen in externen Bibliotheken geben. und die Leute würden immer noch darüber streiten / diskutieren, wie Javas Pair-Klasse repariert werden kann. Mit anderen Worten, irgendwie an dem Ort, an dem wir uns heute befinden.
Inzwischen wird einige Arbeit auf die grundlegende Frage anzusprechen, die eine bessere Unterstützung in der JVM ist (und schließlich die Java - Sprache) für Werttypen . Siehe dieses Dokument zum Status der Werte . Dies ist eine vorläufige, spekulative Arbeit, die nur Themen aus der Sicht der JVM abdeckt, aber bereits einiges an Gedanken hinter sich hat. Natürlich gibt es keine Garantie dafür, dass dies in Java 9 oder jemals irgendwohin gelangt, aber es zeigt die aktuelle Denkrichtung zu diesem Thema.
quelle
Pair<T,U>
. Da Generika müssen vom Referenztyp sein. Alle Grundelemente werden beim Speichern eingerahmt. Um Primitive zu speichern, benötigen Sie wirklich eine andere Klasse.valueOf
der einzige Weg gewesen sein, eine Instanz in Boxen zu erhalten. Aber diese sind seit Java 1.0 vorhanden und es lohnt sich wahrscheinlich nicht, sie an dieser Stelle zu ändern.Pair
oderTuple
Klasse mit einer Factory-Methode geben, die die erforderlichen Spezialisierungsklassen (mit optimiertem Speicher) transparent im Hintergrund erstellt. Am Ende tun Lambdas genau das: Sie können eine beliebige Anzahl von Variablen beliebigen Typs erfassen. Und jetzt stellen Sie sich eine Sprachunterstützung vor, die es ermöglicht, zur Laufzeit die entsprechende Tupelklasse zu erstellen, die durch eineinvokedynamic
Anweisung ausgelöst wird …invokedynamic
Fabrik, die der Lambda-Kreation ähnelt, wäre eine solche spätere Nachrüstung kein Problem. Lambdas haben übrigens auch keine Identität. Wie bereits erwähnt, ist die Identität, die Sie heute möglicherweise wahrnehmen, ein Artefakt der aktuellen Implementierung.Sie können sich diese integrierten Klassen ansehen:
AbstractMap.SimpleEntry
AbstractMap.SimpleImmutableEntry
quelle
SimpleImmutableEntry
nur garantiert wird, dass sich die in der gespeicherten ReferenzenEntry
nicht ändern, nicht, dass sich die Felder der verknüpften Objektekey
und dervalue
Objekte (oder der Objekte, mit denen sie verknüpft sind) nicht ändern.Leider hat Java 8 keine Paare oder Tupel eingeführt. Sie können natürlich immer org.apache.commons.lang3.tuple verwenden (was ich persönlich in Kombination mit Java 8 verwende) oder Sie können Ihre eigenen Wrapper erstellen. Oder verwenden Sie Karten. Oder ähnliches, wie in der akzeptierten Antwort auf die Frage, mit der Sie verlinkt haben, erklärt wird.
UPDATE: JDK 14 führt Datensätze als Vorschaufunktion ein. Dies sind keine Tupel, aber sie können verwendet werden, um viele der gleichen Probleme zu speichern. In Ihrem speziellen Beispiel von oben könnte das ungefähr so aussehen:
Wenn Sie mit JDK 14 (zum Zeitpunkt des Schreibens ein Build mit frühem Zugriff) unter Verwendung des
--enable-preview
Flags kompiliert und ausgeführt werden , erhalten Sie das folgende Ergebnis:quelle
Es scheint, dass das vollständige Beispiel ohne die Verwendung einer Paarstruktur gelöst werden kann. Der Schlüssel besteht darin, nach den Spaltenindizes zu filtern, wobei das Prädikat die gesamte Spalte überprüft, anstatt die Spaltenindizes der Anzahl der
false
Einträge in dieser Spalte zuzuordnen.Der Code, der dies tut, ist hier:
Dies führt zu einer Ausgabe, von
[0, 2, 4]
der ich denke, dass sie das vom OP angeforderte korrekte Ergebnis ist.Beachten Sie auch die
boxed()
Operation, bei der dieint
Werte inInteger
Objekte eingeteilt werden. Dies ermöglicht es einem, den bereits vorhandenentoList()
Kollektor zu verwenden, anstatt Kollektorfunktionen ausschreiben zu müssen, die das Boxen selbst ausführen.quelle
true
). Dementsprechend werde ich Ihre andere Antwort als richtig akzeptieren, aber auch auf diese hinweisen! Vielen Dank :)Vavr (früher Javaslang genannt) ( http://www.vavr.io ) bietet auch Tupel (bis zu einer Größe von 8). Hier ist das Javadoc: https://static.javadoc.io/io.vavr/vavr/0.9.0/io/vavr/Tuple.html .
Dies ist ein einfaches Beispiel:
Warum JDK selbst bis jetzt nicht mit einer einfachen Art von Tupeln geliefert wurde, ist mir ein Rätsel. Das Schreiben von Wrapper-Klassen scheint ein alltägliches Geschäft zu sein.
quelle
Seit Java 9 können Sie Instanzen
Map.Entry
einfacher als zuvor erstellen :Map.entry
gibt eine nicht veränderbare zurückEntry
und verbietet Nullen.quelle
Da Sie sich nur um die Indizes kümmern, müssen Sie Tupeln überhaupt nicht zuordnen. Warum nicht einfach einen Filter schreiben, der die Suchelemente in Ihrem Array verwendet?
quelle
Ja.
Map.Entry
kann als verwendet werdenPair
.Leider hilft es bei Java 8-Streams nicht, da das Problem darin besteht, dass Lambdas zwar mehrere Argumente annehmen können, die Java-Sprache jedoch nur die Rückgabe eines einzelnen Werts (Objekt oder primitiver Typ) zulässt. Dies bedeutet, dass Sie immer dann, wenn Sie einen Stream haben, ein einzelnes Objekt aus der vorherigen Operation übergeben bekommen. Dies ist ein Mangel in der Java-Sprache, denn wenn mehrere Rückgabewerte unterstützt würden UND Streams diese unterstützen würden, könnten Streams viel schönere, nicht triviale Aufgaben ausführen.
Bis dahin nützt es nur wenig.
EDIT 2018-02-12: Während ich an einem Projekt arbeitete, schrieb ich eine Hilfsklasse, die hilft, den Sonderfall zu behandeln, dass eine Kennung früher im Stream vorhanden ist, die Sie zu einem späteren Zeitpunkt benötigen, aber der dazwischen liegende Stream-Teil weiß nichts davon. Bis ich dazu komme, es selbst zu veröffentlichen, ist es bei IdValue.java mit einem Unit-Test bei IdValueTest.java verfügbar
quelle
Eclipse Collections hat
Pair
und alle Kombinationen von Grundelement- / Objektpaaren (für alle acht Grundelemente).Die
Tuples
Factory kann Instanzen von erstellenPair
, und diePrimitiveTuples
Factory kann verwendet werden, um alle Kombinationen von Primitiv / Objekt-Paaren zu erstellen.Wir haben diese hinzugefügt, bevor Java 8 veröffentlicht wurde. Sie waren nützlich, um Schlüssel- / Wert-Iteratoren für unsere primitiven Karten zu implementieren, die wir auch in allen primitiven / Objekt-Kombinationen unterstützen.
Wenn Sie bereit sind, den zusätzlichen Bibliotheksaufwand hinzuzufügen, können Sie die von Stuart akzeptierte Lösung verwenden und die Ergebnisse in einem Grundelement sammeln
IntList
, um Boxen zu vermeiden. Wir haben in Eclipse Collections 9.0 neue Methoden hinzugefügt , damitInt/Long/Double
Sammlungen ausInt/Long/Double
Streams erstellt werden können.Hinweis: Ich bin ein Committer für Eclipse-Sammlungen.
quelle