2017-01-30 5 views
8

Я пишу функцию для преобразования массива в Map с использованием Java 8 Stream.Как преобразовать массив в HashMap с помощью 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"); 

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

Спасибо!

+3

лучше просто использовать добрые старые 'for' loop :) – ZhongYu

+2

Возможно, вы найдете то, что ищете: [собрать последовательные пары из потока] (http://stackoverflow.com/questions/20470010/collect-successive -пар-из-а-потока). – MikaelF

+0

Ссылка https://stackoverflow.com/questions/31693781/convert-string-array-to-map-using-java-8-lambda-expressions –

ответ

6

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

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

, но это даст вам (основано) непроверенной предупреждения. Ваш метод не может сдержать обещание вернуть правильно напечатанный 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) и по-прежнему не имеют безопасности во время компиляции, а только проверку времени выполнения.


Это стоит looking at Java 9. Там вы сможете сделать:

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

Это создаст непреложной карты неопределенного типа, а не 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) создать карту переменной длины (переменные аргументы по-прежнему ограничены 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).

+0

Благодарим за очень подробное решение! – Loc

1

Вот моя идея по 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)); 
} 

И я думаю, самый эффективный способ сделать это - для цикла, если вы не используете прецедент с использованием 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); 
} 
+0

@Holger: Спасибо за очень хорошее решение! Upvoted! – Loc

2

Как точно то, что вы хотите, возможно, не будет работать для карт, тип которых отличается от их типа значений. Это связано с тем, что объявление переменной arity 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)); 
    } 
} 
+0

Спасибо. Я по-прежнему предпочитаю передавать ключи, значения вместо пары. – Loc

0

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

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

Выход:

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

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

+0

Спасибо. Я все же предпочитаю передавать ключи, значения вместо Map.Entry. – Loc

 Смежные вопросы

  • Нет связанных вопросов^_^