文章目錄
-
- 一、前言
- 二、代碼
-
- 2.1 從最簡單的開始
-
-
-
- 2.1.1 源碼
- 2.1.2 使用示例
- 2.1.3 Person類
- 2.1.4 運作結果
-
-
- 2.2 接下來
-
-
-
- 2.2.1 源碼
- 2.2.2 使用示例
- 2.2.3 運作結果
-
-
- 2.3 自定義收集器
-
-
-
- 2.3.1 Collector接口源碼解釋
- 2.3.2 自定義收集器實作
- 2.3.3 使用示例
- 2.3.4 運作結果
-
-
一、前言
- 自從學習了Java 8函數是程式設計後,徹底的被Java 8是以吸引。其中最重要的一個優點就是Stream API 。stream和lambda表達式讓我們平時對集合的操作更加的便捷,省去複雜的for循環嵌套,和繁瑣的容器建立,讓代碼更加美觀。
- 在stream api 的終止操作中,Java已經為我們提供了許多友善好用的收集器,使我們能夠從流中收集到我們想要的結果。但是有的時候内置的收集器也可能不能滿足大家的需求,這個時候我們就可以利用Java預留的接口來定制自己的收集器了。
二、代碼
2.1 從最簡單的開始
- 怎麼用stream把一個Person類的集合按照年齡分組,得到結果Map<Integer,List< Person >>?
- 其實這個在平時的開發中用到的挺多的,用Collectors.groupingBy()收集器很好實作實。
- 這個方法需要傳入一個Function,也就是擷取key的Function
2.1.1 源碼
public static <T, K> Collector<T, ?, Map<K, List<T>>> groupingBy(Function<? super T, ? extends K> classifier) {
return groupingBy(classifier, toList());
}
2.1.2 使用示例
@Slf4j
public class NfzdTest {
@Test
public void test02() {
List<Person> data = this.getPersonList();
Map<Integer, List<Person>> collect = data.stream().collect(Collectors.groupingBy(Person::getAge));
collect.entrySet().forEach(a -> System.out.println("key: " + a.getKey() + " value: " + a.getValue()));
}
private List<Person> getPersonList() {
return Arrays.asList(
new Person("小花", 12),
new Person("小明", 14),
new Person("小馬", 14),
new Person("小壞", 13),
new Person("小紅", 13),
new Person("小趙", 14)
);
}
}
2.1.3 Person類
package com.lh.nfzd.model;
import com.lh.nfzd.common.annotation.CheckAge;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private String name;
@CheckAge(min = 5, max = 20)
private Integer age;
}
2.1.4 運作結果

