Stream 是對集合(Collection)對象功能的增強,它專注於對集合對象進行各種非常便利、高效的聚合操作,或者大批量數據操作。通常我們需要多行代碼才能完成的操作,藉助於Stream流式處理可以很簡單的實現。
Stream 不是集合元素,它不是數據結構並不保存數據,它是有關算法和計算的,它更像一個高級版本的Iterator。同時Stream提供串行和並行兩種模式進行匯聚操作。比如你的Stream裏面有很多數據,Stream可以開多個線程每個線程處理一部分。最後把結果彙總起來。
在開始之前我們先用一個圖來整體的概況下Stream。如下所示:
一 Stream流創建
想使用Stream流,首先咱得先創建一個Stream流對象。創建Steam需要數據源.這些數據源可以是集合、可以是數組、可以使文件、甚至是你可以去自定義等等。
1.1 集合作爲Stream數據源
集合Collection作爲Stream的數據源,應該也是我們用的最多的一種數據源了。Collection裏面也提供了一些方法幫助我們把集合Collection轉換成Stream。
1.1.1 stream()方法
調用Collection.stream()函數創建一個Stream對象。相當於把集合Collection裏面的數據都導入到了Stream裏面去了。
@Test
public void collectionStream() {
List<Integer> list = Lists.newArrayList(1, 2, 3, 4, 5);
// 使用List創建一個流對象
Stream<Integer> stream = list.stream();
// TODO: 對流對象做處理
}
1.1.2 parallelStream()方法
調用Collection.parallelStream()創建Stream對象。
@Test
public void collectionParallelStream() {
List<Integer> list = Lists.newArrayList(1, 2, 3, 4, 5);
// 使用List創建一個流對象
Stream<Integer> stream = list.parallelStream();
// TODO: 對流對象做處理
}
parallelStream()與stream()區別是parallelStream()使用多線程併發處理最後子啊彙總結果,而stream()是單線程。所以相對來說parallelStream()效率要稍微高點。
1.2 數組作爲Stream數據源
數組也可以作爲Stream的數據源。我們可以通過Arrays.stream()方法把一個數組轉化成流對象。Arrays.stream()方法很豐富,有很多個。大家可以根據實際情況使用。
@Test
public void arrayStream() {
int[] intArray = new int[10];
for (int index = 0; index < intArray.length; index++) {
intArray[index] = index;
}
// 使用數組創建一個流對象
IntStream stream = Arrays.stream(intArray);
// TODO: 對流對象做處理
}
1.3 BufferedReader作爲Stream數據源
我們也可以把BufferedReader裏面lines方法把BufferedReader裏面每一行的數據作爲數據源生成一個Stream對象。
@Test
public void bufferedReaderStream() {
File file = new File("/home/tuacy/github/google-guava-study/src/main/resources/application.yml");
try {
// 把文件裏面的內容一行一行的讀出來
BufferedReader in = new BufferedReader(new FileReader(file));
// 生成一個Stream對象
Stream<String> stream = in.lines();
// TODO: 對流對象做處理
} catch (IOException e) {
e.printStackTrace();
}
}
1.4 File作爲Stream數據源
Files裏面多個生成Stream對象的方法,都是對Path(文件)的操作。有的是指定Path目錄下所有的子文件(所有的子文件相當於是一個列表了)作爲Stream數據源,有的把指定Path文件裏面的每一行數據作爲Stream的數據源。
1.4.1 Files.list()
列出指定Path下面的所有文件。把這些文件作爲Stream數據源。
@Test
public void fileListStream() {
Path path = Paths.get("D:\\job\\git\\google-guava-study\\src\\main\\resources");
try {
// 找到指定path下的所有的文件
Stream<Path> stream = Files.list(path);
// TODO: 對流對象做處理
} catch (IOException e) {
e.printStackTrace();
}
}
1.4.2 Files.walk()
Files.walk()方法用於遍歷子文件(包括文件夾)。參數maxDepth用於指定遍歷的深度。把子文件(子文件夾)作爲Stream數據源。
@Test
public void fileWalkStream() {
Path path = Paths.get("D:\\job\\git\\google-guava-study\\src\\main\\resources");
try {
// 第二個參數用於指定遍歷幾層
Stream<Path> stream = Files.walk(path, 2);
// TODO: 對流對象做處理
} catch (IOException e) {
e.printStackTrace();
}
}
1.4.3 Files.find()
Files.find方法用於遍歷查找(過濾)子文件。參數裏面會指定查詢(過濾)條件。把過濾出來的子文件作爲Stream的數據源。
@Test
public void fileFindStream() {
Path path = Paths.get("D:\\job\\git\\google-guava-study\\src\\main\\resources");
try {
// 找到指定path下的所有不是目錄的文件
Stream<Path> stream = Files.find(path, 2, (path1, basicFileAttributes) -> {
// 過濾掉目錄文件
return !basicFileAttributes.isDirectory();
});
// TODO: 對流對象做處理
} catch (IOException e) {
e.printStackTrace();
}
}
1.4.4 Files.lines()
Files.lines方法是把指定Path文件裏面的每一行內容作爲Stream的數據源。
@Test
public void fileLineStream() {
Path path = Paths.get("D:\\job\\git\\google-guava-study\\src\\main\\resources\\application.yml");
try {
// 生成一個Stream對象
Stream<String> stream = Files.lines(path);
// TODO: 對流對象做處理
} catch (IOException e) {
e.printStackTrace();
}
}
1.5 自己構建Stream
我們也可以自己去創建Stream自己提供數據源。Stream類裏面提供of()、iterate()、generate()、builder()等一些方法來創建Stream,Stream的數據源我們自己提供。
1.5.1 Stream.of()
Stream.of()函數參數就是數據源。
Stream<Integer> ofSteam = Stream.of(1,2,3,4,5,6);
1.5.2 Stream.iterate()
Stream.iterate()可以用來生成無限流,函數需要兩個參數:第一個參數是初始值、第二個參數用於確定怎麼根據前一個元素的值生成下一個元素。
// Stream.iterate() 流式迭代器 Stream.iterate()函數的第二個參數告訴你怎麼去生成下一個元素
Stream<BigInteger> integers = Stream.iterate(
BigInteger.ONE,
new UnaryOperator<BigInteger>() {
@Override
public BigInteger apply(BigInteger bigInteger) {
return bigInteger.add(BigInteger.ONE);
}
});
// 簡單輸出
integers.limit(10).forEach(new Consumer<BigInteger>() {
@Override
public void accept(BigInteger bigInteger) {
System.out.println(bigInteger.intValue());
}
});
1.5.3 Stream.generate()
Stream.generate()也是用於生成一個無限流。參數用於獲取每個元素。
// Stream.generate() 生成無限流
Stream<Double> generateA = Stream.generate(new Supplier<Double>() {
@Override
public Double get() {
return java.lang.Math.random() * 100;
}
});
// 簡單輸出前10個值
generateA.limit(10).forEach(new Consumer<Double>() {
@Override
public void accept(Double bigInteger) {
System.out.println(bigInteger.intValue());
}
});
1.5.4 Stream.build()
Stream.build()通過建造者模式生成一個Stream建造器。然後把需要加入Stream裏面的數據源一個一個通過建造器添加進去。
// Stream.builder()構造一個Stream對象
Stream.Builder<Integer> build = Stream.<Integer>builder().add(1)
.add(2)
.add(3);
build.accept(4);
build.accept(5);
build.build().forEach(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer);
}
});
// TODO: 對流對象做處理
1.6 其他Stream創建方式
Stream其他創建方式我們就不一一舉例了。有如下方式。
- Random.ints()。
- BitSet.stream()。
- Pattern.splitAsStream(java.lang.CharSequence)
- JarFile.stream()。
- …
二 Stream流操作(操作符)
Stream流操作就是對Stream流的各種處理。Stream裏面已經給提供了很多中間操作(我們一般稱之爲操作符)。
Stream提供的流操作符。
Stream流操作符 | 解釋 |
---|---|
filter | 過濾 |
map | 對流裏面每個元素做轉換 |
mapToInt | 把流裏面的每個元素轉換成int |
mapToLong | 流裏面每個元素轉換成long |
mapToDouble | 流裏面每個元素轉換成double |
flatMap | 流裏面每個元素轉換成Steam對象,最後平鋪成一個Stream對象 |
flatMapToInt | 流裏面每個元素轉換成IntStream對象,最後平鋪成一個IntStream對象 |
flatMapToLong | 流裏面每個元素轉換成LongStream對象,最後平鋪成一個LongStream對象 |
flatMapToDouble | 流裏面每個元素轉換成DoubleStream對象,最後平鋪成一個DoubleStream對象 |
distinct | 去重 |
sorted | 排序 |
peek | 查看流裏面的每個元素 |
limit | 返回前n個數 |
skip | 跳過前n個元素 |
Stream提供了這麼多的操作符,而且這些操作符是可以組合起來使用。關於每個操作符的使用我們用一個簡單的實例代碼來說明。
2.1 filter
filter用於對流裏面的數據做過濾操作。
// 過濾
@Test
public void filter() {
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 過濾出偶數
Stream<Integer> filterStream = stream.filter(new Predicate<Integer>() {
@Override
public boolean test(Integer integer) {
return integer % 2 == 0;
}
});
// 簡單輸出
filterStream.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer);
}
});
}
2.2 map
map用於對流裏面的元素做轉換。
// 轉換
@Test
public void map() {
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 整數轉換爲String
Stream<String> mapStream = stream.map(new Function<Integer, String>(){
@Override
public String apply(Integer integer) {
return String.valueOf(integer);
}
});
// 簡單輸出
mapStream.forEach(new Consumer<String>() {
@Override
public void accept(String integer) {
System.out.println(integer);
}
});
}
2.3 mapToInt、mapToLong、mapToDouble
mapToInt、mapToLong、mapToDouble用於將流裏面的元素轉換成對應的類型。
// 轉換
@Test
public void mapToInt() {
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 整數轉換爲String
IntStream mapStream = stream.mapToInt(new ToIntFunction<Integer>(){
@Override
public int applyAsInt(Integer value) {
return value == null ? 0 : value;
}
});
// 簡單輸出總和
System.out.println(mapStream.sum());
}
2.4 flatMap、flatMapToInt、flatMapToLong、flatMapToDouble
flatMap、flatMapToInt、flatMapToLong、flatMapToDouble也是對每個元素的轉換,不過他們和map的不同點在於,他們是吧每個元素轉換成一個Stream流,最終在平鋪成一個Stream流。
// 轉換
@Test
public void flatMap() {
Stream<String> stream = Stream.of("java:1", "android:2", "ios:3");
// 整數轉換爲String
Stream<String> rerStream = stream.flatMap(
new Function<String, Stream<String>>() {
@Override
public Stream<String> apply(String s) {
// 分割
Iterable<String> iterableList = Splitter.on(':').trimResults() // 移除前面和後面的空白
.omitEmptyStrings()
.split(s);
return Lists.newArrayList(iterableList).parallelStream();
}
});
// 簡單輸出
rerStream.forEach(new Consumer<String>() {
@Override
public void accept(String integer) {
System.out.println(integer);
}
});
}
// 轉換
@Test
public void reduce() {
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 所有的元素相加,在加上20
Integer reduceValue = stream.reduce(20, new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer integer, Integer integer2) {
System.out.println(integer);
System.out.println(integer2);
return integer + integer2;
}
});
System.out.println(reduceValue);
}
2.5 distinct
distinct操作符用於去重。
// 去重
@Test
public void distinct() {
Stream<Integer> stream = Stream.of(1,2,3,1,2,3,1,2,3);
Stream<Integer> rerStream = stream.distinct();
// 簡單輸出
rerStream.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer);
}
});
}
2.6 sorted
sorted操作符用於對流裏面的元素排序。
// 排序
@Test
public void sorted() {
Stream<Integer> stream = Stream.of(1,2,3,2,5,4,8,6);
Stream<Integer> rerStream = stream.sorted(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
if (o1.equals(o2)) {
return 0;
} else {
return o1 > o2 ? 1 : -1;
}
}
});
// 簡單輸出
rerStream.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer);
}
});
}
2.7 peek
peek操作符用於查看流裏面的每個元素。在多個操作符同時使用的時候的中間加入peek操作符可以參考每個操作符之後的結果。
// 查看
@Test
public void peek() {
Stream<Integer> stream = Stream.of(1,2,3,1,2,3,1,2,3);
// 查看
Stream<Integer> reStream = stream.peek(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer);
}
});
// 簡單輸出
reStream.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer);
}
});
}
2.8 limit
limit操作符用於取流前面多少個元素。
// limit
@Test
public void limit() {
Stream<Integer> stream = Stream.of(1,2,3,1,2,3,1,2,3);
// limit
Stream<Integer> reStream = stream.limit(3);
// 簡單輸出
reStream.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer);
}
});
}
2.9 skip
skip用於跳過裏面的多少個元素。
// skip
@Test
public void skip() {
Stream<Integer> stream = Stream.of(1,2,3,1,2,3,1,2,3);
// skip
Stream<Integer> reStream = stream.skip(3);
// 簡單輸出
reStream.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer);
}
});
}
三. Stream流終端操作
Stream流終端操作是流式處理的最後一步,之前已經對Stream做了一系列的處理之後。該拿出結果了。我們可以在終端操作中實現對流的遍歷、查找、歸約、收集等等一系列的操作。
Stream流終端操作提供的函數有
終端操作符 | 解釋 |
---|---|
forEach | 遍歷 |
forEachOrdered | 如果流裏面的元素是有順序的則按順序遍歷 |
toArray | 轉換成數組 |
reduce | 歸約 - 根據一定的規則將Stream中的元素進行計算後返回一個唯一的值 |
collect | 收集 - 對處理結果的封裝 |
min | 最小值 |
max | 最大值 |
count | 元素的個數 |
anyMatch | 任何一個匹配到了就返回true |
allMatch | 所有都匹配上了就返回true |
noneMatch | 沒有一個匹配上就返回true |
findFirst | 返回滿足條件的第一個元素 |
findAny | 返回某個元素 |
關於Stream終端操作部分,我們就着重講下collect()函數的使用。因爲其他的終端操作符都很好理解。collect()稍稍複雜一點。
3.1 collect()
collect()的使用主要在於對參數的理解,所有我們這裏要專門講下collect()函數的參數Collector這個類,以及怎麼去構建Collector對象。只有在瞭解了這些之後,咱們纔可以熟練的把他們用在各種場景中。
3.1.1 Collector
Collector類目前沒別的用處,就是專門用來作爲Stream的collect()方法的參數的。把Stream裏面的數據轉換成我們最終想要的結果上。
Collector各個方法,以及每個泛型的介紹,都在下面詳細的解釋了。如果有疑問可以留言。
/**
* Collector是專門用來作爲Stream的collect方法的參數的
*
* 泛型含義
* T:是流中要收集的對象的泛型
* A:是累加器的類型,累加器是在收集過程中用於累積部分結果的對象。
* R:是收集操作得到的對象(通常但不一定是集合)的類型。
*/
public interface Collector<T, A, R> {
/**
* 生成結果容器,容器類型爲A
* (多線程的情況下可能會調用多次,開多個線程同時去處理一個流,每個線程調用一次)
*/
Supplier<A> supplier();
/**
* A對應supplier()函數創建的結果容器
* T對應Stream流裏面一個一個的元素
* 用於消費元素,也就是歸納元素,一般在這個裏面把流裏面的元素T(也可以轉換下)放到supplier()創建的結果T裏面去
*/
BiConsumer<A, T> accumulator();
/**
* 用於兩個兩個合併並行執行的線程的執行結果,將其合併爲一個最終結果A
* 多線程的情況下,多個線程並行執行。每個線程產生一個結果
*/
BinaryOperator<A> combiner();
/**
* 用於將之前整合完的結果A轉換成爲R
*
* combiner()完成之後了A, 這裏還可以在轉一道。生成你自己想要的結果
*/
Function<A, R> finisher();
/**
* characteristics表示當前Collector的特徵值,
* 這是個不可變Set
* 它定義了收集器的行爲--尤其是關於流是否可以多線程並行執行,以及可以使用哪些優化的提示
*/
Set<Characteristics> characteristics();
/**
* 它定義了收集器的行爲--尤其是關於流是否可以並行歸約,以及可以使用哪些優化的提示
*/
enum Characteristics {
/**
* accumulator函數可以從多個線程同時調用,且該收集器可以並行歸約流。如果收集器沒有標爲UNORDERED,
* 那它僅在用於無序數據源時纔可以並行歸約
* 多線程並行
*/
CONCURRENT,
/**
* 歸約結果不受流中項目的遍歷和累積順序的影響(無序)
*/
UNORDERED,
/**
* 無需轉換結果
*/
IDENTITY_FINISH
}
/**
* 四參方法,用於生成一個Collector,T代表流中的一個一個元素,R代表最終的結果
*/
public static<T, R> Collector<T, R, R> of(Supplier<R> supplier,
BiConsumer<R, T> accumulator,
BinaryOperator<R> combiner,
Characteristics... characteristics);
/**
* 五參方法,用於生成一個Collector,T代表流中的一個一個元素,A代表中間結果,R代表最終結果,finisher用於將A轉換爲R
*/
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);
}
有了上面的介紹,接下來我們自己來new一個Collector對象,把我們Steam流裏面的數據轉換成List。(當然了Collectors類裏面有提供這個方法,這裏我們自己寫一個也是爲了方便大家的理解)
// 自己來組裝Collector,返回一個List
@Test
public void collectNew() {
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> intList = stream.collect(
new Collector<Integer, List<Integer>, List<Integer>>() {
// 生成結果容器,容器類型爲,我們這裏爲List<Integer>
@Override
public Supplier<List<Integer>> supplier() {
return new Supplier<List<Integer>>() {
@Override
public List<Integer> get() {
return new ArrayList<>();
}
};
}
// 把流裏面的結果都放到結果容器裏面去
@Override
public BiConsumer<List<Integer>, Integer> accumulator() {
return new BiConsumer<List<Integer>, Integer>() {
@Override
public void accept(List<Integer> integers, Integer integer) {
integers.add(integer);
}
};
}
// 兩個兩個合併並行執行的線程的執行結果,將其合併爲一個最終結果A
@Override
public BinaryOperator<List<Integer>> combiner() {
return new BinaryOperator<List<Integer>>() {
@Override
public List<Integer> apply(List<Integer> left, List<Integer> right) {
left.addAll(right);
return left;
}
};
}
// 可以對最終的結果做一個轉換操作
@Override
public Function<List<Integer>, List<Integer>> finisher() {
return new Function<List<Integer>, List<Integer>>() {
@Override
public List<Integer> apply(List<Integer> integers) {
return integers;
}
};
}
// 特徵值
@Override
public Set<Characteristics> characteristics() {
return EnumSet.of(Collector.Characteristics.UNORDERED, Collector.Characteristics.IDENTITY_FINISH);
}
});
for (Integer item : intList) {
System.out.println(item);
}
}
3.1.2 Collectors
Collectors是Collector的工具類,用來創建Collector對象。它內部以及給我們提供了各種各樣的創建Collector對象的方法,已經能滿足我們大部分的需求了,我們可以直接拿來使用,非常方便。
Collectors各個方法介紹
public final class Collectors {
/**
* 將流中的元素全部放置到一個集合中返回
*
* collectionFactory參數用於創建Collection對象(比如List,LinkeList等等)
*/
public static <T, C extends Collection<T>>
Collector<T, ?, C> toCollection(Supplier<C> collectionFactory);
/**
* 將流中的元素放置到一個列表集合中去。這個列表默認爲ArrayList
*/
public static <T>
Collector<T, ?, List<T>> toList();
/**
* 將流中的元素放置到一個無序集set中去。默認爲HashSet
*/
public static <T>
Collector<T, ?, Set<T>> toSet();
/**
* joining的目的是將流中的元素全部以字符序列的方式連接到一起,可以指定連接符,甚至是結果的前後綴
*/
public static Collector<CharSequence, ?, String> joining();
public static Collector<CharSequence, ?, String> joining(CharSequence delimiter);
public static Collector<CharSequence, ?, String> joining(CharSequence delimiter,
CharSequence prefix,
CharSequence suffix);
/**
* 這個映射是首先對流中的每個元素進行映射,即類型轉換,然後再將新元素以給定的Collector進行歸納
*/
public static <T, U, A, R>
Collector<T, ?, R> mapping(Function<? super T, ? extends U> mapper,
Collector<? super U, A, R> downstream);
/**
* 該方法是在歸納動作結束之後,對歸納的結果進行再處理
*/
public static<T,A,R,RR> Collector<T,A,RR> collectingAndThen(Collector<T,A,R> downstream,
Function<R,RR> finisher);
/**
* 該方法用於計數
*/
public static <T> Collector<T, ?, Long> counting();
/**
* 生成一個用於獲取最小/最大值的Optional結果的Collector
*/
public static <T> Collector<T, ?, Optional<T>> minBy(Comparator<? super T> comparator);
public static <T> Collector<T, ?, Optional<T>> maxBy(Comparator<? super T> comparator);
/**
* 生成一個用於求元素和的Collector,首先通過給定的mapper將元素轉換類型,然後再求和
*/
public static <T> Collector<T, ?, Integer> summingInt(ToIntFunction<? super T> mapper);
public static <T> Collector<T, ?, Long> summingLong(ToLongFunction<? super T> mapper);
public static <T> Collector<T, ?, Double> summingDouble(ToDoubleFunction<? super T> mapper);
/**
* 生成一個用於求元素平均值的Collector,首選通過參數將元素轉換爲指定的類型
*/
public static <T> Collector<T, ?, Double> averagingInt(ToIntFunction<? super T> mapper);
public static <T> Collector<T, ?, Double> averagingLong(ToLongFunction<? super T> mapper);
public static <T> Collector<T, ?, Double> averagingDouble(ToDoubleFunction<? super T> mapper);
/**
* 規約 對流中的元素做統計歸納作用
* 和Stream類裏面的reducing操作符一樣
*/
public static <T> Collector<T, ?, T> reducing(T identity, BinaryOperator<T> op);
public static <T> Collector<T, ?, Optional<T>> reducing(BinaryOperator<T> op);
public static <T, U> Collector<T, ?, U> reducing(U identity,
Function<? super T, ? extends U> mapper,
BinaryOperator<U> op);
/**
* 這個方法是用於生成一個擁有分組功能的Collector
*/
public static <T, K> Collector<T, ?, Map<K, List<T>>>
groupingBy(Function<? super T, ? extends K> classifier);
public static <T, K, A, D>
Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,
Collector<? super T, A, D> downstream);
public static <T, K, D, A, M extends Map<K, D>>
Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,
Supplier<M> mapFactory,
Collector<? super T, A, D> downstream);
/**
* 和groupingBy方法一樣,只是返回的Collector是並行的
*/
public static <T, K>
Collector<T, ?, ConcurrentMap<K, List<T>>>
groupingByConcurrent(Function<? super T, ? extends K> classifier);
public static <T, K, A, D>
Collector<T, ?, ConcurrentMap<K, D>> groupingByConcurrent(Function<? super T, ? extends K> classifier,
Collector<? super T, A, D> downstream);
public static <T, K, A, D, M extends ConcurrentMap<K, D>>
Collector<T, ?, M> groupingByConcurrent(Function<? super T, ? extends K> classifier,
Supplier<M> mapFactory,
Collector<? super T, A, D> downstream);
/**
* 該方法將流中的元素按照給定的校驗規則的結果分爲兩個部分,放到一個map中返回,map的鍵是Boolean類型,值爲元素的列表List。
*/
public static <T>
Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate);
public static <T, D, A>
Collector<T, ?, Map<Boolean, D>> partitioningBy(Predicate<? super T> predicate,
Collector<? super T, A, D> downstream);
/**
* 根據給定的鍵生成器和值生成器生成的鍵和值保存到一個map中返回,鍵和值的生成都依賴於元素,
* 可以指定出現重複鍵時的處理方案和保存結果的map
*/
public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper);
public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction);
public static <T, K, U, M extends Map<K, U>>
Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction,
Supplier<M> mapSupplier);
/**
* 併發版本的toMap
*/
public static <T, K, U>
Collector<T, ?, ConcurrentMap<K,U>> toConcurrentMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper);
public static <T, K, U>
Collector<T, ?, ConcurrentMap<K,U>>
toConcurrentMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction);
public static <T, K, U, M extends ConcurrentMap<K, U>>
Collector<T, ?, M> toConcurrentMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction,
Supplier<M> mapSupplier);
/**
* 這三個方法適用於彙總的,返回值分別是IntSummaryStatistics,LongSummaryStatistics,DoubleSummaryStatistics
*/
public static <T>
Collector<T, ?, IntSummaryStatistics> summarizingInt(ToIntFunction<? super T> mapper);
public static <T>
Collector<T, ?, LongSummaryStatistics> summarizingLong(ToLongFunction<? super T> mapper);
public static <T>
Collector<T, ?, DoubleSummaryStatistics> summarizingDouble(ToDoubleFunction<? super T> mapper);
}
爲了加深大家的理解。接下來我們對Collectors裏面的每個函數都寫一個簡單的實例。
3.1.2.1 toCollection
將流中的元素全部放置到一個集合中返回,這裏使用Collection,泛指多種集合。
@Test
public void toCollection() {
List<String> list = Arrays.asList("java", "ios", "c");
LinkedList<String> retList = list.stream().collect(Collectors.toCollection(
new Supplier<LinkedList<String>>() {
@Override
public LinkedList<String> get() {
return new LinkedList<>();
}
}));
}
3.1.2.2 toList
將流中的元素放置到一個列表集合中去。這個列表默認爲ArrayList。
@Test
public void collectList() {
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> intList = stream.collect(Collectors.toList());
}
3.1.2.3 toSet
將流中的元素放置到一個無序集set中去。默認爲HashSet。
@Test
public void toSet() {
List<String> list = Arrays.asList("java", "ios", "c");
Set<String> retList = list.stream().collect(Collectors.toSet());
}
3.1.2.4 joining
joining的目的是將流中的元素全部以字符序列的方式連接到一起,可以指定連接符,甚至是結果的前後綴。
@Test
public void joining() {
List<String> list = Arrays.asList("java", "ios", "c");
String ret = list.stream().collect(Collectors.joining(":", "@@", "@@"));
System.out.println(ret);//@@java:ios:c@@
}
3.1.2.5 mapping
這個映射是首先對流中的每個元素進行映射,即類型轉換,然後再將新元素以給定的Collector進行歸納。
@Test
public void mapping() {
List<String> list = Arrays.asList("java", "ios", "c");
// 先把流裏面的每個元素的前後加上[],之後在用:拼接起來
String ret = list.stream().collect(Collectors.mapping(
new Function<String, String>() {
@Override
public String apply(String s) {
return "[" + s + "]";
}
},
Collectors.joining(":")));
System.out.println(ret);//[java]:[ios]:[c]
}
3.1.2.6 collectingAndThen
該方法是在歸納動作結束之後,對歸納的結果進行再處理。
@Test
public void collectingAndThen() {
List<String> list = Arrays.asList("java", "ios", "c");
// Collectors.toList()之後在把List<String>通過Joiner轉換String
String ret = list.stream().collect(Collectors.collectingAndThen(
Collectors.toList(),
new Function<List<String>, String>() {
@Override
public String apply(List<String> strings) {
return Joiner.on("; ")
.join(strings);
}
}));
System.out.println(ret);//java; ios; c
}
3.1.2.7 counting
計算流裏面的元素個數。
@Test
public void counting() {
List<String> list = Arrays.asList("java", "ios", "c");
// 元素個數
Long ret = list.stream().collect(Collectors.counting());
System.out.println(ret);//3
}
3.1.2.8 minBy/maxBy
生成一個用於獲取最小/最大值的Optional結果的Collector。
@Test
public void minBy() {
List<String> list = Arrays.asList("java", "ios", "c");
// 這裏簡單的用字符串比較的大小
Optional<String> ret = list.stream().collect(Collectors.minBy(
new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
}));
}
3.2.2.9 summingInt/summingLong/summingDouble
生成一個用於求元素和的Collector,首先通過給定的mapper將元素轉換類型,然後再求和。
@Test
public void summingInt() {
List<Integer> list = Arrays.asList(1, 2, 3);
// 求和
Integer ret = list.stream().collect(Collectors.summingInt(
new ToIntFunction<Integer>() {
@Override
public int applyAsInt(Integer value) {
return value;
}
}));
}
3.2.2.10 averagingInt/averagingLong/averagingDouble
生成一個用於求元素平均值的Collector,首選通過參數將元素轉換爲指定的類型。
@Test
public void averagingInt() {
List<Integer> list = Arrays.asList(1, 2, 3);
// 求平均值
Double ret = list.stream().collect(Collectors.averagingInt(
new ToIntFunction<Integer>() {
@Override
public int applyAsInt(Integer value) {
return value;
}
}));
}
3.1.2.11 reducing
reducing方法有三個重載方法,其實是和Stream裏的三個reduce方法對應的,二者是可以替換使用的,作用完全一致,也是對流中的元素做統計歸納作用(規約)。
@Test
public void reducing() {
List<Integer> list = Arrays.asList(1, 2, 3);
// 求平均值
Integer ret = list.stream().collect(Collectors.reducing(
10,
new Function<Integer, Integer>() {
@Override
public Integer apply(Integer integer) {
return integer * integer;
}
},
new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer integer, Integer integer2) {
return integer + integer2;
}
}));
System.out.println(ret); // 10 + 1*1 + 2*2 + 3*3 = 24
}
3.1.2.12 groupingBy
這個方法是用於生成一個擁有分組功能的Collector,它也有三個重載方法。這裏要稍微解釋下參數最長的函數每個參數的含義。
- Function<? super T, ? extends K> classifier:確定怎麼從流裏面每個元素獲取key。
- Supplier mapFactory:用於創建最終要生成的對象一般都是Map。
- Collector<? super T, A, D> downstream:每一組數據用什麼來存放,一般是List,所以這個參數一般是Collectors.toList()。
@Test
public void collectGroupingBy() {
List<Student> list = Arrays.asList(new Student("吳六", 26), new Student("張三", 26), new Student("李四", 27));
Map<Integer, List<Student>> ret = list.stream().collect(Collectors.groupingBy(
new Function<Student, Integer>() {
@Override
public Integer apply(Student student) {
return student.getAge();
}
},
new Supplier<Map<Integer, List<Student>>>() {
@Override
public Map<Integer, List<Student>> get() {
return new HashMap<>();
}
},
Collectors.toList()));
for (Map.Entry<Integer, List<Student>> entry : ret.entrySet()) {
Integer mapKey = entry.getKey();
List<Student> mapValue = entry.getValue();
System.out.println(mapKey + ":" + mapValue);
}
}
private static class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
groupingByConcurrent和groupingBy是一樣的,只不過groupingByConcurrent生成的是ConcurrentMap,而groupingBy生成的是Map。
3.1.2.13 partitioningBy
該方法將流中的元素按照給定的校驗規則的結果分爲兩個部分,放到一個map中返回,map的鍵是Boolean類型,值爲元素的列表List。
@Test
public void partitioningBy() {
List<Integer> list = Arrays.asList(1, 2, 3);
// 求平均值
Map<Boolean, List<Integer>> ret = list.stream()
.collect(Collectors.partitioningBy(
new Predicate<Integer>() {
@Override
public boolean test(Integer integer) {
return integer % 2 == 0;
}
},
Collectors.toList()));
for (Map.Entry<Boolean, List<Integer>> entry : ret.entrySet()) {
Boolean mapKey = entry.getKey();
List<Integer> mapValue = entry.getValue();
System.out.println(mapKey + ":" + mapValue);
}
}
3.1.2.14 toMap
toMap方法是根據給定的鍵生成器和值生成器生成的鍵和值保存到一個map中返回,鍵和值的生成都依賴於元素,可以指定出現重複鍵時的處理方案和保存結果的map。
@Test
public void toMap() {
List<Student> list = Arrays.asList(new Student("吳六", 26), new Student("張三", 26), new Student("李四", 27));
Map<Integer, List<Student>> ret = list.stream()
.collect(Collectors.toMap(
// key
new Function<Student, Integer>() {
@Override
public Integer apply(Student student) {
return student.getAge();
}
},
// value
new Function<Student, List<Student>>() {
@Override
public List<Student> apply(Student student) {
return Lists.newArrayList(student);
}
},
// 有系統key的value怎麼處理
new BinaryOperator<List<Student>>() {
@Override
public List<Student> apply(List<Student> students, List<Student> students2) {
students.addAll(students2);
return students;
}
}));
for (Map.Entry<Integer, List<Student>> entry : ret.entrySet()) {
Integer mapKey = entry.getKey();
List<Student> mapValue = entry.getValue();
System.out.println(mapKey + ":" + mapValue);
}
}
private static class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
3.1.2.15 summarizingInt/summarizingLong/summarizingDouble
這三個方法適用於彙總的,返回值分別是IntSummaryStatistics,LongSummaryStatistics,DoubleSummaryStatistics。
@Test
public void summarizingInt() {
List<Integer> list = Arrays.asList(1, 2, 3);
IntSummaryStatistics ret = list.stream()
.collect(Collectors.summarizingInt(
new ToIntFunction<Integer>(){
@Override
public int applyAsInt(Integer value) {
return value;
}
}));
System.out.println(ret.getAverage());
}
關於JDK8裏面Stream部分內容我們就講這麼些,真心希望能幫助上大家。大家如果有什麼疑問可以留言。最後,上面所有的實例代碼在 https://github.com/tuacy/google-guava-study裏面都可以找到。