Eine Java-Sammlung von Wertepaaren? (Tupel?)

345

Mir gefällt, wie Java eine Karte hat, in der Sie beispielsweise die Typen der einzelnen Einträge in der Karte definieren können <String, Integer> .

Was ich suche, ist eine Art von Sammlung, bei der jedes Element in der Sammlung ein Wertepaar ist. Jeder Wert im Paar kann einen eigenen Typ haben (wie im obigen Beispiel für String und Integer), der zur Deklarationszeit definiert wird.

Die Sammlung behält ihre angegebene Reihenfolge bei und behandelt einen der Werte nicht als eindeutigen Schlüssel (wie in einer Karte).

Im Wesentlichen möchte ich in der Lage sein, einen ARRAY vom Typ <String,Integer>oder andere 2 Typen zu definieren .

Mir ist klar, dass ich eine Klasse nur mit den 2 Variablen erstellen kann, aber das scheint zu ausführlich.

Mir ist auch klar, dass ich ein 2D-Array verwenden könnte, aber aufgrund der verschiedenen Typen, die ich verwenden muss, müsste ich sie zu OBJECT-Arrays machen und dann die ganze Zeit umwandeln.

Ich muss nur Paare in der Sammlung speichern, daher benötige ich nur zwei Werte pro Eintrag. Gibt es so etwas, ohne den Klassenweg zu gehen? Vielen Dank!

DivideByHero
quelle
Ich frage mich, ob Guava auch dafür eine Klasse haben könnte.
Sikorski
Guave ist ziemlich anti- Pairund die Leute bei Google sind so weit gegangen, eine viel bessere Alternative zu schaffen - Auto / Wert . Sie können auf einfache Weise gut typisierte Werttypklassen mit der richtigen Semantik für equals / hashCode erstellen . Sie werden nie wieder einen PairTyp brauchen !
dimo414

Antworten:

255

Die Pair-Klasse ist eines dieser "Gimme" -Generikbeispiele, die einfach genug sind, um sie selbst zu schreiben. Zum Beispiel von oben:

public class Pair<L,R> {

  private final L left;
  private final R right;

  public Pair(L left, R right) {
    assert left != null;
    assert right != null;

    this.left = left;
    this.right = right;
  }

  public L getLeft() { return left; }
  public R getRight() { return right; }

  @Override
  public int hashCode() { return left.hashCode() ^ right.hashCode(); }

  @Override
  public boolean equals(Object o) {
    if (!(o instanceof Pair)) return false;
    Pair pairo = (Pair) o;
    return this.left.equals(pairo.getLeft()) &&
           this.right.equals(pairo.getRight());
  }

}

Und ja, dies gibt es an mehreren Stellen im Internet mit unterschiedlichem Grad an Vollständigkeit und Funktionalität. (Mein Beispiel oben soll unveränderlich sein.)

Paul Brinkley
quelle
17
Ich mag das, aber was denkst du darüber, das linke und das rechte Feld öffentlich zu machen? Es ist ziemlich klar, dass der Pair-Klasse niemals eine Logik zugeordnet sein wird und alle Clients Zugriff auf "links" und "rechts" benötigen. Warum also nicht einfach machen?
Outlaw Programmer
43
Ähm ... nein, würde es nicht. Die Felder sind als endgültig markiert und können daher nicht neu zugewiesen werden. Und es ist nicht threadsicher, weil "links" und "rechts" veränderlich sein können. Wenn getLeft () / getRight () keine defensiven Kopien zurückgibt (in diesem Fall nutzlos), sehe ich nicht, was die große Sache ist.
Outlaw Programmer
17
Beachten Sie, dass hashCode () unverändert denselben Wert liefert, wenn links und rechts vertauscht werden. Vielleicht:long l = left.hashCode() * 2654435761L; return (int)l + (int)(l >>> 32) + right.hashCode();
Karmakaze
68
Wenn ich das richtig verstehe, führt das Ausrollen einer einfachen Paarklasse zu einem Syntaxfehler, einer unterdurchschnittlichen hashCode-Methode, Nullzeigerausnahmen, keiner compareTo-Methode, Entwurfsfragen ... und die Leute befürworten immer noch das Ausrollen dieser Klasse, solange sie in Apache Commons vorhanden ist. Bitte kopieren Sie einfach den Code, wenn Sie die JAR nicht einschließen möchten, aber das Rad nicht mehr neu erfinden möchten!
Cquezel
38
Was meinst du mit "einfach genug, um selbst zu schreiben"? Das ist schreckliches Software-Engineering. Werden die N ^ 2-Adapterklassen zum Konvertieren zwischen MyPair und SomeoneElsesPair auch als einfach genug angesehen, um selbst geschrieben zu werden?
Djechlin
299

