Saltar al contenido

Cómo convertir Array a HashMap usando Java 8 Stream

Solución:

Puedes utilizar

public static <K, V> Map<K, V> toMap(Object... entries) {
    if(entries.length % 2 == 1)
        throw new IllegalArgumentException("Invalid entries");
    return (Map<K, V>)IntStream.range(0, entries.length/2).map(i -> i*2)
        .collect(HashMap::new, (m,i)->m.put(entries[i], entries[i+1]), Map::putAll);
}

pero te dará un (fundado) desenfrenado advertencia. Su método no puede cumplir la promesa de devolver un escrito correctamente Map<K, V> para una matriz de objetos arbitrarios y, lo que es peor, no fallará con una excepción, pero devolverá silenciosamente un mapa inconsistente si pasa objetos del tipo incorrecto.

Una solución más limpia, de uso común, es

public static <K, V> Map<K, V> toMap(
                               Class<K> keyType, Class<V> valueType, Object... entries) {
    if(entries.length % 2 == 1)
        throw new IllegalArgumentException("Invalid entries");
    return IntStream.range(0, entries.length/2).map(i -> i*2)
        .collect(HashMap::new,
                 (m,i)->m.put(keyType.cast(entries[i]), valueType.cast(entries[i+1])),
                 Map::putAll);
}

Esto se puede compilar sin una advertencia, ya que la corrección se comprobará en tiempo de ejecución. El código de llamada debe adaptarse:

Map<String, Integer> map1 = toMap(String.class, Integer.class, "k1", 1, "k2", 2);
Map<String, String> map2 = toMap(
                           String.class, String.class, "k1", "v1", "k2", "v2", "k3", "v3");

Además de la necesidad de especificar los tipos reales como literales de clase, tiene la desventaja de no admitir tipos de valor o clave genéricos (ya que no se pueden expresar como Class) y aún no tiene seguridad en tiempo de compilación, solo una verificación en tiempo de ejecución.


Vale la pena mirar Java 9. Allí, podrá hacer:

Map<String, Integer> map1 = Map.of("k1", 1, "k2", 2);
Map<String, String>  map2 = Map.of("k1", "v1", "k2", "v2", "k3", "v3");

Esto creará un inmutable mapa de un tipo no especificado, en lugar de un HashMap, pero el punto interesante es la API.

Hay un metodo <K,V> Map.Entry<K,V> entry(K k, V v) que se puede combinar con
<K,V> Map<K,V> ofEntries(Map.Entry<? extends K,? extends V>... entries) para crear un mapa de longitud variable (aunque los varargs todavía están limitados a 255 parámetros).

Puedes implementar algo similar:

public static <K,V> Map.Entry<K,V> entry(K k, V v) {
    return new AbstractMap.SimpleImmutableEntry<>(k, v);
}
public static <K,V> Map<K,V> ofEntries(Map.Entry<? extends K,? extends V>... entries) {
    return Arrays.stream(entries)
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}

El (los) método (s) de conveniencia of se implementan de la única manera, esto se puede hacer con seguridad de tipos: como métodos sobrecargados con diferentes números de argumentos, como

public static <K,V> Map<K,V> of() {
    return new HashMap<>();// or Collections.emptyMap() to create immutable maps
}
static <K,V> Map<K,V> of(K k1, V v1) {
    return ofEntries(entry(k1, v1));
}
static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2) {
    return ofEntries(entry(k1, v1), entry(k2, v2));
}
static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2, K k3, V v3) {
    return ofEntries(entry(k1, v1), entry(k2, v2), entry(k3, v3));
}
static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) {
    return ofEntries(entry(k1, v1), entry(k2, v2), entry(k3, v3), entry(k4, v4));
}   
static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) {
    return ofEntries(entry(k1, v1), entry(k2, v2), entry(k3, v3), entry(k4, v4));
}   

(Java 9 hace el corte en diez asignaciones, si tiene más, debe usar el ofEntries(entry(k1, v1), …) variante).

Si sigue este patrón, debe mantener su toMap nombre o use solo map, en lugar de llamar al “of“, Ya que no está escribiendo el Map interfaz.

Es posible que estas sobrecargas no parezcan muy elegantes, pero resuelven todos los problemas. Puede escribir el código como en su pregunta, sin especificar Class objetos, pero obtienen seguridad de tipo en tiempo de compilación e incluso rechazo de intentos de llamarlo con un número impar de argumentos.

Debe realizar un corte en una cierta cantidad de parámetros, pero, como ya se señaló, incluso los varargs no admiten parámetros ilimitados. Y el ofEntries(entry(…), …) la forma no es tan mala para mapas más grandes.


El coleccionista Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue) devuelve un tipo de mapa no especificado, que incluso puede ser inmutable (aunque es un HashMapen la versión actual). Si desea tener la garantía de que un HashMap se devuelve la instancia, tienes que usar Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1,v2)->{throw new IllegalArgumentException("duplicate key");}, HashMap::new) en lugar de.

Obtener exactamente lo que desea probablemente no funcione para mapas cuyo tipo de clave difiera de su tipo de valor. Esto se debe a que la declaración de aridad variable de Java (la Object... entries part) admite solo un tipo.

Algunas opciones me vienen a la mente:

  1. Puede hacer las comprobaciones dinámicamente y lanzar una excepción de argumento ilegal si los valores no coinciden. Pero perderá la verificación de tipos del compilador.

  2. Podrías definir un Pair class, y juegue un poco con la importación estática para obtener casi lo que desea:

p.ej:

class Pair<K,V> {
    final K k;
    final V v;
    Pair( K ak, V av) {
        k=ak;
        v=av;
    }
    static <A,B> Pair<A,B> p(A a, B b) {
        return new Pair(a,b);
    }
}

public class JavaTest8 {

    <K,V> Map<K,V> toMap( Pair<K,V>... pairs ) {
        return Arrays.stream(pairs).collect(Collectors.toMap(p->p.k, p->p.v));
    }

    public static void main(String[] args) {
        // Usage
        Map<String,Integer> sti = toMap( p("A",1), p("B",2) );
        Map<Integer,Boolean> itb = toMap( p(1,true), p(42,false) );
    }
}
¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *