it-swarm.com.ru

Как преобразовать массив в HashMap с помощью Java 8 Stream

Я пишу функцию для преобразования массива в карту с помощью Java 8 Stream.

Вот что я хотел

public static <K, V> Map<K, V> toMap(Object... entries) {
    // Requirements:
    // entries must be K1, V1, K2, V2, .... ( even length )
    if (entries.length % 2 == 1) {
        throw new IllegalArgumentException("Invalid entries");
    }

    // TODO
    Arrays.stream(entries).????
}

Действительные использования

Map<String, Integer> map1 = toMap("k1", 1, "k2", 2);

Map<String, String> map2 = toMap("k1", "v1", "k2", "v2", "k3", "v3");

Неверные использования

Map<String, Integer> map1 = toMap("k1", 1, "k2", 2, "k3");

Любая помогает?

Спасибо!

8
Loc

Вы можете использовать

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

но это даст вам (основано) unchecked предупреждение. Ваш метод не может сдержать обещание вернуть правильно набранный Map<K, V> для массива произвольных объектов и, что еще хуже, он не потерпит неудачу за исключением, но тихо вернет несогласованную карту, если вы передадите объекты неправильного типа.

Чище, обычно используется, решение

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

Это может быть скомпилировано без предупреждения, так как правильность будет проверена во время выполнения. Код вызова должен быть адаптирован:

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

Помимо необходимости указывать фактические типы в качестве литералов класса, у него есть недостаток, заключающийся в том, что он не поддерживает универсальный ключ или типы значений (поскольку они не могут быть выражены как Class) и по-прежнему не имеет безопасности во время компиляции, только проверка во время выполнения.


Это стоит глядя на Java 9 . Там вы сможете сделать:

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

Это создаст карту immutable неопределенного типа, а не HashMap, но интересным моментом является API.

Существует метод <K,V> Map.Entry<K,V> entry(K k, V v), который можно комбинировать с
<K,V> Map<K,V> ofEntries(Map.Entry<? extends K,? extends V>... entries) для создания карты переменной длины (хотя varargs по-прежнему ограничены 255 параметрами).

Вы можете реализовать похожую вещь:

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

Удобные методы of реализованы единственным способом, это может быть сделано с помощью безопасности типов: как перегруженные методы с различным количеством аргументов, например

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 делает разрез на десять отображений, если у вас есть больше, вы должны использовать вариант ofEntries(entry(k1, v1), …)).

Если вы следуете этому шаблону, вы должны оставить свое имя toMap или использовать только map, а не вызывать «of», так как вы не пишете интерфейс Map.

Эти перегрузки могут выглядеть не очень элегантно, но они решают все проблемы. Вы можете написать код так же, как и в своем вопросе, без указания объектов Class, но при этом получить безопасность типов во время компиляции и даже отказ от попыток вызвать его с нечетным числом аргументов.

Вы должны сделать разрез на определенном количестве параметров, но, как уже отмечалось, даже varargs не поддерживают неограниченные параметры. И форма ofEntries(entry(…), …) не так уж плоха для больших карт.


Сборщик Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue) возвращает неопределенный тип карты, который может быть даже неизменным (хотя в текущей версии это HashMap). Если вы хотите иметь гарантию, что экземпляр HashMap будет возвращен, вы должны использовать вместо него Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1,v2)->{throw new IllegalArgumentException("duplicate key");}, HashMap::new).

8
Holger

Получение именно того, что вы хотите, вероятно, не будет работать для карт, тип ключа которых отличается от их типа значения. Это связано с тем, что объявление переменной Java (часть Object... entries) поддерживает только один тип.

Некоторые варианты приходят на ум:

  1. Вы можете выполнять проверки динамически и генерировать исключение недопустимого аргумента, если значения не совпадают. Но вы потеряете проверку типов компилятора.

  2. Вы можете определить класс Pair и немного поиграть со статическим импортом, чтобы получить почти то, что вы хотите:

например.: 

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) );
    }
}
2
Michael Bar-Sinai

Вот моя идея с помощью потока JDK 8:

