Stream 流(重要)
流由三部分構成
- 源(要操作的數據)
- 零個或多箇中間操作(每次操作都會產生一個新的流)
- 終止操作(得到結果)
中間操作都會返回一個Stream流,例如Stream,Stream
終止操作不會返回Stream流,有可能不返回值,也有可能返回其他類型的單個值
流操作的分類:
- 惰性求值
- 及早求值
steam.xxx().yyy().zzz().sum();
xxx().yyy().zzz() 就叫做惰性求值如果沒有終止操作將不會執行,也叫做中間操作
.sum()就叫做及早求值,直接把值求出來 也叫終止操作
流的三種創建方法
/**
* 流的三種創建方式
*/
public class StreamTest {
public static void main(String[] args) {
//第一種通過靜態方法創建
Stream system = Stream.of("hello", "world", "nihao");
//第二種通過數組的方式創建
String[] strings = new String[]{"hello", "world", "nihao"};
Stream system1 = Stream.of(strings);
Stream<String> stream = Arrays.stream(strings);
//第三種(最常見的)集合方式
List<String> list = Arrays.asList("hello", "world", "nihao");
Stream<String> stream1 = list.stream();
}
}
流帶來哪些簡化
public class StreamTest2 {
public static void main(String[] args) {
IntStream.of(new int[]{5, 6, 7}).forEach(x -> System.out.println(x));
System.out.println("=========");
//range 獲取3到8 不包含8
IntStream.range(3, 8).forEach(System.out::println);
System.out.println("=========");
//range 獲取3到8 包含8
IntStream.rangeClosed(3, 8).forEach(System.out::println);
/*
輸出集合所有整數的平方的和
*/
System.out.println("=========");
List<Integer> list = Arrays.asList(9, 8, 89);
/*
list->源,map(x -> x * x)->中間操作,惰性求值,reduce終止操作,及早求值
*/
System.out.println(list.stream().map(x -> x * x).reduce(0, Integer::sum));
}
}
R collect(Supplier supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);方法解析
Supplier supplier: 創建新結果容器的函數(返回結果)。對於並行執行,此函數可能被多次調用,每次都必須返回一個新值。
BiConsumer<R, ? super T> accumulator, 累加器函數 將流中元素經過操作轉換成返回結果中的函數類型(每次合併產生一個結果)
BiConsumer<R, R> combiner) 用於組合兩個值(精簡結果)的函數,該函數必須與累加器函數兼容類型相同 (就是將每次產生的結果給彙總到到一起 返回)
public class StreamTest4 {
public static void main(String[] args) {
// Stream<String> stringStream = Stream.of("hello", "world", "nihaoa");
//
// String[] strings = stringStream.toArray(String[]::new);
// Arrays.asList(strings).forEach(System.out::println);
// System.out.println("===========================");
/*
將一個流轉換成List
*/
Stream<String> stream = Stream.of("hello", "world", "nihaoa");
stream.collect(Collectors.toList()).forEach(System.out::println);
/*
() -> new ArrayList(), 返回的 List集合
(theList, item) -> theList.add(item) 將流中數據轉成要返回的集合類型(轉換一次生成一個集合)
(theList1, theList2) -> theList1.addAll(theList2) 將返回的每個集合合併到一個集合中返回
就是Collectors.toList() 的封裝
*/
stream.collect(() -> new ArrayList(), (theList, item) -> theList.add(item),
(theList1, theList2) -> theList1.addAll(theList2)).forEach(System.out::println);
/*
通過類::實例方法 和構造方法引用代替
*/
List<String> list = stream.collect(LinkedList::new, LinkedList::add, LinkedList::addAll);
System.out.println("=========================");
/*
將字符串拼接
*/
StringBuffer collect1 = stream.collect(StringBuffer::new, StringBuffer::append, StringBuffer::append);
System.out.println(collect1);
}
}
// Stream<String> stream = Stream.of("hello", "world", "nihaoa");
// //Collectors.toCollection 是一種通用的方法
// List<String> list = stream.collect(Collectors.toCollection(LinkedList::new));
// list.forEach(System.out::println);
// System.out.println("=============");
// Stream<String> stream = Stream.of("hello", "world", "nihaoa");
// Set<String> set = stream.collect(Collectors.toCollection(TreeSet::new));
// set.forEach(System.out::println);
Stream<String> stream = Stream.of("hello", "world", "nihaoa");
/*
Collectors.joining() 將流元素按照傳過來的順序進行拼接成字符串
*/
String str = stream.collect(Collectors.joining()).toString();
System.out.println(str);
Stream flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
改接口可以將數據中相同類型的的數據映射成流且合併到一起
public class StreamTest5 {
public static void main(String[] args) {
List<String> list = Arrays.asList("hello", "world", "nihao", "shanghai");
list.stream().map(String::toUpperCase).forEach(s -> System.out.println(s));
System.out.println("=================");
List<Integer> list1 = Arrays.asList(1, 2, 3, 4, 5, 6);
list1.stream().map(x -> x * x).collect(Collectors.toList()).forEach(System.out::println);
System.out.println("=================");
Stream<List<Integer>> stream = Stream.of(Arrays.asList(1), Arrays.asList(2), Arrays.asList(3, 4, 5));
/*
map和flatMap 不同 flatMap 的apply方法可以返回流 map的apply只能返回同類型數據
flatMap可以將同類型數據合併 例如集合數據 合併到一起 類似addAll
*/
Stream<Integer> integerStream = stream.flatMap(x -> x.stream().map(y -> y * y));
//與上面相同
// Stream<Integer> integerStream = stream.flatMap(x -> x.stream()).map(y -> y * y);
integerStream.forEach(System.out::println);
}
}
public static Stream iterate(final T seed, final UnaryOperator f) {}
doc 類似迭代器
返回通過將函數f迭代應用於初始元素seed而生成的無限序列串行流(就是無限執行UnaryOperator行爲操作一直執行下去),生成由seed、f(seed)、f(f(seed))等組成的流。
流中的第一個元素(位置0)將是提供的種子。對於n>0,位置n處的元素將是對位置n-1處的元素應用函數f的結果。
實例:流的使用方法,以及對應注意事項
public class StreamTest6 {
public static void main(String[] args) {
// Stream<String> stringStream = Stream.generate(UUID.randomUUID()::toString);
//查找流中第一個元素返回Optional 類型 通過ifPresent方法獲取到數據
// System.out.println(stringStream.findFirst().get());//未isPresent判斷是否爲空 如果爲空會報錯 一般get和isPresent連用
//正確編寫
// stringStream.findFirst().ifPresent(System.out::println);
// Stream.iterate(1, x -> x + 8).forEach(System.out::println);//無限執行下去
// Stream.iterate(1, x -> x + 8).limit(6).forEach(System.out::println);//limit限制流的長度 limit一般和iterate連用
System.out.println("====");
/*
找出該流中大於2(過濾)的元素,並將每個元素乘以2(映射),然後忽略流中的前兩個元素(skip),
然後取出流中的前兩個元素,最後求出流中元素綜合
*/
Stream<Integer> integerStream = Stream.iterate(1, x -> x + 8).limit(6);
// System.out.println(integerStream.filter(x -> x > 2).mapToInt(x -> x * 2).skip(2).limit(2).sum());
// integerStream.filter(x -> x > 2).mapToInt(x -> x * 2).skip(2).limit(2).min().ifPresent(System.out::println);
/*
同時調用最大值最小值 總和
summaryStatistics 終止操作 裏面統計了 一些求值操作
*/
// IntSummaryStatistics is = integerStream.filter(x -> x > 2).mapToInt(x -> x * 2).skip(2).limit(2).summaryStatistics();
// System.out.println(is.getMax());
// System.out.println(is.getMin());
// System.out.println(is.getSum());
System.out.println("====");
/*
當流已經被使用或已經關閉時 將無法使用
*/
System.out.println(integerStream);
System.out.println(integerStream.filter(x -> x > 2));
System.out.println(integerStream.distinct());
/*
我們可以調用新生成的流
*/
System.out.println(integerStream);
Stream<Integer> integerStream1 = integerStream.filter(x -> x > 2);
System.out.println(integerStream.filter(x -> x > 2));
System.out.println(integerStream1.distinct());
}
}
例:關於中間操作和終止操作
public class StreamTest7 {
public static void main(String[] args) {
List<String> list = Arrays.asList("hello", "world", "nihaoa");
// list.stream().map(x -> x.substring(0,1).toUpperCase() + x.substring(1)).forEach(System.out::println);
System.out.println("============");
/*
惰性求值,如果沒有終止操作(即時求值) 那麼中間操作流將不會執行
*/
// list.stream().map(x -> {
// String result = x.substring(0, 1).toUpperCase() + x.substring(1);
// System.out.println("test");//未被執行
// return result;
// });
System.out.println("============");
/*
執行了終止操作
*/
list.stream().map(x -> {
String result = x.substring(0, 1).toUpperCase() + x.substring(1);
System.out.println("test");//未被執行
return result;
}).forEach(System.out::println);
}
/*
test
Hello
test
World
test
Nihaoa
*/
}
例
public class StreamTest8 {
public static void main(String[] args) {
Stream.iterate(0, i -> (i + 1) % 2).distinct().limit(6).forEach(System.out::println);//卡在去重
}
}
內部迭代和外部迭代解析
內部迭代和外部迭代
- 內部迭代
上述操作只執行了一個循環通過前面學習forEach執行循環去遍歷每個元素,每個元素去執行filter對應的操作 (類似語文填空題 空的是行爲操作)
- 外部迭代 (傳統的循環,迭代器)
外部迭代則執行多次循環(相當於寫作文)
內部迭代和外部迭代的區別
內部迭代和外部迭代的區別:例如傳統迭代我們是拿着集合本身的數據 去編寫獨立的代碼取操作對應數據 代碼和數據是分開的,而內部迭代是我們拿到集合的流,我們去編寫對應的行爲操作當流遇到終止操作時會將我們自己編寫的代碼和流本身自帶的數據方法一同執行
總結
集合關注數據和數據存儲本身,流關注的是對數據的一種計算(填寫行爲)
流與迭代器相似的地方:流是無法被重複消費和使用的
串行流和並行流
public class StreamTest9 {
public static void main(String[] args) {
List<String> list = new ArrayList<>(5000000);
for (int i = 0; i < 5000000; ++i) {
list.add(UUID.randomUUID().toString());
}
System.out.println("開始排序");
long time = System.nanoTime();
//stream串行流 單線程執行
// list.stream().sorted().count(); //3716 3.7s
//parallelStream並行流 功能類似stream //多線程執行
list.parallelStream().sorted().count();//1207 1.2s
long time2 = System.nanoTime();
long l = TimeUnit.NANOSECONDS.toMillis(time2 - time);
System.out.println("排序耗時" + l);
}
}
斷路操作
public class StreamTest10 {
public static void main(String[] args) {
/*
把長度爲5的第一個單詞打出來
*/
List<String> list = Arrays.asList("hello", "world", "hello world");
// list.stream().filter(x -> x.length() == 5).findFirst().ifPresent(System.out::println);//hello
/*
當遇到終止操作會將得到的最終流再去執行 自己編寫的行爲(執行自己的填空語句)
斷路操作: 例如當程序執行到.findFirst() 時發現第一個元素滿足
(第一個長度爲5的單詞)時就不會去判斷後面元素直接返回
*/
list.stream().mapToInt(x -> {
int length = x.length();
System.out.println(x);
return length;
}).filter(y -> y == 5).findFirst().ifPresent(System.out::println);
}
}
// && || 斷路操作
flatMap和Map 區別
public class StreamTest11 {
public static void main(String[] args) {
/*
找出所有的單詞 並去重
*/
List<String> list = Arrays.asList("hello welcome", "world hello",
"hello world hello", "hello welcome");
// List<String[]> collect = list.stream().map(x -> x.split(" ")).distinct().collect(Collectors.toList());
// collect.forEach(x -> Arrays.asList(x).forEach(System.out::println));
/*
flatMap 需要返回流
*/
list.stream().flatMap(x -> Arrays.asList(x.split(" ")).stream()).distinct().forEach(System.out::println);
}
}
例:每條打招呼語句對應每個人
public class StreamTest12 {
/*
每條打招呼語句對應每個人
*/
public static void main(String[] args) {
List<String> list = Arrays.asList("Hi", "Hello", "你好");
List<String> l2 = Arrays.asList("zhangsan", "lisi", "wangwu", "zhaoliu");
list.stream().flatMap(x -> l2.stream().map(y -> x + y)).forEach(System.out::println);
}
}
Stream的分組分區
傳統寫法:
Stream流寫法
例:分組實例
public class StreamTest13 {
public static void main(String[] args) {
Student student = new Student("zhangsan", 100, 20);
Student student2 = new Student("lisi", 90, 20);
Student student3 = new Student("wangwu", 90, 30);
Student student4 = new Student("liuliu", 70, 40);
List<Student> list = Arrays.asList(student, student2, student3, student4);
/*
select * from student group by name 實現根據名字
Collectors.groupingBy 根據返回的名字進行分組
*/
// Map<String, List<Student>> collect = list.stream().collect(Collectors.groupingBy(Student::getUsername));
// System.out.println(collect);
System.out.println("=======================");
/*
select name,count(*) from student group by name
根據名字和數量分組
*/
// Map<String, Long> collect = list.stream().collect(Collectors.groupingBy(Student::getUsername, Collectors.counting()));
// System.out.println(collect);
//根據名字得到平均數
Map<String, Double> collect = list.stream().collect(Collectors.groupingBy(Student::getUsername, Collectors.averagingDouble(Student::getAge)));
System.out.println(collect);
}
}
分區示例
/*
分區 :分兩種 一種false (不符合)和true(符合)
{false=[Student{username='liuliu', score=70, age=40}],
true=[Student{username='zhangsan', score=100, age=20}, Student{username='lisi', score=90, age=20}, Student{username='wangwu', score=90, age=30}]}
*/
Map<Boolean, List<Student>> collect = list.stream().collect(Collectors.partitioningBy(x -> x.getScore() >= 90));
System.out.println(collect);
collect.get(false);//獲取對應false分區