Guía Definitiva de Pares Java - Trabajando con Tuplas en Java

En esta guía, aprenda qué son las tuplas y cómo crear y trabajar con tuplas/pares en Java, a través de paquetes principales, bibliotecas de terceros y una implementación genérica personalizada de pares/tuplas.

Tuplas en Java

Las tuplas son secuencias inmutables y ordenadas de elementos. En ese sentido, son similares a las listas inmutables; sin embargo, comúnmente, las tuplas se usan para representar pares en ingeniería de software. Vale la pena señalar que no se limitan a ser pares y pueden tener una longitud de n. Java tiene un gran soporte para listas inmutables (y otras colecciones), pero no para pares.

{.icon aria-hidden=“true”}

Los pares pueden ser un mapeo de clave-valor entre dos elementos, o simplemente un par de dos elementos que se devuelven desde un método.

Los pares se vuelven extremadamente útiles cuando desea devolver múltiples valores de un método. Por ejemplo:

1
mean, std = getStats()

Técnicamente, podemos devolver mapeos clave-valor entre elementos, o una secuencia de 2 elementos en Java con una implementación Map o List, pero es difícil trabajar con ellos en el contexto de las tuplas. y pares:

1
2
Map<Float, Float> meanStdMap = getStatsMap();
List<Float> meanStdList = getStatsList();

Ni un ‘Mapa’, ni una ‘Lista’, simplemente hablando, sirven para esto. Puede imponer la inmutabilidad para obtener el efecto de las tuplas, haciendo que los métodos getStats_() devuelvan colecciones no modificables:

1
2
3
4
5
6
7
public static Map<Float, Float> getStatsMap() {
    return Collections.unmodifiableMap(new HashMap<Float, Float>());
}

public static List<Float> getStatsList() {
    return Collections.unmodifiableList(new ArrayList<>());
}

¡Pero esto finalmente se siente como una solución para una característica inherentemente faltante del lenguaje! Desafortunadamente, al momento de escribir, todas las soluciones son una solución alternativa para una función inherentemente faltante, aunque algunas son más torpes que otras.

En esta guía, veremos cómo crear y usar tuplas en Java, una función que no está integrada. Exploraremos paquetes y clases principales como Pair y AbstractMap.SimpleImmutableEntry, las bibliotecas de terceros codifican una clase personalizada simple.

javafx.util.Pair

{.icon aria-hidden=“true”}

Nota: A partir de JDK 11, JavaFX no se envía con la descarga de JDK predeterminada y se ha convertido en un paquete independiente. Tiene que ser descargado/importado como una dependencia por separado. Esto hace que el uso de Pair como una Tupla sea más engorroso.

La solución central para la clase Tuple que falta es un Pair. Reside en el paquete javafx.util y se agregó para representar pares nombre-valor, que son comunes en el desarrollo de software de escritorio y móvil. Aunque originalmente estaba destinado a ser utilizado en aplicaciones JavaFX, ¡la clase es altamente generalizable a otros dominios!

Es tan simple como se pone:

1
2
3
4
import javafx.util.Pair;

// public class Pair<K,V> implements Serializable {...}
Pair<String, Integer> pair = new Pair<>("Mean Value", 25);

Tanto la K (clave) como la V (valor) son genéricas, por lo que puede asignarles cualquier tipo. Se puede acceder a ambos a través de sus respectivos captadores:

1
2
3
4
System.out.printf("Key: %s, Value: %s%n", pair.getKey(), pair.getValue());
// Key: Mean Value, Value: 25
System.out.println(pair);
// Mean Value=25

Java inherentemente no puede devolver dos valores de un método, pero puede devolver un Pair, que es un contenedor para los dos:

1
2
3
public static Pair<String, Integer> getStats() {
    return new Pair<>("Mean Value", 25);
}

Un ‘Par’ es inmutable, como lo sería una Tupla, por lo que no hay funciones de establecimiento.

AbstractMap.SimpleImmutableEntry

