2015-05-29 3 views
16

Использование Java 8 lambdas, что является «лучшим» способом для эффективного создания нового List<T> с учетом List<K> возможных ключей и Map<K,V>? Это сценарий, в котором вам дается List возможных ключей Map и, как ожидается, будет генерировать List<T>, где T - это некоторый тип, который построен на основе некоторого аспекта V, типов значений карты.Как создать Список <T> с карты <K,V> и список <K> ключей?

Я исследовал несколько и не чувствую себя комфортно, заявляя, что один путь лучше другого (с одним исключением - см. Код). Я уточню «лучший» как комбинацию четкости кода и эффективности выполнения. Это то, что я придумал. Я уверен, что кто-то может сделать лучше, что является одним из аспектов этого вопроса. Мне не нравится аспект filter, так как это означает необходимость создания промежуточных структур и нескольких проходов над именами List. Прямо сейчас, я выбираю пример 6 - простой цикл ol. (Примечание: Некоторые загадочные мысли в комментариях к коду, особенно «нужно ссылаться извне ...» Это означает, что внешний от лямбда.)

public class Java8Mapping { 
    private final Map<String,Wongo> nameToWongoMap = new HashMap<>(); 
    public Java8Mapping(){ 
     List<String> names = Arrays.asList("abbey","normal","hans","delbrook"); 
     List<String> types = Arrays.asList("crazy","boring","shocking","dead"); 
     for(int i=0; i<names.size(); i++){ 
      nameToWongoMap.put(names.get(i),new Wongo(names.get(i),types.get(i))); 
     } 
    } 

    public static void main(String[] args) { 
     System.out.println("in main"); 
     Java8Mapping j = new Java8Mapping(); 
     List<String> testNames = Arrays.asList("abbey", "froderick","igor"); 
     System.out.println(j.getBongosExample1(testNames).stream().map(Bongo::toString).collect(Collectors.joining(", "))); 
     System.out.println(j.getBongosExample2(testNames).stream().map(Bongo::toString).collect(Collectors.joining(", "))); 
     System.out.println(j.getBongosExample3(testNames).stream().map(Bongo::toString).collect(Collectors.joining(", "))); 
     System.out.println(j.getBongosExample4(testNames).stream().map(Bongo::toString).collect(Collectors.joining(", "))); 
     System.out.println(j.getBongosExample5(testNames).stream().map(Bongo::toString).collect(Collectors.joining(", "))); 
     System.out.println(j.getBongosExample6(testNames).stream().map(Bongo::toString).collect(Collectors.joining(", "))); 
    } 

    private static class Wongo{ 
     String name; 
     String type; 
     public Wongo(String s, String t){name=s;type=t;} 
     @Override public String toString(){return "Wongo{name="+name+", type="+type+"}";} 
    } 

    private static class Bongo{ 
     Wongo wongo; 
     public Bongo(Wongo w){wongo = w;} 
     @Override public String toString(){ return "Bongo{wongo="+wongo+"}";} 
    } 

    // 1: Create a list externally and add items inside 'forEach'. 
    //  Needs to externally reference Map and List 
    public List<Bongo> getBongosExample1(List<String> names){ 
     final List<Bongo> listOne = new ArrayList<>(); 
     names.forEach(s -> { 
        Wongo w = nameToWongoMap.get(s); 
        if(w != null) { 
         listOne.add(new Bongo(nameToWongoMap.get(s))); 
        } 
       }); 
     return listOne; 
    } 

    // 2: Use stream().map().collect() 
    // Needs to externally reference Map 
    public List<Bongo> getBongosExample2(List<String> names){ 
     return names.stream() 
       .filter(s -> nameToWongoMap.get(s) != null) 
       .map(s -> new Bongo(nameToWongoMap.get(s))) 
       .collect(Collectors.toList()); 
    } 

    // 3: Create custom Collector 
    // Needs to externally reference Map 
    public List<Bongo> getBongosExample3(List<String> names){ 
     Function<List<Wongo>,List<Bongo>> finisher = list -> list.stream().map(Bongo::new).collect(Collectors.toList()); 
     Collector<String,List<Wongo>,List<Bongo>> bongoCollector = 
       Collector.of(ArrayList::new,getAccumulator(),getCombiner(),finisher, Characteristics.UNORDERED); 

     return names.stream().collect(bongoCollector); 
    } 
    // example 3 helper code 
    private BiConsumer<List<Wongo>,String> getAccumulator(){ 
     return (list,string) -> { 
      Wongo w = nameToWongoMap.get(string); 
      if(w != null){ 
       list.add(w); 
      } 
     }; 
    } 
    // example 3 helper code 
    private BinaryOperator<List<Wongo>> getCombiner(){ 
     return (l1,l2) -> { 
      l1.addAll(l2); 
      return l1; 
     }; 
    } 

    // 4: Use internal Bongo creation facility 
    public List<Bongo> getBongosExample4(List<String> names){ 
     return names.stream().filter(s->nameToWongoMap.get(s) != null).map(s-> new Bongo(nameToWongoMap.get(s))).collect(Collectors.toList()); 
    } 

    // 5: Stream the Map EntrySet. This avoids referring to anything outside of the stream, 
    // but bypasses the lookup benefit from Map. 
    public List<Bongo> getBongosExample5(List<String> names){ 
     return nameToWongoMap.entrySet().stream().filter(e->names.contains(e.getKey())).map(e -> new Bongo(e.getValue())).collect(Collectors.toList()); 
    } 

    // 6: Plain-ol-java loop 
    public List<Bongo> getBongosExample6(List<String> names){ 
     List<Bongo> bongos = new ArrayList<>(); 
     for(String s : names){ 
      Wongo w = nameToWongoMap.get(s); 
      if(w != null){ 
       bongos.add(new Bongo(w)); 
      } 
     } 
     return bongos; 
    } 
} 
+1

