¿Una colección Java de pares de valores? (tuplas?)

345

Me gusta cómo Java tiene un mapa donde puede definir los tipos de cada entrada en el mapa, por ejemplo <String, Integer>.

Lo que estoy buscando es un tipo de colección donde cada elemento de la colección es un par de valores. Cada valor en el par puede tener su propio tipo (como el ejemplo de String e Integer anterior), que se define en el momento de la declaración.

La colección mantendrá su orden dado y no tratará uno de los valores como una clave única (como en un mapa).

Esencialmente quiero poder definir un ARRAY de tipo <String,Integer>o cualquier otro tipo de 2.

Me doy cuenta de que puedo hacer una clase con nada más que las 2 variables, pero eso parece demasiado detallado.

También me doy cuenta de que podría usar una matriz 2D, pero debido a los diferentes tipos que necesito usar, tendría que hacerlos matrices de OBJETO, y luego tendría que lanzar todo el tiempo.

Solo necesito almacenar pares en la colección, por lo que solo necesito dos valores por entrada. ¿Existe algo como esto sin ir a la ruta de clase? ¡Gracias!

DivideByHero
fuente
Me pregunto que Guava podría tener una clase para esto también.
Sikorski el
La guayaba es bastante anti Pair, y la gente de Google ha ido tan lejos como para crear una alternativa mucho mejor: Auto / Value . Le permite crear fácilmente clases de tipo de valor bien tipadas , con una semántica igual / hashCode adecuada. ¡Nunca más necesitarás un Pairtipo!
dimo414

Respuestas:

255

La clase Pair es uno de esos ejemplos genéricos "dame" que es bastante fácil de escribir por su cuenta. Por ejemplo, fuera de mi cabeza:

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());
  }

}

Y sí, esto existe en múltiples lugares en la red, con diversos grados de integridad y funcionalidad. (Mi ejemplo anterior está destinado a ser inmutable).

Paul Brinkley
fuente
17
Me gusta esto, pero ¿qué piensas acerca de hacer públicos los campos izquierdo y derecho? Está bastante claro que la clase Pair nunca tendrá una lógica asociada y todos los clientes necesitarán acceso a 'izquierda' y 'derecha', entonces, ¿por qué no facilitarlo?
Programador fuera de la ley
43
Um ... no, no lo haría. Los campos están marcados como finales, por lo que no se pueden reasignar. Y no es seguro porque 'izquierda' y 'derecha' podrían ser mutables. A menos que getLeft () / getRight () devuelva copias defensivas (inútiles en este caso), no veo cuál es el problema.
Programador fuera de la ley
17
Tenga en cuenta que hashCode () as-is does da el mismo valor si se intercambian izquierda y derecha. Quizás:long l = left.hashCode() * 2654435761L; return (int)l + (int)(l >>> 32) + right.hashCode();
karmakaze
68
Entonces, si entiendo correctamente, desplegar una clase de par simple produce un error de sintaxis, un método hashCode subpar, excepciones de puntero nulo, un método no comparativo, preguntas de diseño ... y la gente todavía aboga por implementar esta clase mientras existe en Apache commons. ¡Por favor, solo copie el código si no desea incluir el JAR pero deje de reinventar la rueda!
cquezel
38
¿Qué quiere decir "lo suficientemente fácil de escribir por su cuenta"? Esa es una ingeniería de software terrible . ¿Las clases de adaptadores N ^ 2 para convertir entre MyPair y SomeoneElsesPair también se consideran lo suficientemente fáciles de escribir?
djechlin 01 de
299

AbstractMap.SimpleEntry

Fácil estás buscando esto:

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

¿Cómo puedes llenarlo?

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);

Esto se simplifica a:

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);

Y, con la ayuda de un createEntrymétodo, puede reducir aún más la verbosidad a:

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

Como ArrayListno es definitivo, se puede subclasificar para exponer un ofmétodo (y el createEntrymétodo mencionado anteriormente ), lo que resulta en el sintácticamente conciso:

TupleList<java.util.Map.Entry<String,Integer>> pair = new TupleList<>();
pair.of("Not Unique key1", 1);
pair.of("Not Unique key2", 2);
JavaHelp4u
fuente
11
FYI: Eso sugerido SimpleEntrytiene un setValuemétodo de omisión de clase de hermano para ser inmutable. De ahí el nombre SimpleImmutableEntry.
Basil Bourque
3
Esto es mejor que la auto implementación. El hecho de que sea fácil de implementar no es una excusa para implementarlo cada vez.
pevogam
66
No lo consideraría "estándar". Entryestá destinado a ser un par clave-valor, y está bien para eso. Pero tiene debilidades como una verdadera tupla. Por ejemplo, la hashcodeimplementación en SimpleEntrysolo xors los códigos de los elementos, por lo tanto, <a,b>tiene el mismo valor <b,a>. Las implementaciones de tuplas a menudo se clasifican lexicográficamente, pero SimpleEntry no se implementan Comparable. Así que ten cuidado allá afuera ...
Gene
167