Otra clase central que podría usar es la clase AbstractMap.SimpleImmutableEntry, aunque esta es incluso más una solución alternativa que la anterior y generalmente no se usa mucho. Está destinado a ayudar a crear implementaciones de mapas personalizadas, pero en un apuro, puede servir como una tupla.

El principal beneficio de usar una SimpleImmutableEntry en lugar de un Pair es que viene incluido en todas las versiones actuales de JDK, por lo que no tiene que descargar una dependencia externa ni degradar su versión de JDK.

{.icon aria-hidden=“true”}

Nota: Si bien existe una contraparte SimpleEntry, es mutable, por lo que usaremos la versión inmutable.

Podemos reescribir el método anterior como:

1
2
3
public static AbstractMap.SimpleImmutableEntry<String, Integer> getStats() {
    return new AbstractMap.SimpleImmutableEntry<>("Mean Value", 25);
}

Que luego se puede obtener y analizar como:

1
2
3
4
5
6
AbstractMap.SimpleImmutableEntry<String, Integer> stats = getStats();

System.out.printf("Key: %s, Value: %s%n", stats.getKey(), stats.getValue());
// Key: Mean Value, Value: 25
System.out.println(stats);
// Mean Value=25

Apache Commons

Apache Commons es una biblioteca ampliamente utilizada presente en muchos proyectos de Java, utilizada principalmente para métodos y clases de ayuda/conveniencia que amplían las capacidades oficiales de Java. Una de estas clases es la clase Pair, del paquete lang3.

Usando Maven, puede agregar la dependencia como:

1
2
3
4
5
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>${version}</version>
</dependency>

O, si estás usando Gradle:

1
implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.12.0'

¡Vale la pena señalar que la clase Pair de Apache se basa en Map.Entry<K, V>! Implementa la interfaz y proporciona una sola entrada similar a un mapa, en forma de un elemento izquierdo y derecho:

1
public abstract class Pair<L, R> implements Map.Entry<L, R> ... Serializable {...}

Un Pair se crea a través de su método of():

1
2
3
public static Pair<String, Integer> getStats() {
    return Pair.of("Mean Value", 25);
}

El acceso a los elementos se puede lograr a través de los captadores getLeft() y getRight(), lo que implica que no es un mapeo de clave-valor, sino una tupla similar a un par de dos elementos:

1
2
3
4
5
6
Pair<String, Integer> stats = getStats();

System.out.printf("Key: %s, Value: %s%n", stats.getLeft(), stats.getRight());
// Left: Mean Value, Right: 25
System.out.println(stats);
// (Mean Value,25)

Si ha trabajado con lenguajes como Python, este código devuelve un resultado más similar al que podría estar acostumbrado.

{.icon aria-hidden=“true”}

Nota: Aunque la implementación de Apache Common no parece un mapeo de clave-valor entre elementos, tiene un método setValue() interesante, que en última instancia establece la R ( elemento derecho) del Par, como si fuera un valor correspondiente a una clave.

También vale la pena señalar que este Pair es por defecto un ImmutablePair, y aunque el método setValue() existe públicamente, provoca una UnsupportedOperationException:

1
2
3
4
Pair<String, Integer> stats = getStats();
System.out.println(stats);
stats.setValue(15);
System.out.println(stats);
1
2
3
4
(Mean Value,25)
Exception in thread "main" java.lang.UnsupportedOperationException
    at org.apache.commons.lang3.tuple.ImmutablePair.setValue(ImmutablePair.java:202)
    at Main.main(Main.java:31)

Sin embargo, si usa un MutablePair, la operación se realizará correctamente.

tuplas de Java

Javatuples es una biblioteca más antigua, que vio su última actualización en 2011. Ya no se mantiene, pero funciona razonablemente bien como una biblioteca liviana que le permite solucionar la falta de tuplas en Java.

Se puede importar como una dependencia a través de Maven:

