簡單認識一下Stream:Stream類中的官方介紹:html
/** * A sequence of elements supporting sequential and parallel aggregate * operations. The following example illustrates an aggregate operation using * {@link Stream} and {@link IntStream}: * * <pre>{@code * int sum = widgets.stream() * .filter(w -> w.getColor() == RED) * .mapToInt(w -> w.getWeight()) * .sum(); * }</pre> * In this example, {@code widgets} is a {@code Collection<Widget>}. We create * a stream of {@code Widget} objects via {@link Collection#stream Collection.stream()}, * filter it to produce a stream containing only the red widgets, and then * transform it into a stream of {@code int} values representing the weight of * each red widget. Then this stream is summed to produce a total weight. * */
看這麼一個案例,相似於js中的鏈式操做。就明白了大概流是什麼樣子的。相似於 Linux的 pipelinejava
stream.xxx().yyy().zzz().count();
中間操做:惰性求值。只有在count()被調用的時候,中間的操做纔會進行求值。sql
及早求值,count()方法調用的時候馬上求值,這就叫作及早求值。express
流中的及早求值只會有一個。編程
public class StreamTest { public static void main(String[] args) { //本章才正式的開始對流進行講解。 //第一種方式,經過of方法 Stream stream1 = Stream.of("hello","world"); //第二種方式,經過數組方式 String[] strings = new String[]{"hello","world"}; Stream stream2 = Arrays.stream(strings); Stream stream3 = Stream.of(strings); //of的底層就是 經過Arrays.stream()來實現的. //第三種方式,經過集合.stream List<String> list = Arrays.asList("hello", "world"); list.stream(); } }
用法一:api
public class streamTest2 { public static void main(String[] args) { //Intstream 怎麼用 IntStream.of(5, 6, 7).forEach(System.out::println); System.out.println("----"); IntStream.range(3, 8).forEach(System.out::println); System.out.println("----"); IntStream.rangeClosed(3, 8).forEach(System.out::println); System.out.println("----"); } }
public class streamTest3 { public static void main(String[] args) { //List類型,int的值, 對每個元素*2,而後加起來,獲得結果 List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6); //之前的寫法 // int i=0; // for (Integer i : list) { // sum += 2; // } // sum... //stream的寫法,一行 System.out.println(list.stream().map(integer -> integer*2).reduce(0,Integer::sum)); //reduce方法,map()方法的調用會在下面進行詳解. //實現簡單,語義更明確 } }
reduce(),終止操做,及早求值.數組
函數式編程,最根本的一點:方法傳遞的是行爲.之前傳遞的都是數據.markdown
看這個Example:app
public class streamTest4 { public static void main(String[] args) { Stream<String> stream = Stream.of("hello", "world", "hello world"); //lambda寫法 //stream.toArray(length -> new String[length]); //方法引用的寫法 (構造方法引用) String[] stringArray = stream.toArray(String[]::new); Arrays.asList(stringArray).forEach(System.out::println); } }
已知流,轉List框架
//已知流,轉List Stream<String> stream = Stream.of("hello", "world", "hello world"); List<String> collect = stream.collect(Collectors.toList()); collect.forEach(System.out::println);
collect()方法詳解 - Collectors裏面也是經過collect(三個參數)這個方法來實現的
/** * 第一個參數介紹 * Performs a <a href="package-summary.html#MutableReduction">mutable * reduction</a> operation on the elements of this stream. A mutable * reduction is one in which the reduced value is a mutable result container, * such as an {@code ArrayList}, and elements are incorporated by updating * the state of the result rather than by replacing the result. This * produces a result equivalent to: 第二個參數的介紹 * <pre>{@code * R result = supplier.get(); * for (T element : this stream) * accumulator.accept(result, element); * return result; * }</pre> * 被並行化. 流帶來的好處. * <p>Like {@link #reduce(Object, BinaryOperator)}, {@code collect} operations * can be parallelized without requiring additional synchronization. * 這是一個終止操做. * <p>This is a <a href="package-summary.html#StreamOps">terminal * operation</a>. * 方法簽名是很是適合於使用方法引用的方式.就是最下面舉例的Example * @apiNote There are many existing classes in the JDK whose signatures are * well-suited for use with method references as arguments to {@code collect()}. * For example, the following will accumulate strings into an {@code ArrayList}: * <pre>{@code * List<String> asList = stringStream.collect(ArrayList::new, ArrayList::add, * ArrayList::addAll); * }</pre> * 擴展功能:字符串實現拼接的操做 * <p>The following will take a stream of strings and concatenates them into a * single string: * <pre>{@code * String concat = stringStream.collect(StringBuilder::new, StringBuilder::append, * StringBuilder::append) * .toString(); * }</pre> * * @param <R> type of the result 第一個參數:結果容器,如LinkList * @param supplier a function that creates a new result container. For a * parallel execution, this function may be called * multiple times and must return a fresh value each time. 第二個參數:關聯性的,不衝突的,無狀態的,用於合併. item->list * @param accumulator an <a href="package-summary.html#Associativity">associative</a>, * <a href="package-summary.html#NonInterference">non-interfering</a>, * <a href="package-summary.html#Statelessness">stateless</a> * function for incorporating an additional element into a result 第三個參數:用於融合,將上次遍歷獲得的集合融合到最終的結果集中. * @param combiner an <a href="package-summary.html#Associativity">associative</a>, * <a href="package-summary.html#NonInterference">non-interfering</a>, * <a href="package-summary.html#Statelessness">stateless</a> * function for combining two values, which must be * compatible with the accumulator function * @return the result of the reduction */ <R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner);
經過源碼解釋:咱們得知實現流轉List的底層就是經過這個三參數的collect方法來實現的,咱們逐一來對這三個參數進行了解.
1.參數1:supplier,類型Supplier的函數式接口. 功能:用來提供一個初步的List容器
2.參數2: accumulator,類型BiConsumer的函數式接口. 功能:累加器,將流中的一個個元素累加進集合中.
3.參數3:combiner,類型爲BiConsumer的函數式接口. 功能:組合器,將上一次遍歷獲得一個的集合進行融合到最終的List中.
自行閱讀上面的Collector的文檔.我說的這些內容都在裏面有所體現.
經過上述的瞭解,咱們能夠經過三參數的collect()方法,來本身實現一個底層stream轉換List的實現,以下:
//功能描述:已知流,轉List Stream<String> stream = Stream.of("hello", "world", "hello world"); List<String> collect = stream.collect(Collectors.toList()); collect.forEach(System.out::println); //使用collect(三個參數)的底層方法來實現這個操做. 由於這個三參的collect()方法就是這個操做的底層. List<String> list = stream.collect(() -> new ArrayList(),(theList,item)->theList.add(item),(theList1,theList2)->theList1.addAll(theList2)); //經過方法引用優化後的代碼以下: //優化後的代碼: List<String> list1 = stream.collect(LinkedList::new,LinkedList::add,LinkedList::addAll);
上述源碼註釋中還提供了 字符串拼接的操做
* 擴展功能:字符串實現拼接的操做 * <p>The following will take a stream of strings and concatenates them into a * single string: * <pre>{@code * String concat = stringStream.collect(StringBuilder::new, StringBuilder::append, * StringBuilder::append) * .toString(); * }</pre> *
//使用 Collectors.toCollection()方法來實現 流轉List Stream<String> stream = Stream.of("hello", "world", "hello world"); // ArrayList<String> list = stream.collect(Collectors.toCollection(ArrayList::new)); // list.forEach(System.out::println); //使用 Collectors.toCollection()方法來實現 流轉Set Set<String> list = stream.collect(Collectors.toCollection(TreeSet::new)); list.forEach(System.out::println); //使用 方法來實現,流轉String字符串 stream.collect(Collectors.joining());
之後開發的時候,要多考慮,List,Set,這些轉換是否可使用JAVA8提供的這些stream來實現.用到實際開發中.
public class StreamTest5 { public static void main(String[] args) { //集合,所有轉換大寫,而後輸出. List<String> list = Arrays.asList("hello", "world", "hello world"); //要考慮能不能用函數式接口,lambda表達式的技能?顯然是能夠呢 //這是否是映射? 先要要用map. 給定一個參數,返回一個結果. //java8提供這些接口,就是爲了方便開發者.合理的應用. list.stream().map(String::toUpperCase).collect(Collectors.toList()).forEach(System.out::println); //求出每一個數字的平方,而後打印出來 List<Integer> list1 = Arrays.asList(1, 2, 3, 4, 5); list1.stream().map(item -> item * item).collect(Collectors.toList()).forEach(System.out::println); } }
//要考慮能不能用函數式接口,lambda表達式的技能?顯然是能夠呢 //這是否是映射? 先要要用map. 給定一個參數,返回一個結果. //java8提供這些接口,就是爲了方便開發者.合理的應用.
/** * Returns a stream consisting of the results of replacing each element of * this stream with the contents of a mapped stream produced by applying * the provided mapping function to each element. Each mapped stream is * {@link java.util.stream.BaseStream#close() closed} after its contents * have been placed into this stream. (If a mapped stream is {@code null} * an empty stream is used, instead.) * * <p>This is an <a href="package-summary.html#StreamOps">intermediate * operation</a>. * * @apiNote * The {@code flatMap()} operation has the effect of applying a one-to-many * transformation to the elements of the stream, and then flattening the * resulting elements into a new stream. * * <p><b>Examples.</b> * * <p>If {@code orders} is a stream of purchase orders, and each purchase * order contains a collection of line items, then the following produces a * stream containing all the line items in all the orders: * <pre>{@code * orders.flatMap(order -> order.getLineItems().stream())... * }</pre> * * <p>If {@code path} is the path to a file, then the following produces a * stream of the {@code words} contained in that file: * <pre>{@code * Stream<String> lines = Files.lines(path, StandardCharsets.UTF_8); * Stream<String> words = lines.flatMap(line -> Stream.of(line.split(" +"))); * }</pre> * The {@code mapper} function passed to {@code flatMap} splits a line, * using a simple regular expression, into an array of words, and then * creates a stream of words from that array. * * @param <R> The element type of the new stream * @param mapper a <a href="package-summary.html#NonInterference">non-interfering</a>, * <a href="package-summary.html#Statelessness">stateless</a> * function to apply to each element which produces a stream * of new values * @return the new stream */ <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
和map很像,可是徹底不一樣.不然就不會存在這個方法了.
扁平化的map;
1.map中映射的時候, 一個集合有三個List,每一個List又有不一樣的值.映射完以後,模塊還在
2.flatMap中映射的時候,一個集合有三個List, 打平的去給融合的到一個list中.
實例Example:
//每個元素都乘方,而後將數據做爲一個總體,輸出. 當作一個集合. 就要用flatmap() Stream<List<Integer>> listStream = Stream.of(Arrays.asList(1), Arrays.asList(2, 3), Arrays.asList(4, 5, 6)); listStream.flatMap(theList->theList.stream()).map(integer -> integer*integer).forEach(System.out::println);
/** * Returns an infinite sequential unordered stream where each element is * generated by the provided {@code Supplier}. This is suitable for * generating constant streams, streams of random elements, etc. * * @param <T> the type of stream elements * @param s the {@code Supplier} of generated elements * @return a new infinite sequential unordered {@code Stream} */ public static<T> Stream<T> generate(Supplier<T> s) { Objects.requireNonNull(s); return StreamSupport.stream( new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false); }
如何使用?以下Example:
public class StreamTest6 { public static void main(String[] args) { Stream<String> generate = Stream.generate(UUID.randomUUID()::toString); System.out.println(generate.findFirst()); } }
Optional<T> findFirst();
爲何這個findFirst()方法會返回一個Optional?
由於Optional,就是爲了規避NPE的問題.
因此此處須要使用Optional.ifPresent(),這纔是Optional類的正確使用方法.應該修改成:
public class StreamTest6 { public static void main(String[] args) { Stream<String> generate = Stream.generate(UUID.randomUUID()::toString); generate.findFirst().ifPresent(System.out::println); } }
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) { Objects.requireNonNull(f); final Iterator<T> iterator = new Iterator<T>() { @SuppressWarnings("unchecked") T t = (T) Streams.NONE; @Override public boolean hasNext() { return true; } @Override public T next() { return t = (t == Streams.NONE) ? seed : f.apply(t); } }; return StreamSupport.stream(Spliterators.spliteratorUnknownSize( iterator, Spliterator.ORDERED | Spliterator.IMMUTABLE), false); }
如何使用?
package com.dawa.jdk8.stream; import java.util.UUID; import java.util.stream.Stream; public class StreamTest6 { public static void main(String[] args) { Stream<String> generate = Stream.generate(UUID.randomUUID()::toString); generate.findFirst().ifPresent(System.out::println); //若是不加限制,iterate 會變成一個無限流. //Stream.iterate(1, integer -> integer + 2).forEach(System.out::println); //因此在使用的時候必定不要單獨使用. //要搭配limit()方法,一箇中間操做,使用. Stream.iterate(1, integer -> integer + 2).limit(6).forEach(System.out::println); } }
注意: //若是不加限制,iterate 會變成一個無限流.
//因此在使用的時候必定不要單獨使用.
//要搭配limit()方法,一箇中間操做,使用.
找出(1,3,5,7,9)流中大於2的元素,而後將每一個元素乘以2,而後忽略流中的前兩個元素,而後再取出流的前兩個元素,最後求出流中元素的總和.
//找出(1,3,5,7,9)流中大於2的元素,而後將每一個元素乘以2,而後忽略流中的前兩個元素,而後再取出流的前兩個元素,最後求出流中元素的總和. // Stream<Integer> stream = Stream.of(1, 3, 5, 7, 9); Stream<Integer> stream = Stream.iterate(1, integer -> integer + 2).limit(6);//經過iterate方法來獲取值 System.out.println(stream.filter(integer -> integer > 2).mapToInt(integer -> integer * 2).skip(2).limit(2).sum()); //用到的方法. map,mapToint,skip,limit.
...skip() 跳過
...limit() 截取
...map().mapToInt(),mapToDouble().... 映射
mapToInt... 避免自動裝箱和自動拆箱.(避免性能損耗).
...sum(),min(),max(). 最大,最小,求和等等
sum()返回值類型是int.
min().max(),返回值類型是:OptionalInt.
爲何呢?Optional類,由於使用與否,本質是取決於,這個值可不可能爲空.
summaryStatistics():小結,總結.流中的數據的簡單統計.
如:一個小結對象:IntSummaryStatistics{count=2, sum=32, min=14, average=16.000000, max=18}
這個類提供了各樣的方法.
getCount getSum getMin getMax getAverage toString
上面的案例裏面,已經使用了Stream類中的大量的方法.若有須要,自行查詢官方源碼.
注意:在對流進行中間操做的時候,會返回一個全新的流.直到進行一個終止操做的時候,纔會獲得最終的結果.
剛纔無心之間,在操做的時候,拋出來一個這樣的異常,提示流已經被關閉.
覆盤一下代碼:
Stream<Integer> stream = Stream.iterate(1, integer -> integer + 2).limit(6); System.out.println(stream); System.out.println(stream.filter(integer -> integer > 2)); System.out.println(stream.distinct());
流的特色
如何規避?
先生成一個流,操做完以後,再生成一個流.
緊接着去操做新生成的流.
Stream<Integer> stream = Stream.iterate(1, integer -> integer + 2).limit(6);//經過iterate方法來獲取值 System.out.println(stream); Stream<Integer> stream1 = stream.filter(integer -> integer > 2); System.out.println(stream1); Stream<Integer> stream2 = stream1.distinct(); System.out.println(stream2);
Example:
public class StreamTest7 { public static void main(String[] args) { List<String> list = Arrays.asList("hello", "world", "hello world"); //首字母大寫,其餘的小寫,而後打印輸出. list.stream().map(item -> item.substring(0, 1).toUpperCase() + item.substring(1)).forEach(System.out::println); //另外的操做 list.stream().map(item -> { String result = item.substring(0, 1).toUpperCase() + item.substring(1); System.out.println("test"); return result; });//運行以後沒有值 //另外的操做 list.stream().map(item -> { String result = item.substring(0, 1).toUpperCase() + item.substring(1); System.out.println("test"); return result; }).forEach(System.out::println);//運行以後可以執行. //緣由:中間操做,若是沒有終止操做,是不會本身執行的,是lazy類型的.是惰性求值的. } }
緣由:中間操做,若是沒有終止操做,是不會本身執行的,是lazy類型的.是惰性求值的.
再考慮一下效率問題.
也許可能會認爲,屢次中間操做,會屢次循環,會下降效率.
其實只執行了一次.並不會影響效率.
會有一個容器,把全部的中間操做放在一塊兒.一次執行.並不會有冗餘操做.
如何區分中間操做和終止操做
中間操做都會返回一個Stream對象,好比說返回Stream
再看另一個操做:關於中間操做和終止操做的影響
public class StreamTest8 { public static void main(String[] args) { IntStream.iterate(0, i -> (i + 1) % 2).distinct().limit(6).forEach(System.out::println); } }
上述代碼跑起來以後是不會自動終止的.
應該修改成:
public class StreamTest8 { public static void main(String[] args) { IntStream.iterate(0, i -> (i + 1) % 2).limit(6).distinct().forEach(System.out::println); } }
這個緣由就是由於中間操做和終止操做的影響.
若是先執行limit,就是一個終止操做.而後再消除重複一次,程序就會終止.
若是先執行消除重複操做,就是第一種狀況,返回一個流,再截取6個,流並無關閉.
流的本質三個主要操做:源->中間操做->中間操做->...->終止操做
這裏咱們藉助一個SQL來進行參照學習
select name from student where age>20 and address='beijing' order by age desc;
描述性的語言
經過stream把這個SQL描述出來
student.stream() .filter(student->student.getAge>20) .filter(student->student.getAddress() .equals("beijing") .sorted(...) .forEach(student->System.out.println(student.getName())); //這個描述和上面的SQL實際上是等價的.
你只是給DB發送了一個指令,而沒有所怎麼去找.你只是給出了一個描述,而後根據降序之類的規則去進行篩選.對於整個過程,你徹底沒有告訴底層去怎麼實現.
SQL是這樣,Stream也是這樣.只是描述性的語言.
這種方式就叫作內部迭代.
外部迭代(以前的方式)
不是描述性的處理方式,徹底基於老版本的實現方式.和描述性語言相比,這個可讀性太差.
都是串行化的操做,不能並行化
for(int i=0;i<student.size();i++){ Student student = students.get(i); if(student.getAge()>20&&student.getAddress().equals("beijing")){ list.add(student); } } //而後進行排序 Collection.sort(list,Comparator()...); //而後再去尋找須要的東西 for(Student student:list){ System.out.println(student.getName); }
內部迭代(描述性語言)
新人也是能看懂的吧.
student.stream() .filter(student->student.getAge>20) .filter(student->student.getAddress() .equals("beijing") .sorted(...) .forEach(student->System.out.println(student.getName()));
Stream的出現和集合是密不可分的.
內部迭代和外部迭代最本質的區別
總結
最須要注意的
流的執行原理必定不是一個方法一個方法的執行循環遍歷的.
串行流(stream())和並行流(parallelStream())的執行效率判斷.
package com.dawa.jdk8.stream; import java.util.ArrayList; import java.util.UUID; import java.util.concurrent.TimeUnit; public class StreamTest9 { public static void main(String[] args) { ArrayList<Object> list = new ArrayList<>(5000000); for (int i = 0; i < 5000000; i++) { list.add(UUID.randomUUID().toString()); } System.out.println("開始排序"); long startTime = System.nanoTime();//納秒 比毫秒的精度高 list.stream().sorted().count(); long endTime = System.nanoTime(); //納秒, 結束時間 long millis = TimeUnit.NANOSECONDS.toMillis(endTime - startTime); System.out.println("耗時:" + millis + "毫秒"); } }
運行結果: 串行耗時:4.0秒
public class StreamTest9 { public static void main(String[] args) { ArrayList<Object> list = new ArrayList<>(5000000); for (int i = 0; i < 5000000; i++) { list.add(UUID.randomUUID().toString()); } System.out.println("開始排序"); long startTime = System.nanoTime();//納秒 比毫秒的精度高 list.parallelStream().sorted().count(); long endTime = System.nanoTime(); //納秒, 結束時間 long millis = TimeUnit.NANOSECONDS.toMillis(endTime - startTime); System.out.println("耗時:" + millis + "毫秒"); } }
運行結果:並行耗時:1.1秒
並行和串行 - 時間成本相差:3-5倍.
public class StreamTest10 { public static void main(String[] args) { List<String> list = Arrays.asList("hello", "world", "hello world"); //找出列表中,長度爲5的第一個單詞,同時將長度5打印出來. // list.stream().mapToInt( String::length).filter(length -> length == 5).findFirst().ifPresent(System.out::println); list.stream().mapToInt(item -> { int length = item.length(); System.out.println(item); return length; }).filter(length -> length == 5).findFirst().ifPresent(System.out::println); } }
結果集:
爲何打印的時候只打印了1個?
緣由:容器裏面存放的是對每個容器的操做.
當對流進行迭代,處理的時候,會拿着容器的操做,會逐個的運用到值上.這
若是不知足過濾規則,則還會發生短路運算操做.這是緣由之二.只要找到符合條件的,後面就都不會運行.
如:沒有知足的規則,則會進行所有執行完.因此就會出現以下結果:
案例:找出集合中全部的單詞,並去重.(flatMap方法的應用)
public class StreamTest11 { public static void main(String[] args) { //找出集合中全部的單詞,並去重. List<String> list = Arrays.asList("hello world", "hello welcome", "hello", "hello world hello", "hello world welcome"); //要輸出: hello world welcome. // list.stream().map(item -> item.split(" ")).distinct().collect(Collectors.toList()); //不對 List<String> collect = list.stream().map(item -> item.split(" ")).flatMap(Arrays::stream).distinct().collect(Collectors.toList()); collect.forEach(System.out::println); } }
案例:將兩個集合組合起來, 打招呼-人名(flatMap的應用)
public class StreamTest12 { public static void main(String[] args) { //將兩個集合組合起來, 打招呼-人名 List<String> list1 = Arrays.asList("Hi", "Hello", "你好"); List<String> list2 = Arrays.asList("zhangsan", "lisi", "wangwu", "zhaoliu"); // list1.stream().map(item->item.concat(list2.stream().map())) List<String> collect = list1.stream().flatMap(item -> list2.stream().map(item2 ->item+ " " + item2)).collect(Collectors.toList()); collect.forEach(System.out::println); } }
如SQL中的group by.
select * from studeny group by name;
Result:Map<String,List
傳統的實現思路:
經過流的方式來實現分組(groupingby()方法)
public class StreamTest13 { public static void main(String[] args) { Student student1 = new Student("zhangsan", 100, 20); Student student2 = new Student("lisi", 90, 20); Student student3 = new Student("wangwu", 90, 30); Student student4 = new Student("zhaoliu", 80, 40); List<Student> students = Arrays.asList(student1, student2, student3, student4); //Map<String, List<Student>> collect = students.stream().collect(Collectors.groupingBy(Student::getName)); // System.out.println(collect); Map<Integer, List<Student>> collect = students.stream().collect(Collectors.groupingBy(Student::getScore)); System.out.println(collect); } }
這種SQL如何用流來實現?
select name,count(*) from student group by name;
很容易:
Map<String, Long> collect = students.stream().collect(Collectors.groupingBy(Student::getName, Collectors.counting())); System.out.println(collect);
先實現名字的分組,而後再取組內的平均值如何用流實現?
Map<String, Double> collect = students.stream().collect(Collectors.groupingBy(Student::getName, Collectors.averagingDouble(Student::getScore))); System.out.println(collect);
以上所寫的都是關於分組的概念.
分組:group by
分區:partition by
分區
分區是分組的特例,好比Boolean,只有true和false. 上述案例,好比以90分爲分界點,分區
Map<Boolean, List<Student>> collect = students.stream().collect(Collectors.partitioningBy(student -> student.getScore() > 90)); System.out.println(collect);
collect.get(true);//獲取ture對應的值 collect.get(false);//獲取false對應的值