Java 9+

En Java 9, simplemente puede escribir: Map.entry(key, value) para crear un par inmutable.

Nota: este método no permite que las claves o valores sean nulos. Si desea permitir valores nulos, por ejemplo, que te gustaría cambiar esto a: Map.entry(key, Optional.ofNullable(value)).


Java 8+

En Java 8, puede usar el propósito más general javafx.util.Pairpara crear un par inmutable y serializable. Esta clase no permite llaves nulos y valores nulos. (En Java 9, esta clase está incluida en el javafx.basemódulo).EDITAR: a partir de Java 11, JavaFX se ha desacoplado del JDK, por lo que necesitará el artefacto maven adicional org.openjfx: javafx-base.


Java 6+

En Java 6 y versiones posteriores, puede usar el más detallado AbstractMap.SimpleImmutableEntrypara un par inmutable o AbstractMap.SimpleEntrypara un par cuyo valor se puede cambiar. Estas clases también permiten claves nulas y valores nulos, y son serializables.


Androide

Si está escribiendo para Android, solo use Pair.create(key, value)para crear un par inmutable.


Apache Commons

Apache Commons Langproporciona la ayuda Pair.of(key, value)para crear un par inmutable, comparable y serializable.


Colecciones Eclipse

Si usa pares que contienen primitivas, Eclipse Collections proporciona algunas clases de pares primitivos muy eficientes que evitarán todo el ineficiente auto-boxing y auto-unboxing.

Por ejemplo, podría usar PrimitiveTuples.pair(int, int)para crear un IntIntPair, o PrimitiveTuples.pair(float, long)para crear un FloatLongPair.


Proyecto lombok

Con Project Lombok , puede crear una clase de par inmutable simplemente escribiendo:

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

Lombok llenará en el constructor, captadores, equals(), hashCode(), y toString()métodos de forma automática en el código de bytes generada. Si quieres un método de fábrica estática en lugar de un constructor, por ejemplo, una Pair.of(k, v), basta con cambiar la anotación a: @Value(staticConstructor = "of").


De otra manera

Si ninguna de las soluciones anteriores flota en su bote, simplemente puede copiar y pegar el siguiente código (que, a diferencia de la clase que figura en la respuesta aceptada, protege contra NullPointerExceptions):

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
fuente
3
Sin embargo, JavaFX es solo de escritorio, agregando una dependencia innecesaria para entornos que no son de escritorio (por ejemplo, servidores).
foo
2
Eclipse Collections también tiene una Pairinterfaz para objetos, que se puede instanciar llamando Tuples.pair(object1, object2). eclipse.org/collections/javadoc/9.2.0/org/eclipse/collections/…
Donald Raab
2
Esto se siente como la respuesta más verdadera de Java-esque. TODAS las implementaciones disponibles. Muy completo, muchas gracias!
Mike
Hola @Hans, estoy usando una lista List<Pair<Integer, String> listOfTuple. ¿Cómo lo uso listOfTuple.add()? Si lo hago listOfTuple.add(Pair<someInteger, someString>);, esto no funcionará. Gracias por adelantado.
Me_developer
la versión de Android no está disponible en la prueba unitaria, lo que hace que no sea realmente útil ...
OznOg
31

Apache common lang3 tiene la clase Pair y algunas otras bibliotecas mencionadas en este hilo ¿Cuál es el equivalente del par C ++ <L, R> en Java?

Ejemplo que coincide con el requisito de su pregunta original:

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());
}
cambiado
fuente
1
Esta es la opción más simple si Apache Commons está disponible.
Kip
15

¿Qué pasa con la Pairclase "Apache Commons Lang 3" y las subclases relativas?

    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();

ImmutablePaires una subclase específica que no permite modificar los valores en el par, pero hay otras implementaciones con diferentes semánticas. Estas son las coordenadas Maven, si las necesita.

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

Podría escribir una clase genérica Par <A, B> y utilizarla en una matriz o lista. Sí, debe escribir una clase, pero puede reutilizar la misma clase para todos los tipos, por lo que solo tiene que hacerlo una vez.