1
2
3
4
5
<dependency>
  <groupId>org.javatuples</groupId>
  <artifactId>javatuples</artifactId>
  <version>1.2</version>
</dependency>

O Gradle:

1
implementation 'org.javatuples:javatuples:1.2'

{.icon aria-hidden=“true”}

Nota: Es posible que tenga problemas para importar la dependencia a través de Maven. En ese caso, descargue el archivo JAR manualmente.

¡La biblioteca ofrece más de par tuplas! Ofrece tuplas de longitud de 1 a 10 - Unidad, Par, Triplete, ... Década. Todas estas clases son seguras, inmutables, serializables e iterables, por lo que está cubierto en todos los frentes.

Como era de esperar, funcionan de la misma manera que hemos usado otras variantes de Tuple:

1
2
3
public static Pair<String, Integer> getStats() {
    return Pair.with("Mean Value", 25);
}

Los valores en una ‘Tupla’ se almacenan en última instancia en una ‘Lista’, con varios métodos de envoltorio que le permiten acceder a ellos individualmente como dentro de una ‘Tupla’:

1
2
3
4
5
6
Pair<String, Integer> stats = getStats();

System.out.printf("Element_1: %s, Element_2: %s%n", stats.getValue(0), stats.getValue(1));
// Element_1: Mean Value, Element_2: 25
System.out.println(stats);
// ["Mean Value", 25]

Clase personalizada

Finalmente, puede optar por implementar su propia clase para representar tuplas. Las bibliotecas de terceros pueden estar fuera de la mesa para usted, o simplemente no quiere molestarse con la descarga de ninguna.

La clase Pair funciona razonablemente bien, y AbstractMap.SimpleImmutableEntry es simplemente extraño de usar, ya que no estaba destinado a ser usado como una tupla tipo par.

Afortunadamente, no es demasiado difícil implementar algo como esto, y puede optar por una clase contenedora simple o más compleja. La solución más fácil sería crear:

1
2
3
4
public class Tuple {
    private Object element1;
    private Object element2;
}

Sin embargo, esta solución no es muy maleable. Si está seguro de conocer los tipos de devolución, o si está creando un par especializado como objeto titular, este enfoque funcionaría. Sin embargo, para crear un Tuple similar a un par más general en Java, nos gustaría hacer algo como:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public class Tuple<E1, E2> {
    private final E1 e1;
    private final E2 e2;

    public Tuple(E1 e1, E2 e2){
        this.e1 = e1;
        this.e2 = e2;
    }

    public E1 getE1() {
        return e1;
    }

    public E2 getE2() {
        return e2;
    }

    public String toString() {
        return String.format("(%s, %s)", e1, e2);
    }
}

Dos elementos finales genéricos (inmutables), con un constructor que acepta dos elementos de cualquier tipo y getters para ellos. Esta clase se puede utilizar como:

1
2
3
public static Tuple<String, Integer> getStats() {
    return new Tuple("Mean Value", 25);
}

Y podemos extraer datos de la tupla como:

1
2
3
4
5
6
Tuple<String, Integer> stats = getStats();

System.out.printf("E1: %s, E2: %s%n", stats.getE1(), stats.getE2());
// E1: Mean Value, E2: 25
System.out.println(stats);
// (Mean Value, 25)

Conclusión

Las tuplas son secuencias inmutables y ordenadas de elementos. Se usan comúnmente para representar pares: tuplas de dos elementos. Java tiene un gran soporte para listas inmutables (y otras colecciones), pero no para pares.

En esta guía, hemos analizado qué son las tuplas y cómo los pares son un tipo específico de tupla. Echamos un vistazo a los paquetes principales en Java que se pueden usar para representar un par de puntos de datos y exploramos bibliotecas de terceros que ofrecen la misma funcionalidad básica.

Finalmente, implementamos nuestra propia clase Tuple genérica que se puede usar para representar pares de dos tipos cualesquiera.

Licensed under CC BY-NC-SA 4.0