2.2 接下來
- 怎麼從Person類的集合裡按照年齡分組,擷取姓名集合,得到結果Map<Integer,List< String >>。
- 剛開始看到這個其實我也不知道怎們辦,後來查了資料才有的結果。可以看到groupingBy方法的第二個參數是一個收集器,這個參數就是收集傳回的map的value的收集器。可以用Collectors.groupingBy()、Collectors.mapping()組合使用來實作。
2.2.1 源碼
public static <T, K, A, D> Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream) {
return groupingBy(classifier, HashMap::new, downstream);
}
2.2.2 使用示例
@Test
public void test03() {
List<Person> data = this.getPersonList();
Map<Integer, List<String>> collect = data.stream().collect(Collectors.groupingBy(Person::getAge, Collectors.mapping(Person::getName, Collectors.toList())));
collect.entrySet().forEach(a -> System.out.println("key: " + a.getKey() + " value: " + a.getValue()));
}
2.2.3 運作結果
2.3 自定義收集器
- 從上面的例子來看,其實我們想從stream中收集的結果各種各樣,當有些想收集的結果jdk幫我們實作的時候,是很完美的。但當我們的需求複雜時,想收集的結果也更複雜,jdk沒有幫我麼實作,這個時候怎麼辦呢?
- 其實jdk已經考慮到這個問題了,給我們提供了一個java.util.stream.Collector借口,我們隻需要按照自己的需求實作符合自己的收集方法,就可以在stream中用自己的收集器了。下面我們用一個簡答的例子實作下。
2.3.1 Collector接口源碼解釋
package java.util.stream;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
/*
* T 第一個參數是待收集的元素類型
* A 第二個參數是累加器的類型
* R 第三個參數是最終結果類型
*/
public interface Collector<T, A, R> {
/**
* 容器的提供者
*/
Supplier<A> supplier();
/**
* 累加操作
*/
BiConsumer<A, T> accumulator();
/**
* 并發的情況将每個線程的中間容器A合并
*/
BinaryOperator<A> combiner();
/**
* 終止操作
*/
Function<A, R> finisher();
/**
* 收集器特性
*/
Set<Characteristics> characteristics();
/**
*
*/
public static<T, R> Collector<T, R, R> of(Supplier<R> supplier,
BiConsumer<R, T> accumulator,
BinaryOperator<R> combiner,
Characteristics... characteristics) {
Objects.requireNonNull(supplier);
Objects.requireNonNull(accumulator);
Objects.requireNonNull(combiner);
Objects.requireNonNull(characteristics);
Set<Characteristics> cs = (characteristics.length == 0)
? Collectors.CH_ID
: Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH,
characteristics));
return new Collectors.CollectorImpl<>(supplier, accumulator, combiner, cs);
}
/**
*
*/
public static<T, A, R> Collector<T, A, R> of(Supplier<A> supplier,
BiConsumer<A, T> accumulator,
BinaryOperator<A> combiner,
Function<A, R> finisher,
Characteristics... characteristics) {
Objects.requireNonNull(supplier);
Objects.requireNonNull(accumulator);
Objects.requireNonNull(combiner);
Objects.requireNonNull(finisher);
Objects.requireNonNull(characteristics);
Set<Characteristics> cs = Collectors.CH_NOID;
if (characteristics.length > 0) {
cs = EnumSet.noneOf(Characteristics.class);
Collections.addAll(cs, characteristics);
cs = Collections.unmodifiableSet(cs);
}
return new Collectors.CollectorImpl<>(supplier, accumulator, combiner, finisher, cs);
}
/**
*
*/
enum Characteristics {
/**
* 如果一個收集器被标記為concurrent特性,那麼accumulator 方法可以被多線程并發的的調用,
* 并且隻使用一個容器A.如果收集器被标記為concurrent,但是要操作的集合是有序的,那麼
* 最終得到的結果不能保證原來的順序
*/
CONCURRENT,
/**
* 适用于無序的集合
*/
UNORDERED,
/**
* 如果收集器特性被設定IDENTITY_FINISH,那麼會強制将中間容器A類型轉換為結果類型R
*/
IDENTITY_FINISH
}
}
2.3.2 自定義收集器實作
package com.lh.nfzd.utils.collect;
import com.lh.nfzd.model.Person;
import lombok.extern.slf4j.Slf4j;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
@Slf4j
public class NameCollector implements Collector<Person, Map<Integer, List<String>>, Map<Integer, List<String>>> {
/**
* 容器的提供者
*
* @return
*/
@Override
public Supplier<Map<Integer, List<String>>> supplier() {
log.info("supplier invoke ....");
return HashMap::new;
}
/**
* 累加操作
*
* @return
*/
@Override
public BiConsumer<Map<Integer, List<String>>, Person> accumulator() {
log.info("accumulator invoke ....");
return (map, ele) -> {
Integer age = ele.getAge();
String name = ele.getName();
if (map.containsKey(age)) {
List<String> list = map.get(age);
list.add(name);
map.put(age, list);
} else {
List<String> list = new ArrayList<>();
list.add(name);
map.put(age, list);
}
};
}
/**
* 并發的情況将每個線程的中間容器A合并
*
* @return
*/
@Override
public BinaryOperator<Map<Integer, List<String>>> combiner() {
log.info("combiner invoke ....");
return (left, right) -> {
right.entrySet().forEach(a -> {
Integer key = a.getKey();
List<String> value = a.getValue();
if (left.containsKey(key)) {
List<String> list = left.get(key);
list.addAll(value);
left.put(key, list);
} else {
left.put(key, value);
}
});
return left;
};
}
/**
* 終止操作
* 這裡可以不用終止操作 直接傳回中間結果
* characteristics 标記 IDENTITY_FINISH
*
* @return
*/
@Override
public Function<Map<Integer, List<String>>, Map<Integer, List<String>>> finisher() {
log.info("finisher invoke ....");
return (a) -> a;
}
@Override
public Set<Characteristics> characteristics() {
log.info("characteristics invoke ....");
return Collections.unmodifiableSet(
/*
// finisher 不會執行
EnumSet.of(
Characteristics.UNORDERED,
Characteristics.CONCURRENT,
Characteristics.IDENTITY_FINISH
)
*/
// finisher 會執行
EnumSet.of(
Characteristics.UNORDERED,
Characteristics.CONCURRENT
)
);
}
}
2.3.3 使用示例
@Test
public void test09() {
List<Person> data = this.getPersonList();
Map<Integer, List<String>> collect = data.stream().collect(new NameCollector());
collect.entrySet().forEach(a -> System.out.println("key:" + a.getKey() + " value:" + a.getValue()));
}
2.3.4 運作結果