2020你還不會Java8新特性?(學習過程記錄)
Java8(1)新特性介紹及Lambda表示式
前言:
跟大娃一塊看,把原來的電腦拿出來放中間看視訊用
--- 以後會有的課程 難度
- 深入Java 8 難度1
- 併發與netty 難度3
- JVM 難度4
- node 難度2
- spring精髓 難度1
課程中提到的知識:
前後端分離的開發,是靠node當做中間的
netty,已經成為國內外網際網路公司的標配。會涉及底層的原始碼的理解。
JVM 涉及的東西比較多。雖然天天用,但是沒有深入理解過。各種鎖,可見性等。與計算機原理息息相關的。
聖思園主要面對與已經工作的。大部分為一線的開發人員。
課程一定是完整的。由淺入深的。一定要有一種耐心。
對於基礎不好的,可以看看以前面授的時候錄製的視訊。不懂的一定要多查資料。
在講課過程中的設計思路:4000塊錢的收費標準。
jdk8
介紹:Java 8可謂Java語言歷史上變化最大的一個版本,其承諾要調整Java程式設計向著函式式風格邁進,這有助於編寫出更為簡潔、表達力更強,並且在很多情況下能夠利用並行硬體的程式碼。本門課程將會深入介紹Java 8新特性,學員將會通過本門課程的學習深入掌握Java 8新增特性並能靈活運用在專案中。學習者將學習到如何通過Lambda表示式使用一行程式碼編寫Java函式,如何通過這種功能使用新的Stream API進行程式設計,如何將冗長的集合處理程式碼壓縮為簡單且可讀性更好的流程式。學習建立和消費流的機制,分析其效能,能夠判斷何時應該呼叫API的並行執行特性。
課程的介紹:
- Java 8新特性介紹
- Lambda表示式介紹
- 使用Lambda表示式代替匿名內部類
- Lambda表示式的作用
- 外部迭代與內部迭代
- Java Lambda表示式語法詳解
- 函式式介面詳解
- 傳遞值與傳遞行為
- Stream深度解析
- Stream API詳解
- 序列流與並行流
- Stream構成
- Stream源生成方式
- Stream操作型別
- Stream轉換
- Optional詳解
- 預設方法詳解
- 方法與構造方法引用
- Predicate介面詳解
- Function介面詳解
- Consumer介面剖析
- Filter介紹
- Map-Reduce講解、中間操作與終止操作
- 新的Date API分析
拉姆達表示式: 函數語言程式設計。以前的叫做命令式的程式設計。
函數語言程式設計面向的是行為。好處:程式碼可讀性提高。
開發安卓的時候大量的匿名內部類。
提到的關鍵字:
kotlin ,JetBrains 。construction 構造
他以前在學習的時候,翻程式碼。
將要講解的各個技術的簡介、
課程講解的時候遇到的工具:
Mac , jdk8 ,idea(很多功能是通過外掛的形式來實現的)
Java8課程開始
lambda表示式
為什麼要使用lambda表示式
- 在Java中無法將函式座位引數傳遞給一個方法,也無法返回一個函式的方法。
- 在js中,函式的引數是一個函式。返回值是另一個函式的情況是非常常見的。是一門經典的函式式語言。
Java匿名內部類。
匿名內部類的介紹
Gradle的使用。可以完全使用maven的中央倉庫。
進行安卓的開發時,gradle已經成為標配了。
lambda:
匿名內部類
my_jButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Button Pressed! ");
}
});
改造後
my_jButton.addActionListener(e -> System.out.println("Button Pressed!"));
lambda表示式的基本結構:
(param1,param2,param3) ->{
}
函數語言程式設計: 一個接口裡邊只有一個抽象方法。
可以通過lambda表示式來例項。
關於函式式介面:
- 如果一個藉口只有一個抽象方法,那麼該介面就是一個函式式介面。
- 如果我們在某一個介面上聲明瞭functionalInterface註解,那麼編譯器就會按照函式是藉口的定義來要求改介面。
- 如果某個介面只有一個抽象方法,但是我們並沒有給介面宣告functionnaleInterface註解,編譯器依舊會給改介面看作是函式式介面。
通過例項對函式式介面的理解:
package com.erwa.jdk8;
@FunctionalInterface
interface MyInterface {
void test();
// Multiple non-overriding abstract methods found in interface com.erwa.jdk8.MyInterface
// void te();
//如果一個介面宣告一個抽象方法,但是這個方法重寫了 object類中的一個方法.
//介面的抽象方法不會加一.所以依然是函式方法.
// Object 類是所有類的父類.
@Override
String toString();
}
public class Test2 {
public void myTest(MyInterface myInterface) {
System.out.println(1);
myInterface.test();
System.out.println(2);
}
public static void main(String[] args) {
Test2 test2 = new Test2();
test2.myTest(() -> {
System.out.println(3);
});
}
}
接口裡邊從1.8開始也可以有方法實現了。default
預設方法。
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
* <p>Note that instances of functional interfaces can be created with
* lambda expressions, method references, or constructor reference
lambda表示式的作用:
- lambda表示式為Java添加了確實的函數語言程式設計特性,使我們能將函式當做一等公民看待。
- 在將函式座位一等公民的語言中,lambda表示式的型別是函式。但是在Java中,lambda表示式是物件,他們必須依附於一類特別的物件型別-函式式介面(function interface)
迭代的方式:
- 外部迭代:
- 內部迭代:
- 方法引用:
list.forEach(System.out::println);
介面中可以有預設方法和靜態方法。
流: stream
/**
* Returns a sequential {@code Stream} with this collection as its source.
*
* <p>This method should be overridden when the {@link #spliterator()}
* method cannot return a spliterator that is {@code IMMUTABLE},
* {@code CONCURRENT}, or <em>late-binding</em>. (See {@link #spliterator()}
* for details.)
*
* @implSpec
* The default implementation creates a sequential {@code Stream} from the
* collection's {@code Spliterator}.
*
* @return a sequential {@code Stream} over the elements in this collection
* @since 1.8
*/
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
關於流方式實現的舉例:
public static void main(String[] args) {
//函式式介面的實現方式
MyInterface1 i1 = () -> {};
System.out.println(i1.getClass().getInterfaces()[0]);
MyInterface2 i2 = () -> {};
System.out.println(i2.getClass().getInterfaces()[0]);
// 沒有上下文物件,一定會報錯的.
// () -> {};
//通過lambda來實現一個執行緒.
new Thread(() -> System.out.println("hello world")).start();
//有一個list ,將內容中的首字母變大寫輸出.
List<String> list = Arrays.asList("hello","world","hello world");
//通過lambda來實現所有字母程式設計大寫輸出.
// list.forEach(item -> System.out.println(item.toUpperCase()));
//把三個單詞放入到新的集合裡邊.
List<String> list1 = new ArrayList<>(); //diamond語法. 後邊的<>不用再放型別
// list.forEach(item -> list1.add(item.toUpperCase()));
// list1.forEach(System.out::println);
//進一步的改進. 流的方式
// list.stream();//單執行緒
// list.parallelStream(); //多執行緒
list.stream().map(item -> item.toUpperCase()).forEach(System.out::println);//單執行緒
list.stream().map(String::toUpperCase).forEach(System.out::println);
//上邊的兩種方法,都滿足函式式介面的方式.
}
lambda表示式的作
- 傳遞行為,而不僅僅是值
- 提升抽象層次
- API重用性更好
- 更加靈活
lambda基本語法
- (argument) -> (body)
- 如: (arg1,arg2...) -> (body)
Java lambda結構
- 一個Lambda表示式可以有0個或者多個引數
- 引數的型別既可以明確宣告,也可以根據上下文來推斷。例如:(int a) 與 (a) 效果相同
- 所有引數包含在圓括號內,引數之間用逗號相隔。
- 空圓括號代表引數集為空。
- 當只有一個引數,且型別可推倒時。圓括號()可省略。
- lambda表示式的主體可以包含0條或多條語句。
- 如果lambda表示式的主體只有一條語句,花括號{}可以省略,匿名函式的返回型別與該主體表達式一致。
- 如果lambda表示式的主體包含一條以上語句,則表示式必須包含在花括號中。匿名函式的韓繪製型別與程式碼塊的返回型別一致,諾沒有反回則為空。
高階函式:
如果一個函式接收一個函式作為引數,或者返回一個函式作為返回值,那麼該函式就叫做高階函式.
傳遞行為的舉例:
public static void main(String[] args) {
// 函式的測試
// 傳遞行為的一種方式.
FunctionTest functionTest = new FunctionTest();
int compute = functionTest.compute(1, value -> 2 * value);
System.out.println(compute);
System.out.println(functionTest.compute(2,value -> 5+ value));
System.out.println(functionTest.compute(3,a -> a * a));
System.out.println(functionTest.convert(5, a -> a + "hello "));
/**
* 高階函式:
* 如果一個函式接收一個函式作為引數,或者返回一個函式作為返回值,那麼該函式就叫做高階函式.
*/
}
//使用lambda表示式的話,可以直覺預定義行為.用的時候傳遞.
// 即 函數語言程式設計.
public int compute(int a, Function<Integer, Integer> function) {
return function.apply(a);
}
public String convert(int a, Function<Integer, String> function) {
return function.apply(a);
}
// 之前完成行為的做法. 提前把行為定義好,用的時候呼叫方法. 如:
public int method1(int a ){
return a * 2 ;
}
Function類中提供的預設方法的講解:
/**
* Returns a composed function that first applies the {@code before}
* function to its input, and then applies this function to the result.
* If evaluation of either function throws an exception, it is relayed to
* the caller of the composed function.
返回一個組合的函式。對應用完引數後的結果,再次執行apply
*
* @param <V> the type of input to the {@code before} function, and to the
* composed function
* @param before the function to apply before this function is applied
* @return a composed function that first applies the {@code before}
* function and then applies this function
* @throws NullPointerException if before is null
*
* @see #andThen(Function)
*/
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
/**
* Returns a composed function that first applies this function to
* its input, and then applies the {@code after} function to the result.
* If evaluation of either function throws an exception, it is relayed to
* the caller of the composed function.
*
* @param <V> the type of output of the {@code after} function, and of the
* composed function
* @param after the function to apply after this function is applied
* @return a composed function that first applies this function and then
* applies the {@code after} function
* @throws NullPointerException if after is null
*
* @see #compose(Function)
*/
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
compose : 組合function, 形成兩個function的串聯。 先執行引數
andThen :先應用當前的函式apply,然後再當做引數再次執行apply。 後執行引數。
identity:輸入什麼返回什麼。
BiFunction: 整合兩個函式的方法。
為什麼BiFunction不提供 compose ,只提供andThen呢?
因為如果提供compose方法的話,只能獲取一個引數的返回值。不合理。
public static void main(String[] args) {
FunctionTest2 functionTest2 = new FunctionTest2();
// compose
// System.out.println(functionTest2.compute(2,a -> a * 3,b -> b * b));
// andThen
// System.out.println(functionTest2.compute2(2,a -> a * 3,b -> b * b));
//BiFunction
// System.out.println(functionTest2.compute3(1,2, (a,b) -> a - b));
// System.out.println(functionTest2.compute3(1,2, (a,b) -> a * b));
// System.out.println(functionTest2.compute3(1,2, (a,b) -> a + b));
// System.out.println(functionTest2.compute3(1,2, (a,b) -> a / b));
//BiFunction andThen
System.out.println(functionTest2.compute4(2,3,(a,b) ->a + b , a -> a * a ));
}
//compose : 組合function, 形成兩個function的串聯。 先執行引數
//andThen :先應用當前的函式apply,然後再當做引數再次執行apply。 後執行引數
public int compute(int a, Function<Integer, Integer> function1, Function<Integer, Integer> function2) {
return function1.compose(function2).apply(a);
}
public int compute2(int a, Function<Integer, Integer> function1, Function<Integer, Integer> function2) {
return function1.andThen(function2).apply(a);
}
//BiFunction
//求兩個引數的和
//先定義一個抽象的行為.
public int compute3(int a, int b, BiFunction<Integer, Integer, Integer> biFunction) {
return biFunction.apply(a, b);
}
//BiFunction andThen
public int compute4(int a, int b, BiFunction<Integer, Integer, Integer> biFunction, Function<Integer, Integer> function) {
return biFunction.andThen(function).apply(a, b);
}
測試 函式式介面的例項:
public class PersonTest {
public static void main(String[] args) {
List<Person> personList = new ArrayList<>();
personList.add(new Person("zhangsan", 20));
personList.add(new Person("zhangsan", 28));
personList.add(new Person("lisi", 30));
personList.add(new Person("wangwu", 40));
PersonTest test = new PersonTest();
//測試 getPersonUsername
// List<Person> personList1 = test.getPersonUsername("zhangsan", personList);
// personList1.forEach(person -> System.out.println(person.getUsername()));
//測試 getPersonByAge
List<Person> personByAge = test.getPersonByAge(25, personList);
personByAge.forEach(person -> System.out.println(person.getAge()));
//測試第三種: 自定義輸入行為
List<Person> list = test.getPersonByAge2(20,personList,(age,persons) ->{
return persons.stream().filter(person -> person.getAge() > age).collect(Collectors.toList());
});
list.forEach(person -> System.out.println(person.getAge()));
}
public List<Person> getPersonUsername(String username, List<Person> personList) {
return personList.stream().filter(person -> person.getUsername().equals(username)).collect(Collectors.toList());
}
public List<Person> getPersonByAge(int age, List<Person> personList) {
//使用BiFunction的方式
// BiFunction<Integer, List<Person>, List<Person>> biFunction = (ageOfPerson, list) -> {
// return list.stream().filter(person -> person.getAge() > ageOfPerson ).collect(Collectors.toList());
// };
//變換之後:
BiFunction<Integer, List<Person>, List<Person>> biFunction = (ageOfPerson, list) ->
list.stream().filter(person -> person.getAge() > ageOfPerson ).collect(Collectors.toList());
return biFunction.apply(age, personList);
}
//第三種方式, 動作也讓使用者自己定義傳進來
public List<Person> getPersonByAge2(int age ,List<Person> list,BiFunction<Integer,List<Person>,List<Person>> biFunction){
return biFunction.apply(age, list);
}
}
函式式介面的真諦: 傳遞的是行為,而不是資料
。
public static void main(String[] args) {
//給定一個輸入引數,判斷是否滿足條件,滿足的話返回true
Predicate<String> predicate = p -> p.length() > 5;
System.out.println(predicate.test("nnihaoda"));
}
到現在為止,只是講解了Java.lang.function包下的幾個最重要的,經常使用的方法。
2020年01月01日19:03:33 新的一年開始,記錄一下每次學習的時間。
Predicate 謂語。 類中包含的方法:
boolean test(T t);
default Predicate<T> or(Predicate<? super T> other)
default Predicate<T> negate()
default Predicate<T> and(Predicate<? super T> other)
static <T> Predicate<T> isEqual(Object targetRef)
函數語言程式設計,注重傳遞行為,而不是傳遞值。
public class PredicateTest2 {
/**
* 測試Predicate中的test方法
*/
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
PredicateTest2 predicateTest2 = new PredicateTest2();
//獲取大於5的數字
predicateTest2.getAllFunction(list,item -> item > 5);
System.out.println("--------");
//獲取所有的偶數
predicateTest2.getAllFunction(list,item -> item % 2 ==0);
System.out.println("--------");
//獲取所有的數字
predicateTest2.getAllFunction(list,item -> true);
//獲取大於5並且是偶數的
System.out.println("--------");
predicateTest2.testAnd(list,item -> item > 5,item -> item % 2 == 0);
}
public void getAllFunction(List<Integer> list, Predicate<Integer> predicate){
for (Integer integer : list) {
if (predicate.test(integer)) {
System.out.println(integer);
}
}
}
// test or and
public void testAnd(List<Integer> list,Predicate<Integer> integerPredicate,Predicate<Integer> integerPredicate1){
for (Integer integer : list) {
if (integerPredicate.and(integerPredicate1).test(integer)) {
System.out.println(integer);
}
}
}
}
lambda表示式到底給我們帶來了什麼?原來通過面向物件的時候一個方法只能執行一種功能。現在傳遞的是行為,一個方法可以多次呼叫。
邏輯與或非三種的理解.
Supplier類 供應廠商;供應者 (不接收引數,返回結果)
用於什麼場合? 工廠
2020年1月3日08:06:28
BinaryOperator 介面
public class SinaryOpertorTest {
public static void main(String[] args) {
SinaryOpertorTest sinaryOpertorTest = new SinaryOpertorTest();
System.out.println(sinaryOpertorTest.compute(1,2,(a,b) -> a+b));
System.out.println("-- -- - - - -- -");
System.out.println(sinaryOpertorTest.getMax("hello123","world",(a,b) -> a.length() - b.length()));
}
private int compute(int a, int b, BinaryOperator<Integer> binaryOperator) {
return binaryOperator.apply(a, b);
}
private String getMax(String a, String b, Comparator<String> comparator) {
return BinaryOperator.maxBy(comparator).apply(a, b);
}
}
Optional final :Optional 不要試圖用來當做引數, 一般只用來接收返回值,來規避值的空指標異常的問題。
- empty()
- of()
- ofNullable()
- isPresent()
- get()
- ...
public class OptionalTest {
public static void main(String[] args) {
Optional<String> optional = Optional.of("hello");
//不確定是否為 空是 呼叫和這個方法
// Optional<String> optional2 = Optional.ofNullable("hello");
// Optional<String> optional1 = Optional.empty();
//過時
// if (optional.isPresent()) {
// System.out.println(optional.get());
// }
optional.ifPresent(item -> System.out.println(item));
System.out.println(optional.orElse("nihao"));
System.out.println(optional.orElseGet(() -> "nihao"));
}
public class OptionalTest2 {
public static void main(String[] args) {
Employee employee = new Employee();
employee.setName("dawa");
Employee employee1 = new Employee();
employee1.setName("erwa");
List<Employee> list = Arrays.asList(employee, employee1);
Company company = new Company("gongsi", list);
Optional<Company> optionalCompany = Optional.ofNullable(company);
System.out.println(optionalCompany.map(company1 -> company1.getList()).orElse(Collections.emptyList()));
}
}
Java8(2)方法引用詳解及Stream流介紹
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);
}
Java8(3)Collector類原始碼分析
繼續學習Java8 新特性。
Collector類原始碼分析2020了你還不會Java8新特性?
jdk8是怎麼對底層完成支援的。不瞭解底層,平時用還可以,但是遇到問題的時候就會卡在那裡。遲遲滅有解決方案。在學習一門新技術時,先學習怎麼去用,不要執著於原始碼。但是隨著用的越來越多,你去了解底層是比較好的一種學習方法。
有多種方法可以實現同一個功能.什麼方式更好呢? 越具體的方法越好. 減少自動裝箱拆箱操作
- collect : 收集器
- Collector作為collect方法的引數。
- Collector作為一個介面。它是一個可變的匯聚操作,將輸入元素累計到一個可變的結果容器中;它會在所有元素都處理完畢後將累計的結果作為一個最終的表示(這是一個可選操作);它支援序列與並行兩種方式執行。(並不是說並行一定比序列快。)
- Collects本身提供了關於Collectoe的常見匯聚實現,Collectors本身實際上是一個工廠。
- 為了確保序列和並行的結果一致,需要進行額外的處理。必須要滿足兩個約束。
identity 同一性
associativity 結合性 - 同一性:對於任何一條並行線路來說 ,需要滿足a == combiner.apply(a, supplier.get())。舉例來說:
(Listlist1,List list2 -> {list1.addAll(list2);return list1})
結合性: 下方有舉例。
Collector收集器的實現原始碼詳解
/**
* A <a href="package-summary.html#Reduction">mutable reduction operation</a> that
* accumulates input elements into a mutable result container, optionally transforming
* the accumulated result into a final representation after all input elements
* have been processed. Reduction operations can be performed either sequentially
* or in parallel.
Collector作為一個介面。它是一個可變的匯聚操作,將輸入元素累計到一個可變的結果容器中;它會在所有元素都處理 完畢後將累計的結果作為一個最終的表示(這是一個可選操作);它支援序列與並行兩種方式執行。(並不是說並行一定比序列快。)
* <p>Examples of mutable reduction operations include:
* accumulating elements into a {@code Collection}; concatenating
* strings using a {@code StringBuilder}; computing summary information about
* elements such as sum, min, max, or average; computing "pivot table" summaries
* such as "maximum valued transaction by seller", etc. The class {@link Collectors}
* provides implementations of many common mutable reductions.
Collects本身提供了關於Collectoe的常見匯聚實現,Collectors本身實際上是一個工廠。
* <p>A {@code Collector} is specified by four functions that work together to
* accumulate entries into a mutable result container, and optionally perform
* a final transform on the result. They are: <ul>
* <li>creation of a new result container ({@link #supplier()})</li>
* <li>incorporating a new data element into a result container ({@link #accumulator()})</li>
* <li>combining two result containers into one ({@link #combiner()})</li>
* <li>performing an optional final transform on the container ({@link #finisher()})</li>
* </ul>
Collector 包含了4個引數
* <p>Collectors also have a set of characteristics, such as
* {@link Characteristics#CONCURRENT}, that provide hints that can be used by a
* reduction implementation to provide better performance.
*
* <p>A sequential implementation of a reduction using a collector would
* create a single result container using the supplier function, and invoke the
* accumulator function once for each input element. A parallel implementation
* would partition the input, create a result container for each partition,
* accumulate the contents of each partition into a subresult for that partition,
* and then use the combiner function to merge the subresults into a combined
* result.
舉例說明:
1,2, 3, 4 四個部分結果。
1,2 -》 5
5,3 -》 6
6,4 -》 6
### 同一性和結合性的解析:
* <p>To ensure that sequential and parallel executions produce equivalent
* results, the collector functions must satisfy an <em>identity</em> and an
* <a href="package-summary.html#Associativity">associativity</a> constraints.
為了確保序列和並行的結果一致,需要進行額外的處理。必須要滿足兩個約束。
identity 同一性
associativity 結合性
* <p>The identity constraint says that for any partially accumulated result,
* combining it with an empty result container must produce an equivalent
* result. That is, for a partially accumulated result {@code a} that is the
* result of any series of accumulator and combiner invocations, {@code a} must
* be equivalent to {@code combiner.apply(a, supplier.get())}.
同一性: 對於任何一條並行線路來說,需要滿足a == combiner.apply(a, supplier.get())
* <p>The associativity constraint says that splitting the computation must
* produce an equivalent result. That is, for any input elements {@code t1}
* and {@code t2}, the results {@code r1} and {@code r2} in the computation
* below must be equivalent:
* <pre>{@code
* A a1 = supplier.get(); 序列:
* accumulator.accept(a1, t1); 第一個引數,每次累加的中間結果。 第二個引數,下一個要處理的引數
* accumulator.accept(a1, t2);
* R r1 = finisher.apply(a1); // result without splitting
*
* A a2 = supplier.get(); 並行:
* accumulator.accept(a2, t1); 第一個引數,每次累加的中間結果。 第二個引數,下一個要處理的引數
* A a3 = supplier.get();
* accumulator.accept(a3, t2);
* R r2 = finisher.apply(combiner.apply(a2, a3)); // result with splitting
* } </pre>
結合性: 如上例。 最終要求 r1 == r2
* <p>For collectors that do not have the {@code UNORDERED} characteristic,
* two accumulated results {@code a1} and {@code a2} are equivalent if
* {@code finisher.apply(a1).equals(finisher.apply(a2))}. For unordered
* collectors, equivalence is relaxed to allow for non-equality related to
* differences in order. (For example, an unordered collector that accumulated
* elements to a {@code List} would consider two lists equivalent if they
* contained the same elements, ignoring order.)
對於無序的收集器來說,等價性就被放鬆了,會考慮到順序上的區別對應的不相等性。
兩個集合中包含了相同的元素,但是忽略了順序。這種情況下兩個的集合也是等價的。
### collector複合與注意事項:
* <p>Libraries that implement reduction (匯聚) based on {@code Collector}, such as
* {@link Stream#collect(Collector)}, must adhere to the following constraints:
* <ul>
* <li>The first argument passed to the accumulator function, both
* arguments passed to the combiner function, and the argument passed to the
* finisher function must be the result of a previous invocation of the
* result supplier, accumulator, or combiner functions.</li>
* <li>The implementation should not do anything with the result of any of
* the result supplier, accumulator, or combiner functions other than to
* pass them again to the accumulator, combiner, or finisher functions,
* or return them to the caller of the reduction operation.</li>
具體的實現來說,不應該對中間返回的結果進行額外的操作。除了最終的返回的結果。
* <li>If a result is passed to the combiner or finisher
* function, and the same object is not returned from that function, it is
* never used again.</li>
如果一個結果被傳遞給combiner or finisher,但是並沒有返回一個你傳遞的物件,說明你生成了一個新的結果或者建立了新的物件。這個結果就不會再被使用了。
* <li>Once a result is passed to the combiner or finisher function, it
* is never passed to the accumulator function again.</li>
一旦一個結果被傳遞給了 combiner or finisher 函式,他就不會再被傳遞給了accumulator函數了。
* <li>For non-concurrent collectors, any result returned from the result
* supplier, accumulator, or combiner functions must be serially
* thread-confined. This enables collection to occur in parallel without
* the {@code Collector} needing to implement any additional synchronization.
* The reduction implementation must manage that the input is properly
* partitioned, that partitions are processed in isolation, and combining
* happens only after accumulation is complete.</li>
執行緒和執行緒之間的處理都是獨立的,最終結束時再進行合併。
* <li>For concurrent collectors, an implementation is free to (but not
* required to) implement reduction concurrently. A concurrent reduction
* is one where the accumulator function is called concurrently from
* multiple threads, using the same concurrently-modifiable result container,
* rather than keeping the result isolated during accumulation.
* A concurrent reduction should only be applied if the collector has the
* {@link Characteristics#UNORDERED} characteristics or if the
* originating data is unordered.</li>
如果不是併發收集器,4個執行緒會生成4箇中間結果。
是併發收集器的話,4個執行緒會同時呼叫一個結果容器。
* </ul>
*
* <p>In addition to the predefined implementations in {@link Collectors}, the
* static factory methods {@link #of(Supplier, BiConsumer, BinaryOperator, Characteristics...)}
* can be used to construct collectors. For example, you could create a collector
* that accumulates widgets into a {@code TreeSet} with:
*
* <pre>{@code
* Collector<Widget, ?, TreeSet<Widget>> intoSet =
* Collector.of(TreeSet::new, TreeSet::add,
* (left, right) -> { left.addAll(right); return left; });
* }</pre>
通過Collector.of(傳進一個新的要操作的元素,結果容器處理的步驟,多執行緒處理的操作)
將流中的每個Widget 新增到TreeSet中
* (This behavior is also implemented by the predefined collector
* {@link Collectors#toCollection(Supplier)}).
*
* @apiNote
* Performing a reduction operation with a {@code Collector} should produce a
* result equivalent to:
* <pre>{@code
* R container = collector.supplier().get();
* for (T t : data)
* collector.accumulator().accept(container, t);
* return collector.finisher().apply(container);
* }</pre>
api的說明: collector的finisher匯聚的實現過程。
* <p>However, the library is free to partition the input, perform the reduction
* on the partitions, and then use the combiner function to combine the partial
* results to achieve a parallel reduction. (Depending on the specific reduction
* operation, this may perform better or worse, depending on the relative cost
* of the accumulator and combiner functions.)
效能取決於accumulator and combiner的代價。 也就是說 並行流 並不一定比序列流效率高。
* <p>Collectors are designed to be <em>composed</em>; many of the methods
* in {@link Collectors} are functions that take a collector and produce
* a new collector. For example, given the following collector that computes
* the sum of the salaries of a stream of employees:
* <pre>{@code
* Collector<Employee, ?, Integer> summingSalaries
* = Collectors.summingInt(Employee::getSalary))
* }</pre>
蒐集器是可以組合的: take a collector and produce a new collector.
蒐集器的實現過程。 如 員工的工資的求和。
* If we wanted to create a collector to tabulate the sum of salaries by
* department, we could reuse the "sum of salaries" logic using
* {@link Collectors#groupingBy(Function, Collector)}:
* <pre>{@code
* Collector<Employee, ?, Map<Department, Integer>> summingSalariesByDept
* = Collectors.groupingBy(Employee::getDepartment, summingSalaries);
* }</pre>
如果我們想要新建一個蒐集器,我們可以複用之前的蒐集器。
實現過程。
* @see Stream#collect(Collector)
* @see Collectors
*
* @param <T> the type of input elements to the reduction operation
<T> 代表 流中的每一個元素的型別。
* @param <A> the mutable accumulation type of the reduction operation (often
* hidden as an implementation detail)
<A> 代表 reduction操作的可變容器的型別。表示中間操作生成的結果的型別(如ArrayList)。
* @param <R> the result type of the reduction operation
<R> 代表 結果型別
* @since 1.8
*/
public interface Collector<T, A, R>{
/**
* A function that creates and returns a new mutable result container.
* A就代表每一次返回結果的型別
* @return a function which returns a new, mutable result container
*/
Supplier<A> supplier(); // 提供一個結果容器
/**
* A function that folds a value into a mutable result container.
* A代表中間操作返回結果的型別。 T是下一個代操作的元素的型別。
* @return a function which folds a value into a mutable result container
*/
BiConsumer<A, T> accumulator(); //不斷的向結果容器中新增元素。
/**
* A function that accepts two partial results and merges them. The
* combiner function may fold state from one argument into the other and
* return that, or may return a new result container.
* A 中間操作返回結果的型別。
* @return a function which combines two partial results into a combined
* result
*/
BinaryOperator<A> combiner(); //在多執行緒中 合併 部分結果。
/**
和並行流緊密相關的
接收兩個結果,將兩個部分結果合併到一起。
combiner函式,有4個執行緒同時去執行,那麼就會有生成4個部分結果。
舉例說明:
1,2, 3, 4 四個部分結果。
1,2 -》 5
5,3 -》 6
6,4 -》 6
1,2合併返回5 屬於return a new result container.
6,4合併返回6,屬於The combiner function may fold state from one argument into the other and return that。
*/
/**
* Perform the final transformation from the intermediate accumulation type
* {@code A} to the final result type {@code R}.
*R 是最終返回結果的型別。
* <p>If the characteristic {@code IDENTITY_TRANSFORM} is
* set, this function may be presumed to be an identity transform with an
* unchecked cast from {@code A} to {@code R}.
*
* @return a function which transforms the intermediate result to the final
* result
*/
Function<A, R> finisher(); // 合併中間的值,給出返回值。
/**
* Returns a {@code Set} of {@code Collector.Characteristics} indicating
* the characteristics of this Collector. This set should be immutable.
*
* @return an immutable set of collector characteristics
*/
Set<Characteristics> characteristics(); //特徵的集合
/**
* Returns a new {@code Collector} described by the given {@code supplier},
* {@code accumulator}, and {@code combiner} functions. The resulting
* {@code Collector} has the {@code Collector.Characteristics.IDENTITY_FINISH}
* characteristic.
*
* @param supplier The supplier function for the new collector
* @param accumulator The accumulator function for the new collector
* @param combiner The combiner function for the new collector
* @param characteristics The collector characteristics for the new
* collector
* @param <T> The type of input elements for the new collector
* @param <R> The type of intermediate accumulation result, and final result,
* for the new collector
* @throws NullPointerException if any argument is null
* @return the new {@code Collector}