AbstractMap.SimpleEntry

Einfach suchen Sie dies:

java.util.List<java.util.Map.Entry<String,Integer>> pairList= new java.util.ArrayList<>();

Wie kannst du es füllen?

java.util.Map.Entry<String,Integer> pair1=new java.util.AbstractMap.SimpleEntry<>("Not Unique key1",1);
java.util.Map.Entry<String,Integer> pair2=new java.util.AbstractMap.SimpleEntry<>("Not Unique key2",2);
pairList.add(pair1);
pairList.add(pair2);

Dies vereinfacht zu:

Entry<String,Integer> pair1=new SimpleEntry<>("Not Unique key1",1);
Entry<String,Integer> pair2=new SimpleEntry<>("Not Unique key2",2);
pairList.add(pair1);
pairList.add(pair2);

Und mit Hilfe einer createEntryMethode kann die Ausführlichkeit weiter reduziert werden auf:

pairList.add(createEntry("Not Unique key1", 1));
pairList.add(createEntry("Not Unique key2", 2));

Da dies ArrayListnicht endgültig ist, kann es in Unterklassen unterteilt werden, um eine ofMethode (und die oben genannte createEntryMethode) verfügbar zu machen, was zu dem syntaktisch knappen Ergebnis führt:

TupleList<java.util.Map.Entry<String,Integer>> pair = new TupleList<>();
pair.of("Not Unique key1", 1);
pair.of("Not Unique key2", 2);
JavaHelp4u
quelle
11
Zu Ihrer Information: Das hat vorgeschlagen, dass SimpleEntryeine Geschwisterklasse die setValueMethode weglässt , um unveränderlich zu sein. Also der Name SimpleImmutableEntry.
Basil Bourque
3
Dies ist besser als die Selbstimplementierung. Die Tatsache, dass es einfach zu implementieren ist, ist keine Entschuldigung dafür, es jedes Mal zu implementieren.
Pevogam
6
Ich würde es nicht als "Standard" betrachten. Entrysoll ein Schlüssel-Wert-Paar sein, und dafür ist es in Ordnung. Aber es hat Schwächen als wahres Tupel. Zum Beispiel xors die hashcodeImplementierung in SimpleEntrynur xors die Codes der Elemente, so <a,b>hascht auf den gleichen Wert wie <b,a>. Tupel-Implementierungen werden häufig lexikografisch sortiert, jedoch SimpleEntry nicht implementiert Comparable. Also sei vorsichtig da draußen ...
Gene
166

Java 9+

In Java 9 können Sie einfach schreiben: Map.entry(key, value) um ein unveränderliches Paar zu erstellen.

Hinweis: Bei dieser Methode dürfen Schlüssel oder Werte nicht null sein. Wenn Sie beispielsweise Nullwerte zulassen möchten, möchten Sie dies ändern in : Map.entry(key, Optional.ofNullable(value)).


Java 8+

In Java 8 können Sie den allgemeineren Zweck verwenden javafx.util.Pair, um ein unveränderliches, serialisierbares Paar zu erstellen. Diese Klasse ist null Schlüssel und Nullwerte zulassen. (In Java 9 ist diese Klasse im javafx.baseModul enthalten).BEARBEITEN: Ab Java 11 wurde JavaFX vom JDK entkoppelt, sodass Sie das zusätzliche Maven-Artefakt org.openjfx: javafx-base benötigen.


Java 6+

In Java 6 und höher können Sie die ausführlichere Version AbstractMap.SimpleImmutableEntryfür ein unveränderliches Paar oder AbstractMap.SimpleEntryfür ein Paar verwenden, dessen Wert geändert werden kann. Diese Klassen erlauben auch Nullschlüssel und Nullwerte und sind serialisierbar.


Android

Wenn Sie für Android schreiben Pair.create(key, value), erstellen Sie einfach ein unveränderliches Paar.


Apache Commons

Apache Commons Langbietet die Möglichkeit Pair.of(key, value), ein unveränderliches, vergleichbares, serialisierbares Paar zu erstellen.


Eclipse-Sammlungen