public static <K, V> Map<K, V> toMap(final Object... entries) {
    // Requirements:
    // entries must be K1, V1, K2, V2, .... ( even length )
    if (entries.length % 2 == 1) {
        throw new IllegalArgumentException("Invalid entries");
    }

    final Map<K, V> map = new HashMap<>((int) (entries.length / 2 * 1.25 + 1));
    IntStream.range(0, entries.length / 2).forEach(i -> map.put((K) entries[i * 2], (V) entries[i * 2 + 1]));
    return map;

    // OR: 
    //    return IntStream.range(0, entries.length / 2).boxed().reduce(new HashMap<K, V>(), (m, i) -> {
    //        m.put((K) entries[i * 2], (V) entries[i * 2 + 1]);
    //        return m;
    //    }, (a, b) -> {
    //        a.putAll(b);
    //        return b;
    //    });
}

Если вы не возражаете против использования сторонней библиотеки AbacusUtil , код может быть упрощен до:

public static <K, V> Map<K, V> toMap2(final Object... entries) {
    // Requirements:
    // entries must be K1, V1, K2, V2, .... ( even length )
    if (entries.length % 2 == 1) {
        throw new IllegalArgumentException("Invalid entries");
    }

    return Stream.of(entries).split0(2).toMap(e -> (K) e.get(0), e -> (V) e.get(1));
}

И я думаю, что наиболее эффективный способ сделать это - цикл for, если вы не особенно стремитесь использовать Stream API

public static <K, V> Map<K, V> toMap3(final Object... entries) {
    // Requirements:
    // entries must be K1, V1, K2, V2, .... ( even length )
    if (entries.length % 2 == 1) {
        throw new IllegalArgumentException("Invalid entries");
    }

    final Map<K, V> map = new HashMap<>((int) (entries.length / 2 * 1.25 + 1));

    for (int i = 0, len = entries.length; i < len; i++) {
        map.put((K) entries[i], (V) entries[++i]);
    }

    return map;

    // OR just call the method in AbacusUtil.       
    // return N.asMap(entries);
}
1
user_3380739

Вы можете использовать что-то вроде литералов карты.
Для этого вы можете использовать фабричный метод:

// Creates a map from a list of entries
@SafeVarargs
public static <K, V> Map<K, V> mapOf(Map.Entry<K, V>... entries) {
    LinkedHashMap<K, V> map = new LinkedHashMap<>();
    for (Map.Entry<K, V> entry : entries) {
        map.put(entry.getKey(), entry.getValue());
    }
    return map;
}

// Creates a map entry
public static <K, V> Map.Entry<K, V> entry(K key, V value) {
    return new AbstractMap.SimpleEntry<>(key, value);
}

Наконец, вы можете сделать что-то вроде следующего:

public static void main(String[] args) {
    Map<String, Integer> map = mapOf(entry("a", 1), entry("b", 2), entry("c", 3));
    System.out.println(map);
}

Результат:

{a = 1, b = 2, c = 3}

Я надеюсь, что это дает вам правильный путь.

0
nazar_art
public static <K, V, E> Map<K, V> toMap(Function<E, K> toKey, Function<E, V> toValue, E[][] e){

        final Map<K, V> newMap = new HashMap<>();

        Arrays
                .stream(e, 0, e.length - 1)
                .forEach(s ->
                {
                    if (s[0] != null || s[1] != null)
                        newMap.put(toKey.apply(s[0]), toValue.apply(s[1]));
                }
                );

        return newMap;

}




public static void main(String[] args) {

        Object[][] objects = new Object[10][2];
        objects[0][0] ="Ahmet";
        objects[0][1] =28;
        objects[1][0] ="Mehmet";
        objects[1][1] =18;
        objects[2][0] ="Kemal";
        objects[2][1] =55;

Map<String, Integer> newMap = toMap((Object::toString), (Object v) -> Integer.parseInt(v.toString()), objects);

System.out.println(newMap.get("Ahmet") + " " + newMap.get("Kemal"));


}
0
Ahmet Emrebas