2020了你還不會Java8新特性?方法引用詳解及Stream 流介紹和操作方式詳解(三)
方法引用詳解
方法引用: method reference
方法引用實際上是Lambda表示式的一種語法糖
我們可以將方法引用看作是一個「函式指標」,function pointer
方法引用共分為4類:
- 類名::靜態方法名
- 引用名(物件名)::例項方法名
- 類名::例項方法名 (比較不好理解,個地方呼叫的方法只有一個引數,為什麼還能正常呼叫呢? 因為呼叫比較時,第一個物件來呼叫getStudentByScore1. 第二個物件來當做引數)
- 構造方法引用: 類名::new
public class StudentTest { public static void main(String[] args) { Student student = new Student("zhangsan",10); Student student1 = new Student("lisi",40); Student student2 = new Student("wangwu",30); Student student3 = new Student("zhaoliu",550); List<Student> list = Arrays.asList(student, student2, student3, student1); // list.forEach(item -> System.out.println(item.getName())); //1. 類名 :: 靜態方法 // list.sort((studentpar1,studentpar2) -> Student.getStudentByScore(studentpar1,studentpar2)); list.sort(Student::getStudentByScore); list.forEach(item -> System.out.println(item.getScore())); System.out.println(" - - - - - - - -- "); // 2. 引用名(物件名)::例項方法名 StudentMethod studentMethod = new StudentMethod(); list.sort(studentMethod::getStudentBySource); list.forEach(item -> System.out.println(item.getScore())); System.out.println(" - - - -- -- "); // 3. 類名:: 例項方法名 // 這個地方呼叫的方法只有一個引數,為什麼還能正常呼叫呢? 因為呼叫比較時,第一個物件來呼叫getStudentByScore1. 第二個物件來當做引數 list.sort(Student::getStudentByScore1); list.forEach(item -> System.out.println(item.getScore())); System.out.println("- - - - - - - -"); // 原生的sort 來舉個例子 List<String> list1 = Arrays.asList("da", "era", "a"); // Collections.sort(list1,(city1,city2) -> city1.compareToIgnoreCase(city2)); list1.sort(String::compareToIgnoreCase); list1.forEach(System.out::println); System.out.println("- - - - - - -- "); //4. 構造方法引用 StudentTest studentTest = new StudentTest(); System.out.println(studentTest.getString(String::new)); } public String getString(Supplier<String> supplier) { return supplier.get()+"hello"; } }
預設方法
defaute method
預設方法是指實現此介面時,預設方法已經被預設實現。
引入預設方法最重要的作用就是Java要保證向後相容。
情景一: 一個類,實現了兩個介面。兩個介面中有一個相同名字的預設方法。此時會報錯,需要從寫這個重名的方法
情景二: 約定:實現類的優先順序比介面的優先順序要高。 一個類,實現一個介面,繼承一個實現類。介面和實現類中有一個同名的方法,此時,此類會使用實現類中的方法。
Stream 流介紹和操作方式詳解
Collection提供了新的stream()方法。
流不儲存值,通過管道的方式獲取值。
本質是函式式的,對流的操作會生成一個結果,不過並不會修改底層的資料來源,集合可以作為流的底層資料來源。
延遲查詢,很多流操作(過濾、對映、排序等)等可以延遲實現。
通過流的方式可以更好的操作集合。使用函數語言程式設計更為流程。與lambda表示式搭配使用。
流由3部分構成:
- 源
- 零個或多箇中間操作(操作的是誰?操作的是源)
- 終止操作(得到一個結果)
流操作的分類:
- 惰性求值(中間操作)
- 及早求值(種植操作)
使用鏈式的呼叫方式sunc as : stream.xxx().yyy().zzz().count(); 沒有count的時候前邊的三個方法不會被呼叫。後續會進行舉例。
掌握流常用的api,瞭解底層。
流支援並行化,可以多執行緒操作。迭代器不支援並行化。
流怎麼用?
流的建立方式
- 通過靜態方法 : Stream stream = Stream.of();
- 通過陣列:Arrays.stream();
- 通過集合建立物件:Stream stream = list.stream;
流的簡單應用
public static void main(String[] args) {
IntStream.of(1,2,4,5,6).forEach(System.out::println);
IntStream.range(3, 8).forEach(System.out::println);
IntStream.rangeClosed(3, 8).forEach(System.out::println);
}
舉例:將一個數組中的數字都乘以二,然後求和。
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
System.out.println(list.stream().map(i -> i*2).reduce(0,Integer::sum));
}
函數語言程式設計和傳統面向物件程式設計根本上有什麼不同?
傳統面向物件程式設計傳遞的是資料。函數語言程式設計通過方法傳遞的是一種行為,行為指導了函式的處理,根據行為對資料進行加工。
舉例:流轉換成list的練習
public static void main(String[] args) {
Stream<String> stream = Stream.of("hello", "world", "hello world");
// String[] stringArray = stream.toArray(length -> new String[length]);
//替換成方法引用的方式 --> 構造方法引用.
String[] stringArray = stream.toArray(String[]::new);
Arrays.asList(stringArray).forEach(System.out::println);
System.out.println("- - - - - - - - - - -");
//將流轉換成list, 有現成的封裝好的方法
Stream<String> stream1 = Stream.of("hello", "world", "hello world");
List<String> collect = stream1.collect(Collectors.toList());// 本身是一個終止操作
collect.forEach(System.out::println);
System.out.println("- - - - - - ");
//使用原生的 collect 來將流轉成List
Stream<String> stream2 = Stream.of("hello", "world", "hello world");
// List<String> lis = stream2.collect(() -> new ArrayList(), (theList, item) -> theList.add(item),
// (theList1, theList2) -> theList1.addAll(theList2));
// 將上面的轉換成方法引用的方式 -- 這種方法不好理解.
List<String> list = stream2.collect(LinkedList::new, LinkedList::add, LinkedList::addAll);
//這種方法,如果想要返回ArrayList也可以實現.
// List<String> list1 = stream2.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
list.forEach(System.out::println);
}
Collectors類中包含了流轉換的多個輔助類
舉例: 將流 轉成各種型別的資料。
public static void main(String[] args) {
Stream<String> stream = Stream.of("hello", "world", "hello world");
//將流轉換成List 另一種方法
// List<String> list= stream.collect(Collectors.toCollection(ArrayList::new));
// list.forEach(System.out::println);
//將流轉成set
// Set<String> set = stream.collect(Collectors.toSet());
//轉成TreeSet
// TreeSet<String> set = stream.collect(Collectors.toCollection(TreeSet::new));
// set.forEach(System.out::println);
//轉成字串
String string = stream.collect(Collectors.joining());
System.out.println(string);
//Collectors 類中有多重輔助的方法.
}
遇到問題的時候,先思考一下能否用方法引用的方式,使用流的方式來操作。因為用起來比較簡單。
舉例:將集合中的每一個元素 轉換成大寫的字母, 給輸出來。
public static void main(String[] args) {
//將集合中的每一個元素 轉換成大寫的字母, 給輸出來
List<String> list = Arrays.asList("hello","world","hello world");
//轉成字串,然後轉成大寫.
// System.out.println(list.stream().collect(Collectors.joining()).toUpperCase());
//上面的程式碼 可以轉換成下邊的程式碼.
// System.out.println(String.join("", list).toUpperCase());
//視訊上給出的 還是List的大寫
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);
}
流中的 .map () 方法,是對集合中的每一個數據進行一下操作。
stream 的 flat操作。 打平操作。
public static void main(String[] args) {
// 舉例: flag 的操作, 打平. 一個集合中有三個陣列, 打平之後,三個陣列的元素依次排列.
Stream<List<Integer>> stream = Stream.of(Arrays.asList(1), Arrays.asList(2, 3), Arrays.asList(4, 5));
//將裡邊每一個ArrayList的資料 做一個平方. 然後打平. 輸出一個list
stream.flatMap(theList -> theList.stream()).map(item -> item * item).forEach(System.out::println);
}
Stream 其他方法介紹:
public static void main(String[] args) {
// stream 其他方法介紹.
// generate(). 生成stream物件
Stream<String> stream = Stream.generate(UUID.randomUUID()::toString);
// System.out.println(stream.findFirst().get());
// findFirst,找到第一個物件.然後就短路了,會返回一個Optional物件(為了避免NPE),不符合函數語言程式設計
// stream.findFirst().isPresent(System.out::print);
// iterate() 會生成 一個 無限的序列流.
// 一般不會單獨使用. 會使用limit 來限制一下總長度.
Stream.iterate(1, item -> item + 2).limit(6).forEach(System.out::println);
}
Stream 運算練習:(Stream提供了各種操作符)
舉例:找出該流中大於2的元素,然後每個元素*2 ,然後忽略掉流中的前兩個元素,然後再取流中的前兩個元素,最後求出流元素中的總和.
Stream<Integer> stream = Stream.iterate(1, item -> item + 2).limit(6);
//找出該流中大於2的元素,先使用filter()過濾.
//每個元素*2 使用mapToInt 避免重複拆箱.
//忽略掉流中的前兩個元素; 使用 skip(2)
//再取流中的前兩個元素; 使用limit(2)
//求出流元素中的總和. 使用sum()
System.out.println(stream.filter(item -> item>2).mapToInt(item -> item * 2).skip(2).limit(2).sum());
舉例:找出該流中大於2的元素,然後每個元素*2 ,然後忽略掉流中的前兩個元素,然後再取流中的前兩個元素,最後找到最小的元素.
// .min() 返回的是IntOptional.
// System.out.println(stream.filter(item -> item>2).mapToInt(item -> item * 2).skip(2).limit(2).min());
//應該這樣呼叫. 上邊的可能會出NPE異常
stream.filter(item -> item>2).mapToInt(item -> item * 2).skip(2).limit(2).min().ifPresent(System.out::println);
舉例:獲取最大值,最小值,求和等各種操作。 .summaryStatistics();
在練習的過程中發現了一個問題。如果是這樣連續列印兩條對流操作之後的結果。會報流未關閉的異常。
注意事項:流被重複使用了,或者流被關閉了,就會出異常。
如何避免:使用方法鏈的方式來處理流。 具體出現的原因,後續進行詳細的原始碼講解。
舉例 :中間操作(惰性求值) 和中止操作(及早求值)本質的區別
public static void main(String[] args) {
List<String> list = Arrays.asList("hello", "world", "hello world");
//首字母轉大寫
list.stream().map(item ->{
String s = item.substring(0, 1).toUpperCase() + item.substring(1);
System.out.println("test");
return s;
}).forEach(System.out::println);
//沒有遇到中止操作時,是不會執行中間操作的.是延遲的
// 遇到.forEach() 中止操作時,才會執行中間操作的程式碼
}
舉例:流使用順序不同的區別
//程式不會停止
IntStream.iterate(0,i->(i+1)%2).distinct().limit(6).forEach(System.out::println);
//程式會停止
IntStream.iterate(0,i->(i+1)%2).limit(6).distinct().forEach(System.out::println);
Stream底層深入
和迭代器不同的是,Stream可以並行化操作,迭代器只能命令式地、序列化操作
- 當使用穿行方式去遍歷時,每個item讀完後再讀下一個item
- 使用並行去遍歷時,資料會被分成多個段,其中每一個都在不同的執行緒中處理,然後將結果一起輸出。
Stream的並行操作依賴於Java7中引入的Fork/Join框架。
流(Stream)由3部分構成:
- 源(Source)
- 零個或多箇中間操作(Transforming values)(操作的是誰?操作的是源)
- 終止操作(Operations)(得到一個結果)
內部迭代和外部迭代
描述性的語言:sql和Stream的對比
select name from student where age > 20 and address = 'beijing' order by desc;
===================================================================================
Student.stream().filter(student -> student.getAge >20 ).filter(student -> student.getAddress().equals("beijing")).sorted(..).forEach(student -> System.out.println(student.getName));
上述的描述,並沒有明確的告訴底層具體要怎麼做,只是發出了描述性的資訊。這種流的方式就叫做內部迭代。針對於效能來說,流的操作肯定不會降低效能。
外邊迭代舉例: jdk8以前的用的方式。
List
list = new ArrayList<>(); for(int i = 0 ;i <= students.size();i++){
Student student = students.get(i);
If(student.getAge() > 20 )
list.add(student);
}
Collections.sort(list.....)
list.forEach().....
Stream的出現和集合是密不可分的。
集合關注的是資料與資料儲存本身,流關注的則是對資料的計算。
流與迭代器類似的一點是:流是無法重複使用或消費的。
如何區分中間操作和中止操作:
中間操作都會返回一個Stream物件,比如說返回Stream
中止操作則不會返回Stream型別,可能不返回值,也可能返回其他型別的單個值。
並行流的基本使用
舉例: 序列流和並行流的簡單舉例比較
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 startTime = System.nanoTime();
// list.parallelStream().sorted().count(); //序列流
list.parallelStream().sorted().count(); //並行流
long endTime = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(endTime - startTime);
System.out.println("排序時間為: "+ millis);
}
結果如圖,並行流和序列流時間上錯了4倍。
舉例: 打印出列表中出來第一個長度為5的單詞.. 同時將長度5打印出來.
public static void main(String[] args) {
List<String> list = Arrays.asList("hello", "world", "hello world");
// list.stream().mapToInt(item -> item.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);
//返回的是hello , 不包含 world.
}
返回的是hello , 不包含 world.
流的操作原理: 把流想成一個容器,裡邊儲存的是對每一個元素的操作。操作時,把操作序列化。對同一個元素進行序列的操作。操作中還包含著短路操作。
舉例: 找出 這個集合中所有的單詞,而且要去重. flatMap()的使用。
public static void main(String[] args) {
//舉例; 找出 這個集合中所有的單詞,而且要去重.
List<String> list = Arrays.asList("hello welcome", "world hello", "hello world", "hello hello world");
// list.stream().map(item -> item.split(" ")).distinct()
// .collect(Collectors.toList()).forEach(System.out::println);
//使用map不能滿足需求, 使用flatMap
list.stream().map(item -> item.split(" ")).flatMap(Arrays::stream)
.distinct().collect(Collectors.toList()).forEach(System.out::println);
//結果為 hello welcome world
}
舉例:組合起來. 打印出 hi zhangsan , hi lisi , hi wangwu , hello zhangsan , hello lisi .... flatMap()的使用。
public static void main(String[] args) {
//組合起來. 打印出 hi zhangsan , hi lisi , hi wangwu , hello zhangsan , hello lisi ....
List<String> list = Arrays.asList("Hi", "Hello", "你好");
List<String> list1 = Arrays.asList("zhangsan", "lisi", "wangwu");
List<String> collect = list.stream().flatMap(item -> list1.stream().map(item2 -> item + " " +
item2)).collect(Collectors.toList());
collect.forEach(System.out::println);
}
舉例: 流對分組/分割槽操作的支援. group by / protition by
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("zhangsan", 80, 40);
List<Student> students = Arrays.asList(student1, student2, student3, student4);
//對學生按照姓名分組.
Map<String, List<Student>> listMap = students.stream().collect(Collectors.groupingBy(Student::getName));
System.out.println(listMap);
//對學生按照分數分組.
Map<Integer, List<Student>> collect = students.stream().collect(Collectors.groupingBy(Student::getScore));
System.out.println(collect);
//按照年齡分組.
Map<Integer, List<Student>> ageMap = students.stream().collect(Collectors.groupingBy(Student::getAge));
System.out.println(ageMap);
//按照名字分組後,獲取到每個分組的元素的個數.
Map<String, Long> nameCount = students.stream().collect(Collectors.groupingBy(Student::getName, Collectors.counting()));
System.out.println(nameCount);
//按照名字分組,求得每個組的平均值.
Map<String, Double> doubleMap = students.stream().collect(Collectors.groupingBy(Student::getName, Collectors.averagingDouble(Student::getScore)));
System.out.println(doubleMap);
//分割槽, 分組的一種特例. 只能分兩個組 true or flase . partitioning By
Map<Boolean, List<Student>> collect1 = students.stream().collect(Collectors.partitioningBy(student -> student.getScore() >= 90));
System.out.println(collect1);
}