Dan Dyer
fuente
¡Me encantaría ver un ejemplo de eso!
DivideByHero
1
Dan, lo malo de eso es que no es posible aceptar, por ejemplo, solo Pair <String, Integer> s debido al borrado de tipo, ¿no? Pero también lo es Java ...
Johannes Weiss
No creo que esto funcione para matrices simples, pero definitivamente funcionará para otras colecciones.
Programador fuera de la ley
Programador @Outlaw: Buen punto, puedes usarlo con matrices, pero es feo ya que tienes que usar un truco para crear matrices genéricas.
Dan Dyer
7

Al expandir las otras respuestas, un par inmutable genérico debe tener un método estático para evitar saturar su código con la llamada al constructor:

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);
      }
}

si nombra el método estático "of" o "pairOf", el código se vuelve fluido ya que puede escribir:

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

Es una verdadera lástima que las bibliotecas centrales de Java sean tan escasas en tales cosas que tienes que usar commons-lang u otros terceros para hacer cosas tan básicas. otra razón más para actualizar a scala ...

simbo1905
fuente
6

Iba a preguntar si no querrías simplemente usar un List<Pair<T, U>>? pero luego, por supuesto, el JDK no tiene una clase Pair <>. Pero un rápido Google encontró uno en Wikipedia y en foros.sun.com . Salud

JMD
fuente
6

La solución preferida como la ha descrito es una Lista de pares (es decir, Lista).

Para lograr esto, crearía una clase Pair para usar en su colección. Esta es una clase de utilidad útil para agregar a su base de código.

La clase más cercana en Sun JDK que proporciona una funcionalidad similar a una clase de par típica es AbstractMap.SimpleEntry. Podrías usar esta clase en lugar de crear tu propia clase Pair, aunque tendrías que vivir con algunas restricciones incómodas y creo que la mayoría de la gente desaprobaría esto porque no es realmente el papel previsto de SimpleEntry. Por ejemplo, SimpleEntry no tiene el método "setKey ()" y no tiene un constructor predeterminado, por lo que puede ser demasiado limitante.

Tenga en cuenta que las Colecciones están diseñadas para contener elementos de un solo tipo. Las interfaces de utilidad relacionadas, como Map, en realidad no son Colecciones (es decir, Map no implementa la interfaz de Colección) Un Par tampoco implementaría la interfaz de Colección, pero obviamente es una clase útil en la construcción de estructuras de datos más grandes.

Jeremy Rishel
fuente
Encuentro esta solución mucho mejor que la "ganadora" con el par genérico <K, V>. Hace todo lo solicitado y sale de la caja. Yo uso este para mi implementación.
Sauer
5

Esto se basa en el código de JavaHelp4u.

Menos detallado y muestra cómo hacerlo en una línea y cómo recorrer las cosas.

//======>  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
fuente
4

solo crea una clase como

class tuples 
{ 
int x;
int y;
} 

luego crea una lista de estos objetos de tuplas

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

para que también pueda implementar otras nuevas estructuras de datos de la misma manera.

usuario93
fuente
4

Quiero decir, aunque no hay Pairclase en Java, hay algo bastante similar:Map.Entry

Map.Entry Documentation

Esto es (simplificando bastante) qué HashMap, o en realidad cualquierMap tienda.

Puede crear una instancia de Mapalmacenar sus valores en ella y obtener el conjunto de entradas. Terminarás con unSet<Map.Entry<K,V>> que efectivamente es lo que quieres.

Entonces:

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
fuente
Esta opción tiene el problema de las claves únicas. Supongamos que tiene atributos de personas (por ejemplo, {(persona1, "ojos azules"), (persona1, "pelirrojo", (persona2, "miope"), (persona2, "pensador rápido")}) no poder almacenarlos como pares en un mapa, ya que cada persona es la clave del mapa y solo permitiría un atributo por persona.
manuelvigarcia
2

Spring tiene un Pair<S,T>tipo en el paquete Data Utilsorg.springframework.data.util

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

¿Qué pasa con com.sun.tools.javac.util.Pair?

René H.
fuente
77
Este tipo está en tools.jar, parte de la implementación "Sun" del compilador javac. Solo se distribuye como parte del JDK, puede no estar presente en las implementaciones de otros proveedores y es poco probable que sea parte de la API pública.
McDowell
0

Lo primero que tengo en mente cuando hablo de pares clave / valor es la clase de propiedades donde puede guardar y cargar elementos en una secuencia / archivo.

muka90
fuente
0

En el proyecto Reactor (io.projectreactor: reactor-core) hay soporte avanzado para n-Tuples:

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

Allí puede obtener t.getT1(), t.getT2(), ...especialmente con Stream o Flux, incluso puede asignar los elementos de tupla:

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