Wenn Sie Paare verwenden, die Grundelemente enthalten, Eclipse-Sammlungen bietet einige sehr effiziente Grundelementpaarklassen, die das ineffiziente automatische Boxen und automatische Entpacken vermeiden.

Sie können beispielsweise PrimitiveTuples.pair(int, int)ein IntIntPairoder ein PrimitiveTuples.pair(float, long)erstellen FloatLongPair.


Projekt Lombok

Mit Project Lombok können Sie eine unveränderliche Paarklasse erstellen, indem Sie einfach Folgendes schreiben:

@Value
public class Pair<K, V> {
    K key;
    V value;
}

Lombok wird in den Konstruktor, Getter, füllen equals(), hashCode()und toString()Methoden für Sie automatisch in der erzeugten Bytecode. Wenn Sie eine statische Factory-Methode anstelle eines Konstruktors möchten, z. B. a Pair.of(k, v), ändern Sie einfach die Anmerkung in : @Value(staticConstructor = "of").


Andernfalls

Wenn keine der oben genannten Lösungen Ihr Boot schwimmt, können Sie einfach den folgenden Code kopieren und einfügen (der im Gegensatz zu der in der akzeptierten Antwort aufgeführten Klasse vor NullPointerExceptions schützt):

import java.util.Objects;

public class Pair<K, V> {

    public final K key;
    public final V value;

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public boolean equals(Object o) {
        return o instanceof Pair && Objects.equals(key, ((Pair<?,?>)o).key) && Objects.equals(value, ((Pair<?,?>)o).value);
    }

    public int hashCode() {
        return 31 * Objects.hashCode(key) + Objects.hashCode(value);
    }

    public String toString() {
        return key + "=" + value;
    }
}
Hans Brende
quelle
3
JavaFX ist jedoch nur für den Desktop verfügbar und fügt eine unnötige Abhängigkeit für Nicht-Desktop-Umgebungen (z. B. Server) hinzu.
foo
2
Eclipse Collections verfügt auch über eine PairSchnittstelle für Objekte, die durch Aufrufen instanziiert werden kann Tuples.pair(object1, object2). eclipse.org/collections/javadoc/9.2.0/org/eclipse/collections/…
Donald Raab
2
Dies scheint die wirklich Java-ähnliche Antwort zu sein. ALLE verfügbaren Implementierungen. Sehr gründlich, vielen Dank!
Mike
Hallo @Hans, ich benutze eine Liste List<Pair<Integer, String> listOfTuple. Wie benutze ich listOfTuple.add()? Wenn ich das mache listOfTuple.add(Pair<someInteger, someString>);, wird das nicht funktionieren. Danke im Voraus.
Me_developer
Die Android-Version ist nicht im Unit-Test verfügbar, was es nicht wirklich nützlich macht ...
OznOg
64

Map.Entry

Diese integrierten Klassen sind ebenfalls eine Option. Beide implementieren die Map.EntrySchnittstelle.

UML-Diagramm der Map.Entry-Schnittstelle mit zwei implementierenden Klassen

Johannes Weiss
quelle
10
Sie sind nicht nur eine Option, sie sind die richtige Antwort. Ich denke, einige Leute ziehen es einfach vor, das Rad neu zu erfinden.
CurtainDog
31

Apache common lang3 hat die Pair-Klasse und einige andere in diesem Thread erwähnte Bibliotheken. Was entspricht dem C ++ - Pair <L, R> in Java?

Beispiel, das der Anforderung Ihrer ursprünglichen Frage entspricht:

List<Pair<String, Integer>> myPairs = new ArrayList<Pair<String, Integer>>();
myPairs.add(Pair.of("val1", 11));
myPairs.add(Pair.of("val2", 17));

//...

for(Pair<String, Integer> pair : myPairs) {
  //following two lines are equivalent... whichever is easier for you...
  System.out.println(pair.getLeft() + ": " + pair.getRight());
  System.out.println(pair.getKey() + ": " + pair.getValue());
}
geändert
quelle
1
Dies ist die einfachste Option, wenn Apache Commons verfügbar ist.
Kip
18

Für alle, die für Android entwickeln, können Sie android.util.Pair verwenden . :) :)

XåpplI'-I0llwlg'I -
quelle
15

