2016-04-06 3 views
1

Я пытался превратить этуJava собирает с примером выражения лямбды

String r = ""; 
for (Persona p : list) { 
    r += p.lastName; 
} 

Для stream().filter.collect() формы, но я хочу знать, как писать collect с лямбдой-выражение (не ссылки метода). Я не мог найти хороший пример.

Это то, что у меня есть

class B { 
    public static void main(String ... args) { 
     List<Person> p = Arrays.asList(
       new Person("John", "Wilson"), 
       new Person("Scott", "Anderson"), 
       new Person("Bruce", "Kent")); 

     String r; 
     String s = p.stream() 
      .filter(p -> p.lastName.equals("kent")) 
      .collect((r, p) -> r += p.lastName);/// ????? 
    } 
} 
class Person { 
    String name; 
    String lastName; 
    public Person(String name, String lastName) { 
     this.name = name; 
     this.lastName = lastName; 
    } 
} 

Все примеры, которые я нахожу используют ссылки метод, тогда как я думаю должна быть простой способ, чтобы написать лямбда-выражение вместо этого.

+4

Вы смотрели на 'Collectors.joining()' или вопрос о его реализации? –

+0

http://stackoverflow.com/questions/31456898/convert-a-for-loop-to-concat-string-into-a-lambda-expression – Savior

+0

Вопрос в том, как использовать выражение лямбда, например: '. collect ((acc, p) -> acc + = p.lastName); ' – OscarRyz

ответ

2

Предполагая, что вы не хотите использовать готовый коллекционер, например Collectors.joining(), вы действительно можете создать свой собственный коллекционер.

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

Предполагая, что вы хотите использовать первую версию, принимая 3 лямбда-выражения, вы заметите, прочитав javadoc, что результат должен быть изменчивым объектом, а String не изменен. Поэтому вместо этого вы должны использовать StringBuilder. Например:

StringBuilder s = 
    p.stream() 
    .filter(p -> p.lastName.equals("kent")) 
    .map(p -> p.lastName) 
    .collect(StringBuilder::new, 
       StringBuilder::append, 
       StringBuilder::append); 

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

StringBuilder s = 
    p.stream() 
    .filter(p -> p.lastName.equals("kent")) 
    .map(p -> p.lastName) 
    .collect(() -> new StringBuilder(), 
       (stringBuilder, string) -> stringBuilder.append(string), 
       (stringBuilder1, stringBuilder2) -> stringBuilder1.append(stringBuilder2)); 
+0

Да, это именно то, что я искал. Итак, первое выражение говорит о том, как создавать объекты, используемые двумя другими выражениями, второе - как накапливать, а третье - как объединить накопленный результат со следующим накопленным результатом? В этот момент не было бы смысла использовать 'сокращение' вместо этого? – OscarRyz

+1

Это подробно обсуждается в документе: https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html#MutableReduction. Использование метода reduce() приведет к созданию множества копий String, что значительно сократит сокращение, чем измененное сокращение. –

+0

Обратите внимание, что 'Collectors.joining()' равнозначен 'Collector.of (StringBuilder :: new, StringBuilder :: append, StringBuilder :: append, StringBuilder :: toString)', отличающийся только тем, что функция финишера превратится 'StringBuilder' в' String'. – Holger

2

Вы также можете использовать функцию .reduce(), это легче читать, чем собирать.

String s = p.stream() 
     .filter(p -> p.lastName.equals("kent")) 
     .reduce("", (acc, p) -> acc + p.lastName(), String::concat); 

Версия с StringBuilder:

StringBuilder s = p.stream() 
     .filter(p -> p.lastName.equals("kent")) 
     .reduce(new StringBuilder(), (acc, p) -> acc.append(p.lastName()), StringBuilder::append); 
+1

Но это намного, намного, намного медленнее. Как это отвечает на вопрос, BTW? –

+1

Это не скомпилируется. 'reduce' требует' BinaryOperator ', но для' Persona' нет оператора '+'. –

+0

правильно, но «преждевременная оптимизация - корень всех злых» Я бы предпочел, читаемость. Но его можно настроить с помощью StringBuilder/Buffer –

1

Возможно, вы были в reduce представляя операцию, а не собирать, что позволило бы использовать лямбда-выражения, как вы планировалось. Например,

String s = persons.stream() 
     .map((s1, str) -> s1.concat(str)) 
     .reduce("", String::concat); 

Это упражнение, конечно. Конкатенация строк неэффективна, поэтому было бы лучше использовать collect(Collectors.joining()), в качестве аккумулятора которой используется StringBuffer.

+0

В чем смысл использования 'StringBuil..Buffer', если вы создаете его на каждой итерации? – nyxz

+0

Хорошая точка @nyxz, и спасибо. –