java8 stream
TRANSCRIPT
Java 8 Stream@kojilin
2014/3/29@TWJUG
Stream•java.util.stream •Support functional-style operations on
streams of elementsint sum = widgetList.stream() .filter(b -> b.color == RED) .mapToInt(b -> b.getWeight()) .sum();
External Iteration
long sum = 0;
for(int i = 0; i < N; ++i){
sum += i * i;
}
Internal Iteration
long sum = LongStream
.range(0, N)
.map(i -> i * i)
.sum();
•為什麼需要 Stream ?
•從外部迭代轉為內部迭代
•外部迭代需要注意要做什麼跟如何做
•內部迭代著重在要做什麼比如何做更多
•將行為資料化
•讓函式庫幫你更多的忙
•讓程式碼更簡潔好讀
Stream•Stream 不是資料結構,沒有儲存資料
•包裹在現有的資料結構之上
•透過內部迭代
•Multi-thread
•Laziness
•Stream 有 iterator 方法,但沒有實作 Iterable
•所以不能寫 for (String item : stream)
Stream 的種類•Stream<T>
•Primitive Streams
•IntStream
•DoubleStream
•LongStream
•彼此之間可以透過像 mapToInt, mapToDouble, mapToObj 等方式切換
Stream 的使用方式
long sum = LongStream
.range(0, N)
.map(i -> i * i)
.filter(i -> i % 2 == 0)
.sum();•建立 Stream
Stream 的使用方式
long sum = LongStream
.range(0, N)
.map(i -> i * i)
.filter(i -> i % 2 == 0)
.sum();•透過連續的 intermediate operation 操作
Stream 的使用方式
long sum = LongStream
.range(0, N)
.map(i -> i * i)
.filter(i -> i % 2 == 0)
.sum();•最後用 terminal operation 結束
如何取得 Stream?•Collection#stream() •Arrays.stream(Object[]) •Stream.of(Object[]) •IntStream.range(int, int) •BufferedReader#lines() •Stream#filter() •etc.
Intermediate Operation•回傳 Stream,可以接下一個 operation
•Lazy
•直到遇到 terminal operation 的方法前不會有效果
•filter
•map
•flatMap
•sorted
stream.filter( p -> p > 20);
stream.map( p -> p.name);
stream.flatMap( p -> p.cars.stream());
stream.sorted(p1, p2 -> p1.name.compareTo(p2.name));
•limit
•distinct
•skip
stream.limit(5);
stream.distinct();
stream.skip(5);
Stateful & Stateless•Stateful 表示在操作元素時,除了當下的元
素外需要考慮其他狀態
•sorted, skip, limit
•Stateless 表示在操作元素時,除了當下的元素外不用考慮其他狀態
•filter, map
Terminal Operation•不會回傳 stream,整個 stream 只能有一個
terminal operation 呼叫
•呼叫後才會開始走查 stream,最後產生並回傳結果或是製造 side-effect
•呼叫完 terminal operation 後 stream 會被當作被使用完畢
•Stream -> (Intermediate)* -> Terminal
•forEach
•reduce
•collect
•count
stream.forEach( p -> print(p));
stream.reduce( 0, (a, b) -> a + b);
stream.collect( Collectors.toList());
stream.count();
•toArray
•max
•sum ( Primitive Stream )
•average ( Primitive Stream )
stream.toArray(String[]::new);
stream.max(String::compareTo);
intStream.sum();
intStream.average();
Reduction•將元素組合成一個結果
•reduce, collect, sum, max, count
intStream.sum();
numbers.stream().reduce(0, (x,y) -> x + y);
<U> U reduce(
U identity,
BiFunction<U, ? super T, U >
accumulator,
BinaryOperator<U> combiner);
Stream<T>#reduce
•accumulator 和 combiner 每次處理完元素後,必須回傳新的值
Integer result = stream.reduce(0,
(sum, b) -> sum + b.getWeight(),
Integer::sum);
Stream<T>#reduce
list.stream().reduce(
new ArrayList<>(),
(integers, o) -> {
integers.add(o);
return integers;
},
(integers, integers2) -> {
integers.addAll(integers2);
return integers;
}); ❌
list.stream().reduce(
new ArrayList<>(),
(integers, o) -> {
ArrayList<Integer> list = new
ArrayList<>(integers);
list.add(o);
return list;
},
(integers, integers2) -> {
ArrayList<Integer> list = new
ArrayList<>(integers);
list.addAll(integers2);
return list;
});△
Mutable Reduction•將元素累加到 mutable 的容器中
•Stream#collect
•和 reduce 不同,是改變已經存在的值
<R> R collect(
Supplier<R> supplier,
BiConsumer<R,? super T>
accumulator,
BiConsumer<R,R> combiner);
Stream<T>#collect
•accumulator 和 combiner 都是 consumer
ArrayList<String> asList =
stringStream.collect(
ArrayList::new,
ArrayList::add,
ArrayList::addAll);
Stream<T>#collect
<R> R collect(
Collector<? super T,A,R>
collector);
Stream<T>#collect
•Collector 並不是很容易實作
•所以 Collectors 提供了許多預設的方法提供常用的 Collector
•toList, groupingBy, toSet, toMap, counting
stream.collect(Collectors.toList());
stream.collect(Collectors .groupingBy(…));
strStream.collect(Collectors .join(","));
???
????
!
List<Item> items = new ArrayList<>…;
Map<String, List<Item>> result = items.stream().collect(groupingBy( Item::getOwner));
!
>> {owner1=[i1, i2], owner2=[i3],…}
!
List<Item> items = new ArrayList<>…;
Map<String, Set<String>> result = items.stream().collect(groupingBy( Item::getOwner, toSet()));>> {owner1=[i1], owner2=[i3], …}
List<Item> items = new ArrayList<>…;
Map<String, Set<String>> result = items.stream().collect(groupingBy( Item::getOwner, mapping(Item::getName(), toSet())));
!
>> {owner1=[name1], owner2=[name2],…}
Lazy EvaluationList<Integer> items = Arrays.asList(1, 2, 3, 4);
items.stream()
.filter( i -> { sout("A"+i); i % 2 == 0; })
.map( i -> { sout("B"+i); return i; })
.map( i -> { sout("C"+i); return i; });
•不會印出東西
Lazy EvaluationList<Integer> items = Arrays.asList(1, 2, 3, 4);
items.stream()
.filter( i -> { sout("A"+i); i % 2 == 0; })
.map( i -> { sout("B"+i); return i; })
.map( i -> { sout("C"+i); return i; })
.collect(toList());
Lazy EvaluationList<Integer> items = Arrays.asList(1, 2, 3, 4);
items.stream()
.filter( i -> { sout("A"+i); i % 2 == 0; })
.map( i -> { sout("B"+i); return i; })
.map( i -> { sout("C"+i); return i; })
.collect(toList());
>> A1, A2, B2, C2, A3, A4, B4, C4
ParallelList<Integer> items = Arrays.asList(1, 2, 3, 4);
items.parallelStream()
.filter( i ->
{ sout("A"+i); return i % 2 == 0; })
.map( i -> { sout("B"+i); return i; })
.map( i -> { sout("C"+i); return i; })
.collect(toList());
>> A1, A3, A4, A2, B4, B2, C2, C4
Short-circuit Operation•Intermediate operation
•能將無限的輸入轉換成有限的 stream
•limitlong sum = IntStream
.iterate(1, n -> n+1)
.limit(10)
.sum();
Short-circuit Operation•Terminal operation
•能將無限的輸入, 在有限時間內結束
•findFirst, anyMatch, allMatchlong sum = IntStream
.iterate(1, n -> n+1)
.filter(i -> i > 100)
.findFirst();
Example 1•找出第一個 age > 20 的 Student
for (Student student : students) {
if (student.age > 20) {
return student;
}
}
}
Optional<Student> student = students.stream()
.filter(s -> s.age > 20)
.findFirst();
Example 2•尋找 age > 20,成績 > 90 的 10 位
Student 的 id
List<String> result = …;
for (Student student : students) {
if (student.age > 20 && student.grade > 90) {
result.add(student.id);
if(result.size() >= 10){
break;
}
}
}
List<String> result = students.stream()
.filter(student -> student.age > 20)
.filter(student -> student.grade > 90)
.limit(10)
.map(student -> student.id)
.collect(Collectors.toList());
Example 3•找出所有 Article 評論大於 20 的
Category,並依照名稱排序。
List<Category> result = …;
for (Category category : categories) {
for (Article a : c.getArticles()) {
if (a.getCommentCount() >= 20) {
result.add(c);
break;
}
}
}
Collections.sort(result, Comparator.comparing(c -> c.getName()));
categories.stream()
.filter(c -> c.getArticles()
.stream()
.anyMatch(a -> a.getCommentCount() >= 20))
.sorted(Comparator.comparing(c -> c.getName()))
.collect(Collectors.toList());
Example 4•將 Person 依照年齡分類到不同 List
•Map<Integer, List< Person >>
Map<Integer, List<Person>> result = …;
for (Person person : people) {
result.computeIfAbsent(person.age,
t -> new ArrayList<>())
.add(person);
}
Map<Integer,List<Person>> peopleByAge =
people.stream()
.collect(groupingBy(Person::getAge));
•Stream 走查很容易跟預期的不同,所以操作過程要避免產生副作用
•順序,走了哪些元素,是否並行
注意
ArrayList<String> results = new ArrayList<>();
stream.filter(…) .forEach(s -> results.add(s));
List<String>results = stream .filter(…) .collect(Collectors.toList());
Side Effects
Set<Integer> seen = Collections.synchronizedSet( new HashSet<>());
stream.parallel().map(e -> { if (seen.add(e)) return 0; else return e; })...
Stateless Behaviors
List<String> source = ...;
source.stream() .filter(s -> { source.add(...); return s.length() > 10; }) ...
Non-inferences•走查過程中不該動到來源資料結構
•除非該資料結構是支援 concurrent 存取
•使用 Stream 的方式和外部迭代差異不小
•很多方法只看參數型態很難理解,從方法和變數名稱去理解使用方式
•只能多寫多習慣
•通常寫完後會有較佳的可讀性
最後