Was ist mit der PairKlasse "Apache Commons Lang 3" und den relativen Unterklassen?

    import org.apache.commons.lang3.tuple.ImmutablePair;
    import org.apache.commons.lang3.tuple.Pair;
    ...
    @SuppressWarnings("unchecked")
    Pair<String, Integer>[] arr = new ImmutablePair[]{
            ImmutablePair.of("A", 1),
            ImmutablePair.of("B", 2)};

    // both access the 'left' part
    String key = arr[0].getKey();
    String left = arr[0].getLeft();

    // both access the 'right' part
    Integer value = arr[0].getValue();
    Integer right = arr[0].getRight();

ImmutablePairist eine bestimmte Unterklasse, mit der die Werte im Paar nicht geändert werden können. Es gibt jedoch auch andere Implementierungen mit unterschiedlicher Semantik. Dies sind die Maven-Koordinaten, falls Sie sie benötigen.

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.4</version>
        </dependency>
danidemi
quelle
11

Sie können eine generische Pair <A, B> -Klasse schreiben und diese in einem Array oder einer Liste verwenden. Ja, Sie müssen eine Klasse schreiben, aber Sie können dieselbe Klasse für alle Typen wiederverwenden, sodass Sie sie nur einmal ausführen müssen.

Dan Dyer
quelle
Ich würde gerne ein Beispiel dafür sehen!
DivideByHero
1
Dan, das Schlimme daran ist, dass es wegen der Typlöschung nicht möglich ist, zB nur Pair <String, Integer> s zu akzeptieren, oder? Aber Java auch ...
Johannes Weiss
Ich denke nicht, dass dies für einfache Arrays funktionieren wird, aber es wird definitiv für andere Sammlungen funktionieren.
Outlaw Programmer
@Outlaw Programmer: Guter Punkt, Sie können es mit Arrays verwenden, aber es ist hässlich, da Sie einen Hack verwenden müssen, um generische Arrays zu erstellen.
Dan Dyer
7

Wenn Sie die anderen Antworten erweitern, sollte ein generisches unveränderliches Paar über eine statische Methode verfügen, um zu vermeiden, dass Ihr Code beim Aufruf des Konstruktors überladen wird:

class Pair<L,R> {
      final L left;
      final R right;

      public Pair(L left, R right) {
        this.left = left;
        this.right = right;
      }

      static <L,R> Pair<L,R> of(L left, R right){
          return new Pair<L,R>(left, right);
      }
}

Wenn Sie die statische Methode "of" oder "pairOf" nennen, wird der Code fließend, da Sie entweder schreiben können:

    list.add(Pair.of(x,y)); // my preference
    list.add(pairOf(x,y)); // use with import static x.y.Pair.pairOf

Es ist eine echte Schande, dass die Java-Kernbibliotheken so spärlich mit solchen Dingen umgehen, dass Sie commons-lang oder andere Drittanbieter verwenden müssen, um solche grundlegenden Dinge zu erledigen. Ein weiterer Grund für ein Upgrade auf Scala ...

simbo1905
quelle
6

Ich wollte fragen, ob Sie nicht einfach eine verwenden möchten List<Pair<T, U>>? Aber dann hat das JDK natürlich keine Pair <> -Klasse. Aber ein schneller Google hat einen sowohl auf Wikipedia als auch auf forums.sun.com gefunden . Prost

JMD
quelle
6

Die bevorzugte Lösung, wie Sie sie beschrieben haben, ist eine Liste von Paaren (dh Liste).

Um dies zu erreichen, erstellen Sie eine Pair-Klasse zur Verwendung in Ihrer Sammlung. Dies ist eine nützliche Dienstprogrammklasse, die Sie Ihrer Codebasis hinzufügen können.

Die nächstgelegene Klasse im Sun JDK, die ähnliche Funktionen wie eine typische Pair-Klasse bietet, ist AbstractMap.SimpleEntry. Sie könnten diese Klasse verwenden, anstatt Ihre eigene Pair-Klasse zu erstellen, obwohl Sie mit einigen umständlichen Einschränkungen leben müssten, und ich denke, die meisten Leute würden dies als nicht wirklich die beabsichtigte Rolle von SimpleEntry missbilligen. Zum Beispiel hat SimpleEntry keine "setKey ()" - Methode und keinen Standardkonstruktor, daher ist dies möglicherweise zu einschränkend.

Beachten Sie, dass Sammlungen Elemente eines einzelnen Typs enthalten. Verwandte Dienstprogrammschnittstellen wie Map sind eigentlich keine Sammlungen (dh Map implementiert die Collection-Schnittstelle nicht). Ein Paar würde auch die Collection-Schnittstelle nicht implementieren, ist aber offensichtlich eine nützliche Klasse beim Aufbau größerer Datenstrukturen.