У вас есть 'K' и' V', но что такое 'T'? – user2357112

+0

@ user2357112 Отредактировано. T - некоторый тип не на карте, а построенный с использованием значений карты. Надеюсь, это поможет. – MadConan

+3

plain-ol-java для победы! – ZhongYu

ответ

11

Если namesToWongoMap является переменным экземпляром, вы не можете действительно избегайте захвата лямбды.

Вы можете очистить поток, разделив эти операции немного больше:

return names.stream() 
    .map(n -> namesToWongoMap.get(n)) 
    .filter(w -> w != null) 
    .map(w -> new Bongo(w)) 
    .collect(toList()); 
return names.stream() 
    .map(namesToWongoMap::get) 
    .filter(Objects::nonNull) 
    .map(Bongo::new) 
    .collect(toList()); 

Таким образом, вы не звоните get дважды.

Это очень похоже на цикл for, за исключением, например, теоретически его можно распараллелить, если namesToWongoMap не может быть мутирован одновременно.

Я не люблю filter аспекта наиболее как это означает, что необходимость создавать промежуточные структуры и несколько проходов над именами List.

Промежуточных структур нет и есть только один проход над List. В потоковом конвейере говорится: «для каждого элемента ... выполните эту последовательность операций». Каждый элемент посещается один раз и применяется конвейер.

Вот некоторые соответствующие цитаты из java.util.stream package description:

поток не является структурой данных, которая хранит элементы; вместо этого он передает элементы из источника, такого как структура данных, массив, функция генератора или канал ввода-вывода, через конвейер вычислительных операций.

Обработка потоков лениво позволяет значительно повысить эффективность; в конвейере, таком как приведенный выше пример фильтра-карты, фильтрация, сопоставление и суммирование могут быть объединены в один проход данных с минимальным промежуточным состоянием.

+0

Ницца. Простой и чистый. Мне это нравится! – MadConan

+1

Я думаю, что я собираюсь сидеть на этом в течение дня или двух и видеть, как другие звонят. Мне интересно узнать, может ли кто-нибудь придумать что-то лучше, хотя я в этом сомневаюсь. :) – MadConan

3

Один подход, который я не вижу retainAll:

public List<Bongo> getBongos(List<String> names) { 
    Map<String, Wongo> copy = new HashMap<>(nameToWongoMap); 
    copy.keySet().retainAll(names); 

    return copy.values().stream().map(Bongo::new).collect(
     Collectors.toList()); 
} 

Дополнительная карта является минимальным падением производительности, так как это просто копируя указатели на объекты, а не сами объекты.

+1

Weeelllll ... Я думаю, что есть * относительно * большое количество накладных расходов, связанных с созданием всех узлов для каждой записи. Отключение [этой старой статьи] (http://www.javacodegeeks.com/2010/08/java-best-practices-vector-arraylist.html). Но ваш ответ, если хороший и чистый, и накладные расходы не могут быть фактором. – MadConan

7

Radiodef's answer довольно много прибиты гвоздем, думаю. Приведенное решения есть:

return names.stream() 
    .map(namesToWongoMap::get) 
    .filter(Objects::nonNull) 
    .map(Bongo::new) 
    .collect(toList()); 

, вероятно, о самом лучшем, что может быть сделано в Java 8.

я хочу упомянуть небольшие морщины в этом, хотя. Вызов Map.get возвращает null, если имя отсутствует на карте, и это впоследствии отфильтровывается. Нет ничего плохого в этом как таковой, хотя он испекает семантику нулевой середины-не-настоящего в структуру конвейера.

В каком-то смысле нам нужна операция трубопровода картера, у которой есть выбор возврата нуля или одного элемента. Способ сделать это с потоками - с flatMap. Функция flatmapper может возвращать произвольное количество элементов в поток, но в этом случае мы хотим только нуль или один. Вот как это сделать:

Я признаю, что это довольно неуклюже, и поэтому я бы не рекомендовал это делать. Немного лучше, но немного непонятная подход заключается в следующем:

return names.stream() 
    .flatMap(name -> Optional.ofNullable(nameToWongoMap.get(name)) 
          .map(Stream::of).orElseGet(Stream::empty)) 
    .map(Bongo::new) 
    .collect(toList()); 

, но я все еще не уверен, что я рекомендовал бы это, как он стоит.

Использование flatMap действительно указывает на другой подход. Если у вас есть более сложная политика, касающаяся того, как бороться с непредвиденным случаем, вы можете реорганизовать это в вспомогательную функцию, которая возвращает поток, содержащий результат, или пустой поток, если результата нет.

Наконец, JDK 9 - еще в стадии разработки, как это письмо - добавил Stream.ofNullable, который полезен в точно таких ситуациях:

return names.stream() 
    .flatMap(name -> Stream.ofNullable(nameToWongoMap.get(name))) 
    .map(Bongo::new) 
    .collect(toList()); 

Как и в стороне, JDK 9 также добавил Optional.stream, который создает ноль или один поток от Optional. Это полезно в тех случаях, когда вы хотите вызвать функцию необязательного возврата из flatMap. См. this answer и this answer для более подробного обсуждения.

+2

Я уже добавил [StreamEx.ofNullable (obj)] (http://amaembo.github.io/streamex/javadoc/javax/util/streamex/StreamEx.html#ofNullable-T-) и [StreamEx.of (необязательно) ] (HTTP: //amaembo.github.io/streamex/javadoc/javax/util/streamex/StreamEx.html # of-java.util.Optional-) в мою библиотеку. Фактически даже без сторонних библиотек и перехода на JDK9 любой пользователь может создавать подобные статические методы в некоторых классах, специфичных для проекта, и использовать их. –

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