Jeremy Rishel
quelle
Ich finde diese Lösung viel besser als die "gewinnende" mit dem generischen Paar <K, V>. Es macht alles, was gewünscht wird und kommt aus der Box. Ich benutze diese für meine Implementierung.
Sauer
5

Dies basiert auf dem Code von JavaHelp4u.

Weniger ausführlich und zeigt, wie man in einer Zeile vorgeht und wie man Dinge durchläuft.

//======>  Imports
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;

//======>  Single Entry
SimpleEntry<String, String> myEntry = new SimpleEntry<String, String>("ID", "Text");
System.out.println("key: " + myEntry.getKey() + "    value:" + myEntry.getValue());
System.out.println();

//======>  List of Entries
List<Entry<String,String>> pairList = new ArrayList<>();

//-- Specify manually
Entry<String,String> firstButton = new SimpleEntry<String, String>("Red ", "Way out");
pairList.add(firstButton);

//-- one liner:
pairList.add(new SimpleEntry<String,String>("Gray", "Alternate route"));  //Ananomous add.

//-- Iterate over Entry array:
for (Entry<String, String> entr : pairList) {
    System.out.println("Button: " + entr.getKey() + "    Label: " + entr.getValue());
}
Leo Ufimtsev
quelle
4

Erstelle einfach eine Klasse wie

class tuples 
{ 
int x;
int y;
} 

Erstellen Sie dann eine Liste dieser Tupelobjekte

List<tuples> list = new ArrayList<tuples>();

Auf diese Weise können Sie auch andere neue Datenstrukturen implementieren.

user93
quelle
4

Ich meine, obwohl es Pairin Java keine Klasse gibt, gibt es etwas ziemlich Ähnliches:Map.Entry

Map.Entry-Dokumentation

Dies ist (einiges vereinfacht) was HashMapoder tatsächlich irgendeinMap Geschäfte.

Sie können eine Instanz zum MapSpeichern Ihrer Werte darin erstellen und den Eintragssatz abrufen. Sie werden mit einem endenSet<Map.Entry<K,V>> das effektiv das ist, was Sie wollen.

Damit:

public static void main(String []args)
{    
    HashMap<String, Integer> values = new HashMap<String,Integer>();
    values.put("A", 235);//your custom data, the types may be different
    //more data insertions....
    Set<Map.Entry<String,Integer>> list = values.entrySet();//your list 
    //do as you may with it
}
SomeDude
quelle
Diese Option hat das Problem der eindeutigen Schlüssel. Angenommen, Sie haben Attribute von Personen (z. B. {(Person1, "blauäugig"), (Person1, "rothaarig", (Person2, "kurzsichtig"), (Person2, "Schnelldenker")}) nicht in der Lage sein, sie als Paare in einer Karte zu speichern, da jede Person der Schlüssel zur Karte ist und nur ein Attribut pro Person zulassen würde.
manuelvigarcia
2

Spring hat einen Pair<S,T>Typ im Data Utils-Paketorg.springframework.data.util

Pair<String,Integer> pair = Pair.of("Test", 123);
System.out.println(pair.getFirst());
System.out.println(pair.getSecond());
HammelUp
quelle
0

Was ist mit com.sun.tools.javac.util.Pair?

Rene H.
quelle
7
Dieser Typ befindet sich in tools.jar - Teil der "Sun" -Implementierung des Javac-Compilers. Es wird nur als Teil des JDK verteilt, ist möglicherweise nicht in Implementierungen anderer Anbieter vorhanden und ist wahrscheinlich nicht Teil der öffentlichen API.
McDowell
0

Wenn ich über Schlüssel / Wert-Paare spreche, denke ich zuerst an die Eigenschaftenklasse, in der Sie Elemente speichern und in einen Stream / eine Datei laden können.

muka90
quelle
0

Im Projekt Reaktor (io.projectreactor: Reaktorkern) gibt es erweiterte Unterstützung für n-Tupel:

Tuple2<String, Integer> t = Tuples.of("string", 1)

Dort können Sie vor t.getT1(), t.getT2(), ...allem mit Stream oder Flux sogar die Tupelelemente abbilden:

Stream<Tuple2<String, Integer>> s;
s.map(t -> t.mapT2(i -> i + 2));
Reifen
quelle