清原招聘網(wǎng)站建設(shè)成crm軟件
JDK8新特性
? Java 是第一大編程語(yǔ)言和開(kāi)發(fā)平臺(tái)。它有助于企業(yè)降低成本、縮短開(kāi)發(fā)周期、推動(dòng)創(chuàng)新以及改善應(yīng)用服務(wù)。如今全球有數(shù)百萬(wàn)開(kāi)發(fā)人員運(yùn)行著超過(guò) 51 億個(gè) Java 虛擬機(jī),Java 仍是企業(yè)和開(kāi)發(fā)人員的首選開(kāi)發(fā)平臺(tái)
課程內(nèi)容的介紹
- 了解Java發(fā)展史
- Lambda表達(dá)式
- 接口的增強(qiáng)
- 函數(shù)式接口
- 方法引用
- Stream API
- Optional
- 新時(shí)間日期API
- 其他新特性
一、Java發(fā)展歷史
1. Java的發(fā)展歷史
? Sun公司在1991年成立了一個(gè)稱為綠色計(jì)劃( Green Project )的項(xiàng)目,由James Gosling(高斯林)博土領(lǐng)導(dǎo),綠色計(jì)劃
的目的是開(kāi)發(fā)一種能夠在各種消費(fèi)性電子產(chǎn)品(機(jī)頂盒、冰箱、收音機(jī)等)上運(yùn)行的程序架構(gòu)。這個(gè)項(xiàng)目的產(chǎn)品就是
Java語(yǔ)言的前身: Oak(橡樹(shù))。Oak當(dāng)時(shí)在消費(fèi)品市場(chǎng)上并不算成功,但隨著1995年互聯(lián)網(wǎng)潮流的興起,Oak迅速找到
了最適合自己發(fā)展的市場(chǎng)定位。
- JDK Beta - 1995
- JDK 1.0 - 1996年1月 (真正第一個(gè)穩(wěn)定的版本JDK 1.0.2,被稱作 Java 1 )
- JDK 1.1 - 1997年2月
- J2SE 1.2 - 1998年12月
- J2ME(Java 2 Micro Edition,Java 2平臺(tái)的微型版),應(yīng)用于移動(dòng)、無(wú)線及有限資源的環(huán)境。
- J2SE(Java 2 Standard Edition,Java 2平臺(tái)的標(biāo)準(zhǔn)版),應(yīng)用于桌面環(huán)境。
- J2EE(Java 2 Enterprise Edition,Java 2平臺(tái)的企業(yè)版),應(yīng)用于基于Java的應(yīng)用服務(wù)器。
- J2SE 1.3 - 2000年5月
- J2SE 1.4 - 2002年2月
- J2SE 5.0 - 2004年9月
- Java SE 6 - 2006年12月
- Java SE 7 - 2011年7月
- Java SE 8(LTS) - 2014年3月
- Java SE 9 - 2017年9月
- Java SE 10(18.3) - 2018年3月
- **Java SE 11(18.9 LTS) **- 2018年9月
- Java SE 12(19.3) - 2019年3月
- Java SE 13(19.9) - 2019年9月
- Java SE 14(20.3) - 2020年3月
- Java SE 15(20.9) - 2020年9月
? 我們可以看到Java SE的主要版本大約每?jī)赡臧l(fā)布一次,直到Java SE 6到Java SE 7開(kāi)始花了五年時(shí)間,之后又花了三
年時(shí)間到達(dá)Java SE 8。
2.OpenJDK和OracleJDK
2.1 Open JDK來(lái)源
? Java 由 Sun 公司發(fā)明,Open JDK是Sun在2006年末把Java開(kāi)源而形成的項(xiàng)目。也就是說(shuō)Open JDK是Java SE平臺(tái)版
的開(kāi)源和免費(fèi)實(shí)現(xiàn),它由 SUN 和 Java 社區(qū)提供支持,2009年 Oracle 收購(gòu)了 Sun 公司,自此 Java 的維護(hù)方之一的
SUN 也變成了 Oracle。
2.2 Open JDK 和 Oracle JDK的關(guān)系
? 大多數(shù) JDK 都是在 Open JDK 的基礎(chǔ)上進(jìn)一步編寫(xiě)實(shí)現(xiàn)的,比如 IBM J9, Oracle JDK 和 Azul Zulu, Azul Zing。
Oracle JDK完全由 Oracle 公司開(kāi)發(fā),Oracle JDK是基于Open JDK源代碼的商業(yè)版本。此外,它包含閉源組件。
Oracle JDK根據(jù)二進(jìn)制代碼許可協(xié)議獲得許可,在沒(méi)有商業(yè)許可的情況下,在2019年1月之后發(fā)布的Oracle Java SE 8
的公開(kāi)更新將無(wú)法用于商業(yè)或生產(chǎn)用途。但是 Open JDK是完全開(kāi)源的,可以自由使用。
2.3 Open JDK 官網(wǎng)介紹
Open JDK 官網(wǎng): http://openjdk.java.net/ 。
JDK Enhancement Proposals(JDK增強(qiáng)建議)。通俗的講JEP就是JDK的新特性
小結(jié)
Oracle JDK是基于Open JDK源代碼的商業(yè)版本。我們要學(xué)習(xí)Java新技術(shù)可以去Open JDK 官網(wǎng)學(xué)習(xí)。
二、Lambda表達(dá)式
1. 需求分析
? 創(chuàng)建一個(gè)新的線程,指定線程要執(zhí)行的任務(wù)
public static void main(String[] args) {// 開(kāi)啟一個(gè)新的線程new Thread(new Runnable() {@Overridepublic void run() {System.out.println("新線程中執(zhí)行的代碼 : "+Thread.currentThread().getName());}}).start();System.out.println("主線程中的代碼:" + Thread.currentThread().getName());}
代碼分析:
- Thread類需要一個(gè)Runnable接口作為參數(shù),其中的抽象方法run方法是用來(lái)指定線程任務(wù)內(nèi)容的核心
- 為了指定run方法體,不得不需要Runnable的實(shí)現(xiàn)類
- 為了省去定義一個(gè)Runnable 的實(shí)現(xiàn)類,不得不使用匿名內(nèi)部類
- 必須覆蓋重寫(xiě)抽象的run方法,所有的方法名稱,方法參數(shù),方法返回值不得不都重寫(xiě)一遍,而且不能出錯(cuò),
- 而實(shí)際上,我們只在乎方法體中的代碼
2.Lambda表達(dá)式初體驗(yàn)
Lambda表達(dá)式是一個(gè)匿名函數(shù),可以理解為一段可以傳遞的代碼
new Thread(() -> { System.out.println("新線程Lambda表達(dá)式..." +Thread.currentThread().getName()); }).start();
Lambda表達(dá)式的優(yōu)點(diǎn):簡(jiǎn)化了匿名內(nèi)部類的使用,語(yǔ)法更加簡(jiǎn)單。
匿名內(nèi)部類語(yǔ)法冗余,體驗(yàn)了Lambda表達(dá)式后,發(fā)現(xiàn)Lambda表達(dá)式是簡(jiǎn)化匿名內(nèi)部類的一種方式。
3. Lambda的語(yǔ)法規(guī)則
Lambda省去了面向?qū)ο蟮臈l條框框,Lambda的標(biāo)準(zhǔn)格式由3個(gè)部分組成:
(參數(shù)類型 參數(shù)名稱) -> {代碼體;
}
格式說(shuō)明:
- (參數(shù)類型 參數(shù)名稱):參數(shù)列表
- {代碼體;} :方法體
- -> : 箭頭,分割參數(shù)列表和方法體
3.1 Lambda練習(xí)1
? 練習(xí)無(wú)參無(wú)返回值的Lambda
定義一個(gè)接口
public interface UserService {void show();
}
然后創(chuàng)建主方法使用
public class Demo03Lambda {public static void main(String[] args) {goShow(new UserService() {@Overridepublic void show() {System.out.println("show 方法執(zhí)行了...");}});System.out.println("----------");goShow(() -> { System.out.println("Lambda show 方法執(zhí)行了..."); });}public static void goShow(UserService userService){userService.show();}
}
輸出:
show 方法執(zhí)行了...
----------
Lambda show 方法執(zhí)行了...
3.2 Lambda練習(xí)2
? 完成一個(gè)有參且有返回值得Lambda表達(dá)式案例
創(chuàng)建一個(gè)Person對(duì)象
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {private String name;private Integer age;private Integer height;}
然后我們?cè)贚ist集合中保存多個(gè)Person對(duì)象,然后對(duì)這些對(duì)象做根據(jù)age排序操作
public static void main(String[] args) {List<Person> list = new ArrayList<>();list.add(new Person("周杰倫",33,175));list.add(new Person("劉德華",43,185));list.add(new Person("周星馳",38,177));list.add(new Person("郭富城",23,170));Collections.sort(list, new Comparator<Person>() {@Overridepublic int compare(Person o1, Person o2) {return o1.getAge()-o2.getAge();}});for (Person person : list) {System.out.println(person);}}
我們發(fā)現(xiàn)在sort方法的第二個(gè)參數(shù)是一個(gè)Comparator接口的匿名內(nèi)部類,且執(zhí)行的方法有參數(shù)和返回值,那么我們可以改寫(xiě)為L(zhǎng)ambda表達(dá)式
public static void main(String[] args) {List<Person> list = new ArrayList<>();list.add(new Person("周杰倫",33,175));list.add(new Person("劉德華",43,185));list.add(new Person("周星馳",38,177));list.add(new Person("郭富城",23,170));/*Collections.sort(list, new Comparator<Person>() {@Overridepublic int compare(Person o1, Person o2) {return o1.getAge()-o2.getAge();}});for (Person person : list) {System.out.println(person);}*/System.out.println("------");Collections.sort(list,(Person o1,Person o2) -> {return o1.getAge() - o2.getAge();});for (Person person : list) {System.out.println(person);}}
輸出結(jié)果
Person(name=郭富城, age=23, height=170)
Person(name=周杰倫, age=33, height=175)
Person(name=周星馳, age=38, height=177)
Person(name=劉德華, age=43, height=185)
4. @FunctionalInterface注解
/*** @FunctionalInterface* 這是一個(gè)標(biāo)志注解,被該注解修飾的接口只能聲明一個(gè)抽象方法*/
@FunctionalInterface
public interface UserService {void show();}
5. Lambda表達(dá)式的原理
匿名內(nèi)部類的本質(zhì)是在編譯時(shí)生成一個(gè)Class 文件。XXXXX$1.class
public class Demo01Lambda {public static void main(String[] args) {// 開(kāi)啟一個(gè)新的線程new Thread(new Runnable() {@Overridepublic void run() {System.out.println("新線程中執(zhí)行的代碼 : "+Thread.currentThread().getName());}}).start();System.out.println("主線程中的代碼:" + Thread.currentThread().getName());System.out.println("---------------");/*new Thread(() -> { System.out.println("新線程Lambda表達(dá)式..." +Thread.currentThread().getName()); }).start();*/}
}
還可以通過(guò)反編譯工具來(lái)查看生成的代碼 XJad 工具來(lái)查看
static class Demo01Lambda$1implements Runnable
{public void run(){System.out.println((new StringBuilder()).append("新線程中執(zhí)行的代碼 : " ).append(Thread.currentThread().getName()).toString());}Demo01Lambda$1(){}
}
那么Lambda表達(dá)式的原理是什么呢?我們也通過(guò)反編譯工具來(lái)查看
寫(xiě)的有Lambda表達(dá)式的class文件,我們通過(guò)XJad查看報(bào)錯(cuò)。這時(shí)我們可以通過(guò)JDK自帶的一個(gè)工具:javap 對(duì)字節(jié)碼進(jìn)行反匯編操作。
javap -c -p 文件名.class-c:表示對(duì)代碼進(jìn)行反匯編
-p:顯示所有的類和成員
反匯編的結(jié)果:
E:\workspace\OpenClassWorkSpace\JDK8Demo\target\classes\com\bobo\jdk\lambda>javap -c -p Demo03Lambda.class
Compiled from "Demo03Lambda.java"
public class com.bobo.jdk.lambda.Demo03Lambda {public com.bobo.jdk.lambda.Demo03Lambda();Code:0: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnpublic static void main(java.lang.String[]);Code:0: invokedynamic #2, 0 // InvokeDynamic #0:show:()Lcom/bobo/jdk/lambda/service/UserService;5: invokestatic #3 // Method goShow:(Lcom/bobo/jdk/lambda/service/UserService;)V8: returnpublic static void goShow(com.bobo.jdk.lambda.service.UserService);Code:0: aload_01: invokeinterface #4, 1 // InterfaceMethod com/bobo/jdk/lambda/service/UserService.show:()V6: returnprivate static void lambda$main$0();Code:0: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;3: ldc #6 // String Lambda show 方法執(zhí)行了...5: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: return
}
在這個(gè)反編譯的源碼中我們看到了一個(gè)靜態(tài)方法 lambda$main$0(),這個(gè)方法里面做了什么事情呢?我們通過(guò)debug的方式來(lái)查看下:
上面的效果可以理解為如下:
public class Demo03Lambda {public static void main(String[] args) {....}private static void lambda$main$0();System.out.println("Lambda show 方法執(zhí)行了...");}
}
為了更加直觀的理解這個(gè)內(nèi)容,我們可以在運(yùn)行的時(shí)候添加 -Djdk.internal.lambda.dumpProxyClasses, 加上這個(gè)參數(shù)會(huì)將內(nèi)部class碼輸出到一個(gè)文件中
java -Djdk.internal.lambda.dumpProxyClasses 要運(yùn)行的包名.類名
命令執(zhí)行
E:\workspace\OpenClassWorkSpace\JDK8Demo\target\classes>java -Djdk.internal.lambda.dumpProxyClasses com.bobo.jdk.lambda.Demo03Lambda
Lambda show 方法執(zhí)行了...
反編譯后的內(nèi)容:
可以看到這個(gè)匿名的內(nèi)部類實(shí)現(xiàn)了UserService接口,并重寫(xiě)了show()方法。在show方法中調(diào)用了Demo03Lambda.lambda$main$0(),也就是調(diào)用了Lambda中的內(nèi)容。
public class Demo03Lambda {public static void main(String[] args) {goShow(new UserService() {@Overridepublic void show() {Demo03Lambda.lambda$main$0();}});System.out.println("----------");}public static void goShow(UserService userService){userService.show();}private static void lambda$main$0();System.out.println("Lambda show 方法執(zhí)行了...");}
}
小結(jié):
匿名內(nèi)部類在編譯的時(shí)候會(huì)產(chǎn)生一個(gè)class文件。
Lambda表達(dá)式在程序運(yùn)行的時(shí)候會(huì)形成一個(gè)類。
- 在類中新增了一個(gè)方法,這個(gè)方法的方法體就是Lambda表達(dá)式中的代碼
- 還會(huì)形成一個(gè)匿名內(nèi)部類,實(shí)現(xiàn)接口,重寫(xiě)抽象方法
- 在接口中重寫(xiě)方法會(huì)調(diào)用新生成的方法
6.Lambda表達(dá)式的省略寫(xiě)法
在lambda表達(dá)式的標(biāo)準(zhǔn)寫(xiě)法基礎(chǔ)上,可以使用省略寫(xiě)法的規(guī)則為:
- 小括號(hào)內(nèi)的參數(shù)類型可以省略
- 如果小括號(hào)內(nèi)有且僅有一個(gè)參數(shù),則小括號(hào)可以省略
- 如果大括號(hào)內(nèi)有且僅有一個(gè)語(yǔ)句,可以同時(shí)省略大括號(hào),return 關(guān)鍵字及語(yǔ)句分號(hào)。
public class Demo05Lambda {public static void main(String[] args) {goStudent((String name,Integer age)->{return name+age+" 6666 ...";});// 省略寫(xiě)法goStudent((name,age)-> name+age+" 6666 ...");System.out.println("------");goOrder((String name)->{System.out.println("--->" + name);return 666;});// 省略寫(xiě)法goOrder(name -> {System.out.println("--->" + name);return 666;});goOrder(name -> 666);}public static void goStudent(StudentService studentService){studentService.show("張三",22);}public static void goOrder(OrderService orderService){orderService.show("李四");}}
7.Lambda表達(dá)式的使用前提
Lambda表達(dá)式的語(yǔ)法是非常簡(jiǎn)潔的,但是Lambda表達(dá)式不是隨便使用的,使用時(shí)有幾個(gè)條件要特別注意
- 方法的參數(shù)或局部變量類型必須為接口才能使用Lambda
- 接口中有且僅有一個(gè)抽象方法(@FunctionalInterface)
8.Lambda和匿名內(nèi)部類的對(duì)比
Lambda和匿名內(nèi)部類的對(duì)比
-
所需類型不一樣
- 匿名內(nèi)部類的類型可以是 類,抽象類,接口
- Lambda表達(dá)式需要的類型必須是接口
-
抽象方法的數(shù)量不一樣
- 匿名內(nèi)部類所需的接口中的抽象方法的數(shù)量是隨意的
- Lambda表達(dá)式所需的接口中只能有一個(gè)抽象方法
-
實(shí)現(xiàn)原理不一樣
- 匿名內(nèi)部類是在編譯后形成一個(gè)class
- Lambda表達(dá)式是在程序運(yùn)行的時(shí)候動(dòng)態(tài)生成class
三、接口中新增的方法
1. JDK8中接口的新增
在JDK8中針對(duì)接口有做增強(qiáng),在JDK8之前
interface 接口名{靜態(tài)常量;抽象方法;
}
JDK8之后對(duì)接口做了增加,接口中可以有默認(rèn)方法和靜態(tài)方法
interface 接口名{靜態(tài)常量;抽象方法;默認(rèn)方法;靜態(tài)方法;
}
2.默認(rèn)方法
2.1 為什么要增加默認(rèn)方法
在JDK8以前接口中只能有抽象方法和靜態(tài)常量,會(huì)存在以下的問(wèn)題:
如果接口中新增抽象方法,那么實(shí)現(xiàn)類都必須要抽象這個(gè)抽象方法,非常不利于接口的擴(kuò)展的
package com.bobo.jdk.inter;public class Demo01Interface {public static void main(String[] args) {A a = new B();A c = new C();}
}interface A{void test1();// 接口中新增抽象方法,所有實(shí)現(xiàn)類都需要重寫(xiě)這個(gè)方法,不利于接口的擴(kuò)展void test2();
}class B implements A{@Overridepublic void test1() {}@Overridepublic void test2() {}
}class C implements A{@Overridepublic void test1() {}@Overridepublic void test2() {}
}
2.2 接口默認(rèn)方法的格式
? 接口中默認(rèn)方法的語(yǔ)法格式是
interface 接口名{修飾符 default 返回值類型 方法名{方法體;}
}
package com.bobo.jdk.inter;public class Demo01Interface {public static void main(String[] args) {A a = new B();a.test3();A c = new C();c.test3();}
}interface A{void test1();// 接口中新增抽象方法,所有實(shí)現(xiàn)類都需要重寫(xiě)這個(gè)方法,不利于接口的擴(kuò)展void test2();/*** 接口中定義的默認(rèn)方法* @return*/public default String test3(){System.out.println("接口中的默認(rèn)方法執(zhí)行了...");return "hello";}
}class B implements A{@Overridepublic void test1() {}@Overridepublic void test2() {}@Overridepublic String test3() {System.out.println("B 實(shí)現(xiàn)類中重寫(xiě)了默認(rèn)方法...");return "ok ...";}
}class C implements A{@Overridepublic void test1() {}@Overridepublic void test2() {}
}
2.3 接口中默認(rèn)方法的使用
接口中的默認(rèn)方法有兩種使用方式
- 實(shí)現(xiàn)類直接調(diào)用接口的默認(rèn)方法
- 實(shí)現(xiàn)類重寫(xiě)接口的默認(rèn)方法
3. 靜態(tài)方法
JDK8中為接口新增了靜態(tài)方法,作用也是為了接口的擴(kuò)展
3.1 語(yǔ)法規(guī)則
interface 接口名{修飾符 static 返回值類型 方法名{方法體;}
}
package com.bobo.jdk.inter;public class Demo01Interface {public static void main(String[] args) {A a = new B();a.test3();A c = new C();c.test3();A.test4();}
}interface A{void test1();// 接口中新增抽象方法,所有實(shí)現(xiàn)類都需要重寫(xiě)這個(gè)方法,不利于接口的擴(kuò)展void test2();/*** 接口中定義的默認(rèn)方法* @return*/public default String test3(){System.out.println("接口中的默認(rèn)方法執(zhí)行了...");return "hello";}/*** 接口中的靜態(tài)方法* @return*/public static String test4(){System.out.println("接口中的靜態(tài)方法....");return "Hello";}
}class B implements A{@Overridepublic void test1() {}@Overridepublic void test2() {}@Overridepublic String test3() {System.out.println("B 實(shí)現(xiàn)類中重寫(xiě)了默認(rèn)方法...");return "ok ...";}}class C implements A{@Overridepublic void test1() {}@Overridepublic void test2() {}
}
3.2 靜態(tài)方法的使用
接口中的靜態(tài)方法在實(shí)現(xiàn)類中是不能被重寫(xiě)的,調(diào)用的話只能通過(guò)接口類型來(lái)實(shí)現(xiàn): 接口名.靜態(tài)方法名();
4. 兩者的區(qū)別介紹
- 默認(rèn)方法通過(guò)實(shí)例調(diào)用,靜態(tài)方法通過(guò)接口名調(diào)用
- 默認(rèn)方法可以被繼承,實(shí)現(xiàn)類可以直接調(diào)用接口默認(rèn)方法,也可以重寫(xiě)接口默認(rèn)方法
- 靜態(tài)方法不能被繼承,實(shí)現(xiàn)類不能重寫(xiě)接口的靜態(tài)方法,只能使用接口名調(diào)用
四、函數(shù)式接口
1. 函數(shù)式接口的由來(lái)
? 我們知道使用Lambda表達(dá)式的前提是需要有函數(shù)式接口,而Lambda表達(dá)式使用時(shí)不關(guān)心接口名,抽象方法名。只關(guān)心抽象方法的參數(shù)列表和返回值類型。因此為了讓我們使用Lambda表達(dá)式更加的方法,在JDK中提供了大量常用的函數(shù)式接口
package com.bobo.jdk.fun;public class Demo01Fun {public static void main(String[] args) {fun1((arr)->{int sum = 0 ;for (int i : arr) {sum += i;}return sum;});}public static void fun1(Operator operator){int[] arr = {1,2,3,4};int sum = operator.getSum(arr);System.out.println("sum = " + sum);}
}/*** 函數(shù)式接口*/
@FunctionalInterface
interface Operator{int getSum(int[] arr);
}
2. 函數(shù)式接口介紹
在JDK中幫我們提供的有函數(shù)式接口,主要是在 java.util.function 包中。
2.1 Supplier
? 無(wú)參有返回值的接口,對(duì)于的Lambda表達(dá)式需要提供一個(gè)返回?cái)?shù)據(jù)的類型。
@FunctionalInterface
public interface Supplier<T> {/*** Gets a result.** @return a result*/T get();
}
使用:
/*** Supplier 函數(shù)式接口的使用*/
public class SupplierTest {public static void main(String[] args) {fun1(()->{int arr[] = {22,33,55,66,44,99,10};// 計(jì)算出數(shù)組中的最大值Arrays.sort(arr);return arr[arr.length-1];});}private static void fun1(Supplier<Integer> supplier){// get() 是一個(gè)無(wú)參的有返回值的 抽象方法Integer max = supplier.get();System.out.println("max = " + max);}
}
2.2 Consumer
? 有參無(wú)返回值得接口,前面介紹的Supplier接口是用來(lái)生產(chǎn)數(shù)據(jù)的,而Consumer接口是用來(lái)消費(fèi)數(shù)據(jù)的,使用的時(shí)候需要指定一個(gè)泛型來(lái)定義參數(shù)類型
@FunctionalInterface
public interface Consumer<T> {/*** Performs this operation on the given argument.** @param t the input argument*/void accept(T t);
}
使用:將輸入的數(shù)據(jù)統(tǒng)一轉(zhuǎn)換為小寫(xiě)輸出
public class ConsumerTest {public static void main(String[] args) {test(msg -> {System.out.println(msg + "-> 轉(zhuǎn)換為小寫(xiě):" + msg.toLowerCase());});}public static void test(Consumer<String> consumer){consumer.accept("Hello World");}
}
默認(rèn)方法:andThen
? 如果一個(gè)方法的參數(shù)和返回值全部是Consumer類型,那么就可以實(shí)現(xiàn)效果,消費(fèi)一個(gè)數(shù)據(jù)的時(shí)候,首先做一個(gè)操作,然后再做一個(gè)操作,實(shí)現(xiàn)組合,而這個(gè)方法就是Consumer接口中的default方法 andThen方法
default Consumer<T> andThen(Consumer<? super T> after) {Objects.requireNonNull(after);return (T t) -> { accept(t); after.accept(t); };}
具體的操作
public class ConsumerAndThenTest {public static void main(String[] args) {test2(msg1->{System.out.println(msg1 + "-> 轉(zhuǎn)換為小寫(xiě):" + msg1.toLowerCase());},msg2->{System.out.println(msg2 + "-> 轉(zhuǎn)換為大寫(xiě):" + msg2.toUpperCase());});}public static void test2(Consumer<String> c1,Consumer<String> c2){String str = "Hello World";//c1.accept(str); // 轉(zhuǎn)小寫(xiě)//c2.accept(str); // 轉(zhuǎn)大寫(xiě)//c1.andThen(c2).accept(str);c2.andThen(c1).accept(str);}
}
2.3 Function
? 有參有返回值的接口,Function接口是根據(jù)一個(gè)類型的數(shù)據(jù)得到另一個(gè)類型的數(shù)據(jù),前者稱為前置條件,后者稱為后置條件。有參數(shù)有返回值。
@FunctionalInterface
public interface Function<T, R> {/*** Applies this function to the given argument.** @param t the function argument* @return the function result*/R apply(T t);
}
使用:傳遞進(jìn)入一個(gè)字符串返回一個(gè)數(shù)字
public class FunctionTest {public static void main(String[] args) {test(msg ->{return Integer.parseInt(msg);});}public static void test(Function<String,Integer> function){Integer apply = function.apply("666");System.out.println("apply = " + apply);}
}
默認(rèn)方法:andThen,也是用來(lái)進(jìn)行組合操作,
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {Objects.requireNonNull(after);return (T t) -> after.apply(apply(t));}
public class FunctionAndThenTest {public static void main(String[] args) {test(msg ->{return Integer.parseInt(msg);},msg2->{return msg2 * 10;});}public static void test(Function<String,Integer> f1,Function<Integer,Integer> f2){/*Integer i1 = f1.apply("666");Integer i2 = f2.apply(i1);*/Integer i2 = f1.andThen(f2).apply("666");System.out.println("i2:" + i2);}
}
默認(rèn)的compose方法的作用順序和andThen方法剛好相反
而靜態(tài)方法identity則是,輸入什么參數(shù)就返回什么參數(shù)
2.4 Predicate
有參且返回值為Boolean的接口
@FunctionalInterface
public interface Predicate<T> {/*** Evaluates this predicate on the given argument.** @param t the input argument* @return {@code true} if the input argument matches the predicate,* otherwise {@code false}*/boolean test(T t);
}
使用:
public class PredicateTest {public static void main(String[] args) {test(msg -> {return msg.length() > 3;},"HelloWorld");}private static void test(Predicate<String> predicate,String msg){boolean b = predicate.test(msg);System.out.println("b:" + b);}
}
在Predicate中的默認(rèn)方法提供了邏輯關(guān)系操作 and or negate isEquals方法
package com.bobo.jdk.fun;import java.util.function.Predicate;public class PredicateDefaultTest {public static void main(String[] args) {test(msg1 -> {return msg1.contains("H");},msg2 -> {return msg2.contains("W");});}private static void test(Predicate<String> p1,Predicate<String> p2){/*boolean b1 = predicate.test(msg);boolean b2 = predicate.test("Hello");*/// b1 包含H b2 包含W// p1 包含H 同時(shí) p2 包含Wboolean bb1 = p1.and(p2).test("Hello");// p1 包含H 或者 p2 包含Wboolean bb2 = p1.or(p2).test("Hello");// p1 不包含Hboolean bb3 = p1.negate().test("Hello");System.out.println(bb1); // FALSESystem.out.println(bb2); // TRUESystem.out.println(bb3); // FALSE}
}
五、方法引用
1. 為什么要用方法引用
1.1 lambda表達(dá)式冗余
在使用Lambda表達(dá)式的時(shí)候,也會(huì)出現(xiàn)代碼冗余的情況,比如:用Lambda表達(dá)式求一個(gè)數(shù)組的和
package com.bobo.jdk.funref;import java.util.function.Consumer;public class FunctionRefTest01 {public static void main(String[] args) {printMax(a->{// Lambda表達(dá)式中的代碼和 getTotal中的代碼冗余了int sum = 0;for (int i : a) {sum += i;}System.out.println("數(shù)組之和:" + sum);});}/*** 求數(shù)組中的所有元素的和* @param a*/public void getTotal(int a[]){int sum = 0;for (int i : a) {sum += i;}System.out.println("數(shù)組之和:" + sum);}private static void printMax(Consumer<int[]> consumer){int[] a= {10,20,30,40,50,60};consumer.accept(a);}
}
1.2 解決方案
? 因?yàn)樵贚ambda表達(dá)式中要執(zhí)行的代碼和我們另一個(gè)方法中的代碼是一樣的,這時(shí)就沒(méi)有必要重寫(xiě)一份邏輯了,這時(shí)我們就可以“引用”重復(fù)代碼
package com.bobo.jdk.funref;import java.util.function.Consumer;public class FunctionRefTest02 {public static void main(String[] args) {// :: 方法引用 也是JDK8中的新的語(yǔ)法printMax(FunctionRefTest02::getTotal);}/*** 求數(shù)組中的所有元素的和* @param a*/public static void getTotal(int a[]){int sum = 0;for (int i : a) {sum += i;}System.out.println("數(shù)組之和:" + sum);}private static void printMax(Consumer<int[]> consumer){int[] a= {10,20,30,40,50,60};consumer.accept(a);}
}
:: 方法引用 也是JDK8中的新的語(yǔ)法
2. 方法引用的格式
符號(hào)表示:::
符號(hào)說(shuō)明:雙冒號(hào)為方法引用運(yùn)算符,而它所在的表達(dá)式被稱為方法引用
應(yīng)用場(chǎng)景:如果Lambda表達(dá)式所要實(shí)現(xiàn)的方案,已經(jīng)有其他方法存在相同的方案,那么則可以使用方法引用。
常見(jiàn)的引用方式:
方法引用在JDK8中使用是相當(dāng)靈活的,有以下幾種形式:
-
instanceName::methodName 對(duì)象::方法名
-
ClassName::staticMethodName 類名::靜態(tài)方法
-
ClassName::methodName 類名::普通方法
-
ClassName::new 類名::new 調(diào)用的構(gòu)造器
-
TypeName[]::new String[]::new 調(diào)用數(shù)組的構(gòu)造器
2.1 對(duì)象名::方法名
這是最常見(jiàn)的一種用法。如果一個(gè)類中的已經(jīng)存在了一個(gè)成員方法,則可以通過(guò)對(duì)象名引用成員方法
public static void main(String[] args) {Date now = new Date();Supplier<Long> supplier = ()->{return now.getTime();};System.out.println(supplier.get());// 然后我們通過(guò) 方法引用 的方式來(lái)處理Supplier<Long> supplier1 = now::getTime;System.out.println(supplier1.get());}
方法引用的注意事項(xiàng):
- 被引用的方法,參數(shù)要和接口中的抽象方法的參數(shù)一樣
- 當(dāng)接口抽象方法有返回值時(shí),被引用的方法也必須有返回值
2.2 類名::靜態(tài)方法名
也是比較常用的方式:
public class FunctionRefTest04 {public static void main(String[] args) {Supplier<Long> supplier1 = ()->{return System.currentTimeMillis();};System.out.println(supplier1.get());// 通過(guò) 方法引用 來(lái)實(shí)現(xiàn)Supplier<Long> supplier2 = System::currentTimeMillis;System.out.println(supplier2.get());}
}
2.3 類名::引用實(shí)例方法
? Java面向?qū)ο笾?#xff0c;類名只能調(diào)用靜態(tài)方法,類名引用實(shí)例方法是用前提的,實(shí)際上是拿第一個(gè)參數(shù)作為方法的調(diào)用者
package com.bobo.jdk.funref;import java.util.Date;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;public class FunctionRefTest05 {public static void main(String[] args) {Function<String,Integer> function = (s)->{return s.length();};System.out.println(function.apply("hello"));// 通過(guò)方法引用來(lái)實(shí)現(xiàn)Function<String,Integer> function1 = String::length;System.out.println(function1.apply("hahahaha"));BiFunction<String,Integer,String> function2 = String::substring;String msg = function2.apply("HelloWorld", 3);System.out.println(msg);}
}
2.4 類名::構(gòu)造器
? 由于構(gòu)造器的名稱和類名完全一致,所以構(gòu)造器引用使用::new
的格式使用,
public class FunctionRefTest06 {public static void main(String[] args) {Supplier<Person> sup = ()->{return new Person();};System.out.println(sup.get());// 然后通過(guò) 方法引用來(lái)實(shí)現(xiàn)Supplier<Person> sup1 = Person::new;System.out.println(sup1.get());BiFunction<String,Integer,Person> function = Person::new;System.out.println(function.apply("張三",22));}
}
2.5 數(shù)組::構(gòu)造器
? 數(shù)組是怎么構(gòu)造出來(lái)的呢?
public static void main(String[] args) {Function<Integer,String[]> fun1 = (len)->{return new String[len];};String[] a1 = fun1.apply(3);System.out.println("數(shù)組的長(zhǎng)度是:" + a1.length);// 方法引用 的方式來(lái)調(diào)用數(shù)組的構(gòu)造器Function<Integer,String[]> fun2 = String[]::new;String[] a2 = fun2.apply(5);System.out.println("數(shù)組的長(zhǎng)度是:" + a2.length);}
小結(jié):方法引用是對(duì)Lambda表達(dá)式符合特定情況下的一種縮寫(xiě)方式,它使得我們的Lambda表達(dá)式更加的精簡(jiǎn),也可以理解為lambda表達(dá)式的縮寫(xiě)形式,不過(guò)要注意的是方法引用只能引用已經(jīng)存在的方法。
六、Stream API
1.集合處理數(shù)據(jù)的弊端
? 當(dāng)我們?cè)谛枰獙?duì)集合中的元素進(jìn)行操作的時(shí)候,除了必需的添加,刪除,獲取外,最典型的操作就是集合遍歷,
package com.bobo.jdk.stream;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;public class StreamTest01 {public static void main(String[] args) {// 定義一個(gè)List集合List<String> list = Arrays.asList("張三","張三豐","成龍","周星馳");// 1.獲取所有 姓張的信息List<String> list1 = new ArrayList<>();for (String s : list) {if(s.startsWith("張")){list1.add(s);}}// 2.獲取名稱長(zhǎng)度為3的用戶List<String> list2 = new ArrayList<>();for (String s : list1) {if(s.length() == 3){list2.add(s);}}// 3. 輸出所有的用戶信息for (String s : list2) {System.out.println(s);}}
}
上面的代碼針對(duì)與我們不同的需求總是一次次的循環(huán)循環(huán)循環(huán).這時(shí)我們希望有更加高效的處理方式,這時(shí)我們就可以通過(guò)JDK8中提供的Stream API來(lái)解決這個(gè)問(wèn)題了。
Stream更加優(yōu)雅的解決方案:
package com.bobo.jdk.stream;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;public class StreamTest02 {public static void main(String[] args) {// 定義一個(gè)List集合List<String> list = Arrays.asList("張三","張三豐","成龍","周星馳");// 1.獲取所有 姓張的信息// 2.獲取名稱長(zhǎng)度為3的用戶// 3. 輸出所有的用戶信息list.stream().filter(s->s.startsWith("張")).filter(s->s.length() == 3).forEach(s->{System.out.println(s);});System.out.println("----------");list.stream().filter(s->s.startsWith("張")).filter(s->s.length() == 3).forEach(System.out::println);}
}
上面的SteamAPI代碼的含義:獲取流,過(guò)濾張,過(guò)濾長(zhǎng)度,逐一打印。代碼相比于上面的案例更加的簡(jiǎn)潔直觀
2. Steam流式思想概述
注意:Stream和IO流(InputStream/OutputStream)沒(méi)有任何關(guān)系,請(qǐng)暫時(shí)忘記對(duì)傳統(tǒng)IO流的固有印象!
Stream流式思想類似于工廠車間的“生產(chǎn)流水線”,Stream流不是一種數(shù)據(jù)結(jié)構(gòu),不保存數(shù)據(jù),而是對(duì)數(shù)據(jù)進(jìn)行加工
處理。Stream可以看作是流水線上的一個(gè)工序。在流水線上,通過(guò)多個(gè)工序讓一個(gè)原材料加工成一個(gè)商品。
Stream API能讓我們快速完成許多復(fù)雜的操作,如篩選、切片、映射、查找、去除重復(fù),統(tǒng)計(jì),匹配和歸約。
3. Stream流的獲取方式
3.1 根據(jù)Collection獲取
? 首先,java.util.Collection 接口中加入了default方法 stream,也就是說(shuō)Collection接口下的所有的實(shí)現(xiàn)都可以通過(guò)steam方法來(lái)獲取Stream流。
public static void main(String[] args) {List<String> list = new ArrayList<>();list.stream();Set<String> set = new HashSet<>();set.stream();Vector vector = new Vector();vector.stream();}
? 但是Map接口別沒(méi)有實(shí)現(xiàn)Collection接口,那這時(shí)怎么辦呢?這時(shí)我們可以根據(jù)Map獲取對(duì)應(yīng)的key value的集合。
public static void main(String[] args) {Map<String,Object> map = new HashMap<>();Stream<String> stream = map.keySet().stream(); // keyStream<Object> stream1 = map.values().stream(); // valueStream<Map.Entry<String, Object>> stream2 = map.entrySet().stream(); // entry}
3.1 通過(guò)Stream的of方法
? 在實(shí)際開(kāi)發(fā)中我們不可避免的還是會(huì)操作到數(shù)組中的數(shù)據(jù),由于數(shù)組對(duì)象不可能添加默認(rèn)方法,所有Stream接口中提供了靜態(tài)方法of
public class StreamTest05 {public static void main(String[] args) {Stream<String> a1 = Stream.of("a1", "a2", "a3");String[] arr1 = {"aa","bb","cc"};Stream<String> arr11 = Stream.of(arr1);Integer[] arr2 = {1,2,3,4};Stream<Integer> arr21 = Stream.of(arr2);arr21.forEach(System.out::println);// 注意:基本數(shù)據(jù)類型的數(shù)組是不行的int[] arr3 = {1,2,3,4};Stream.of(arr3).forEach(System.out::println);}
}
4.Stream常用方法介紹
Stream常用方法
Stream流模型的操作很豐富,這里介紹一些常用的API。這些方法可以被分成兩種:
方法名 | 方法作用 | 返回值類型 | 方法種類 |
---|---|---|---|
count | 統(tǒng)計(jì)個(gè)數(shù) | long | 終結(jié) |
forEach | 逐一處理 | void | 終結(jié) |
filter | 過(guò)濾 | Stream | 函數(shù)拼接 |
limit | 取用前幾個(gè) | Stream | 函數(shù)拼接 |
skip | 跳過(guò)前幾個(gè) | Stream | 函數(shù)拼接 |
map | 映射 | Stream | 函數(shù)拼接 |
concat | 組合 | Stream | 函數(shù)拼接 |
終結(jié)方法:返回值類型不再是 Stream 類型的方法,不再支持鏈?zhǔn)秸{(diào)用。本小節(jié)中,終結(jié)方法包括 count 和
forEach 方法。
非終結(jié)方法:返回值類型仍然是 Stream 類型的方法,支持鏈?zhǔn)秸{(diào)用。(除了終結(jié)方法外,其余方法均為非終結(jié)
方法。)
Stream注意事項(xiàng)(重要)
-
Stream只能操作一次
-
Stream方法返回的是新的流
-
Stream不調(diào)用終結(jié)方法,中間的操作不會(huì)執(zhí)行
4.1 forEach
forEach用來(lái)遍歷流中的數(shù)據(jù)的
void forEach(Consumer<? super T> action);
該方法接受一個(gè)Consumer接口,會(huì)將每一個(gè)流元素交給函數(shù)處理
public static void main(String[] args) {Stream.of("a1", "a2", "a3").forEach(System.out::println);;}
4.2 count
Stream流中的count方法用來(lái)統(tǒng)計(jì)其中的元素個(gè)數(shù)的
long count();
該方法返回一個(gè)long值,代表元素的個(gè)數(shù)。
public static void main(String[] args) {long count = Stream.of("a1", "a2", "a3").count();System.out.println(count);}
4.3 filter
filter方法的作用是用來(lái)過(guò)濾數(shù)據(jù)的。返回符合條件的數(shù)據(jù)
可以通過(guò)filter方法將一個(gè)流轉(zhuǎn)換成另一個(gè)子集流
Stream<T> filter(Predicate<? super T> predicate);
該接口接收一個(gè)Predicate函數(shù)式接口參數(shù)作為篩選條件
public static void main(String[] args) {Stream.of("a1", "a2", "a3","bb","cc","aa","dd").filter((s)->s.contains("a")).forEach(System.out::println);}
輸出:
a1
a2
a3
aa
4.4 limit
limit方法可以對(duì)流進(jìn)行截取處理,支取前n個(gè)數(shù)據(jù),
Stream<T> limit(long maxSize);
參數(shù)是一個(gè)long類型的數(shù)值,如果集合當(dāng)前長(zhǎng)度大于參數(shù)就進(jìn)行截取,否則不操作:
public static void main(String[] args) {Stream.of("a1", "a2", "a3","bb","cc","aa","dd").limit(3).forEach(System.out::println);}
輸出:
a1
a2
a3
4.5 skip
如果希望跳過(guò)前面幾個(gè)元素,可以使用skip方法獲取一個(gè)截取之后的新流:
Stream<T> skip(long n);
操作:
public static void main(String[] args) {Stream.of("a1", "a2", "a3","bb","cc","aa","dd").skip(3).forEach(System.out::println);}
輸出:
bb
cc
aa
dd
4.6 map
如果我們需要將流中的元素映射到另一個(gè)流中,可以使用map方法:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
該接口需要一個(gè)Function函數(shù)式接口參數(shù),可以將當(dāng)前流中的T類型數(shù)據(jù)轉(zhuǎn)換為另一種R類型的數(shù)據(jù)
public static void main(String[] args) {Stream.of("1", "2", "3","4","5","6","7")//.map(msg->Integer.parseInt(msg)).map(Integer::parseInt).forEach(System.out::println);}
4.7 sorted
如果需要將數(shù)據(jù)排序,可以使用sorted方法:
Stream<T> sorted();
在使用的時(shí)候可以根據(jù)自然規(guī)則排序,也可以通過(guò)比較強(qiáng)來(lái)指定對(duì)應(yīng)的排序規(guī)則
public static void main(String[] args) {Stream.of("1", "3", "2","4","0","9","7")//.map(msg->Integer.parseInt(msg)).map(Integer::parseInt)//.sorted() // 根據(jù)數(shù)據(jù)的自然順序排序.sorted((o1,o2)->o2-o1) // 根據(jù)比較強(qiáng)指定排序規(guī)則.forEach(System.out::println);}
4.8 distinct
如果要去掉重復(fù)數(shù)據(jù),可以使用distinct方法:
Stream<T> distinct();
使用:
public static void main(String[] args) {Stream.of("1", "3", "3","4","0","1","7")//.map(msg->Integer.parseInt(msg)).map(Integer::parseInt)//.sorted() // 根據(jù)數(shù)據(jù)的自然順序排序.sorted((o1,o2)->o2-o1) // 根據(jù)比較強(qiáng)指定排序規(guī)則.distinct() // 去掉重復(fù)的記錄.forEach(System.out::println);System.out.println("--------");Stream.of(new Person("張三",18),new Person("李四",22),new Person("張三",18)).distinct().forEach(System.out::println);}
? Stream流中的distinct方法對(duì)于基本數(shù)據(jù)類型是可以直接出重的,但是對(duì)于自定義類型,我們是需要重寫(xiě)hashCode和equals方法來(lái)移除重復(fù)元素。
4.9 match
如果需要判斷數(shù)據(jù)是否匹配指定的條件,可以使用match相關(guān)的方法
boolean anyMatch(Predicate<? super T> predicate); // 元素是否有任意一個(gè)滿足條件
boolean allMatch(Predicate<? super T> predicate); // 元素是否都滿足條件
boolean noneMatch(Predicate<? super T> predicate); // 元素是否都不滿足條件
使用
public static void main(String[] args) {boolean b = Stream.of("1", "3", "3", "4", "5", "1", "7").map(Integer::parseInt)//.allMatch(s -> s > 0)//.anyMatch(s -> s >4).noneMatch(s -> s > 4);System.out.println(b);}
注意match是一個(gè)終結(jié)方法
4.10 find
如果我們需要找到某些數(shù)據(jù),可以使用find方法來(lái)實(shí)現(xiàn)
Optional<T> findFirst();Optional<T> findAny();
使用:
public static void main(String[] args) {Optional<String> first = Stream.of("1", "3", "3", "4", "5", "1", "7").findFirst();System.out.println(first.get());Optional<String> any = Stream.of("1", "3", "3", "4", "5", "1", "7").findAny();System.out.println(any.get());}
4.11 max和min
如果我們想要獲取最大值和最小值,那么可以使用max和min方法
Optional<T> min(Comparator<? super T> comparator);
Optional<T> max(Comparator<? super T> comparator);
使用
public static void main(String[] args) {Optional<Integer> max = Stream.of("1", "3", "3", "4", "5", "1", "7").map(Integer::parseInt).max((o1,o2)->o1-o2);System.out.println(max.get());Optional<Integer> min = Stream.of("1", "3", "3", "4", "5", "1", "7").map(Integer::parseInt).min((o1,o2)->o1-o2);System.out.println(min.get());}
4.12 reduce方法
如果需要將所有數(shù)據(jù)歸納得到一個(gè)數(shù)據(jù),可以使用reduce方法
T reduce(T identity, BinaryOperator<T> accumulator);
使用:
public static void main(String[] args) {Integer sum = Stream.of(4, 5, 3, 9)// identity默認(rèn)值// 第一次的時(shí)候會(huì)將默認(rèn)值賦值給x// 之后每次會(huì)將 上一次的操作結(jié)果賦值給x y就是每次從數(shù)據(jù)中獲取的元素.reduce(0, (x, y) -> {System.out.println("x="+x+",y="+y);return x + y;});System.out.println(sum);// 獲取 最大值Integer max = Stream.of(4, 5, 3, 9).reduce(0, (x, y) -> {return x > y ? x : y;});System.out.println(max);}
4.13 map和reduce的組合
? 在實(shí)際開(kāi)發(fā)中我們經(jīng)常會(huì)將map和reduce一塊來(lái)使用
public static void main(String[] args) {// 1.求出所有年齡的總和Integer sumAge = Stream.of(new Person("張三", 18), new Person("李四", 22), new Person("張三", 13), new Person("王五", 15), new Person("張三", 19)).map(Person::getAge) // 實(shí)現(xiàn)數(shù)據(jù)類型的轉(zhuǎn)換.reduce(0, Integer::sum);System.out.println(sumAge);// 2.求出所有年齡中的最大值Integer maxAge = Stream.of(new Person("張三", 18), new Person("李四", 22), new Person("張三", 13), new Person("王五", 15), new Person("張三", 19)).map(Person::getAge) // 實(shí)現(xiàn)數(shù)據(jù)類型的轉(zhuǎn)換,符合reduce對(duì)數(shù)據(jù)的要求.reduce(0, Math::max); // reduce實(shí)現(xiàn)數(shù)據(jù)的處理System.out.println(maxAge);// 3.統(tǒng)計(jì) 字符 a 出現(xiàn)的次數(shù)Integer count = Stream.of("a", "b", "c", "d", "a", "c", "a").map(ch -> "a".equals(ch) ? 1 : 0).reduce(0, Integer::sum);System.out.println(count);}
輸出結(jié)果
87
22
3
4.14 mapToInt
如果需要將Stream中的Integer類型轉(zhuǎn)換成int類型,可以使用mapToInt方法來(lái)實(shí)現(xiàn)
使用
public static void main(String[] args) {// Integer占用的內(nèi)存比int多很多,在Stream流操作中會(huì)自動(dòng)裝修和拆箱操作Integer arr[] = {1,2,3,5,6,8};Stream.of(arr).filter(i->i>0).forEach(System.out::println);System.out.println("---------");// 為了提高程序代碼的效率,我們可以先將流中Integer數(shù)據(jù)轉(zhuǎn)換為int數(shù)據(jù),然后再操作IntStream intStream = Stream.of(arr).mapToInt(Integer::intValue);intStream.filter(i->i>3).forEach(System.out::println);}
4.15 concat
? 如果有兩個(gè)流,希望合并成為一個(gè)流,那么可以使用Stream接口的靜態(tài)方法concat
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) {Objects.requireNonNull(a);Objects.requireNonNull(b);@SuppressWarnings("unchecked")Spliterator<T> split = new Streams.ConcatSpliterator.OfRef<>((Spliterator<T>) a.spliterator(), (Spliterator<T>) b.spliterator());Stream<T> stream = StreamSupport.stream(split, a.isParallel() || b.isParallel());return stream.onClose(Streams.composedClose(a, b));}
使用:
public static void main(String[] args) {Stream<String> stream1 = Stream.of("a","b","c");Stream<String> stream2 = Stream.of("x", "y", "z");// 通過(guò)concat方法將兩個(gè)流合并為一個(gè)新的流Stream.concat(stream1,stream2).forEach(System.out::println);}
4.16 綜合案例
定義兩個(gè)集合,然后在集合中存儲(chǔ)多個(gè)用戶名稱。然后完成如下的操作:
- 第一個(gè)隊(duì)伍只保留姓名長(zhǎng)度為3的成員
- 第一個(gè)隊(duì)伍篩選之后只要前3個(gè)人
- 第二個(gè)隊(duì)伍只要姓張的成員
- 第二個(gè)隊(duì)伍篩選之后不要前兩個(gè)人
- 將兩個(gè)隊(duì)伍合并為一個(gè)隊(duì)伍
- 根據(jù)姓名創(chuàng)建Person對(duì)象
- 打印整個(gè)隊(duì)伍的Person信息
package com.bobo.jdk.stream;import com.bobo.jdk.lambda.domain.Person;import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;public class StreamTest21Demo {/*** 1. 第一個(gè)隊(duì)伍只保留姓名長(zhǎng)度為3的成員* 2. 第一個(gè)隊(duì)伍篩選之后只要前3個(gè)人* 3. 第二個(gè)隊(duì)伍只要姓張的成員* 4. 第二個(gè)隊(duì)伍篩選之后不要前兩個(gè)人* 5. 將兩個(gè)隊(duì)伍合并為一個(gè)隊(duì)伍* 6. 根據(jù)姓名創(chuàng)建Person對(duì)象* 7. 打印整個(gè)隊(duì)伍的Person信息* @param args*/public static void main(String[] args) {List<String> list1 = Arrays.asList("迪麗熱巴", "宋遠(yuǎn)橋", "蘇星河", "老子", "莊子", "孫子", "洪七 公");List<String> list2 = Arrays.asList("古力娜扎", "張無(wú)忌", "張三豐", "趙麗穎", "張二狗", "張?zhí)鞇?ài)", "張三");// 1. 第一個(gè)隊(duì)伍只保留姓名長(zhǎng)度為3的成員// 2. 第一個(gè)隊(duì)伍篩選之后只要前3個(gè)人Stream<String> stream1 = list1.stream().filter(s -> s.length() == 3).limit(3);// 3. 第二個(gè)隊(duì)伍只要姓張的成員// 4. 第二個(gè)隊(duì)伍篩選之后不要前兩個(gè)人Stream<String> stream2 = list2.stream().filter(s -> s.startsWith("張")).skip(2);// 5. 將兩個(gè)隊(duì)伍合并為一個(gè)隊(duì)伍// 6. 根據(jù)姓名創(chuàng)建Person對(duì)象// 7. 打印整個(gè)隊(duì)伍的Person信息Stream.concat(stream1,stream2)//.map(n-> new Person(n)).map(Person::new).forEach(System.out::println);}
}
輸出結(jié)果:
Person{name='宋遠(yuǎn)橋', age=null, height=null}
Person{name='蘇星河', age=null, height=null}
Person{name='張二狗', age=null, height=null}
Person{name='張?zhí)鞇?ài)', age=null, height=null}
Person{name='張三', age=null, height=null}
5.Stream結(jié)果收集
5.1 結(jié)果收集到集合中
/*** Stream結(jié)果收集* 收集到集合中*/@Testpublic void test01(){// Stream<String> stream = Stream.of("aa", "bb", "cc");List<String> list = Stream.of("aa", "bb", "cc","aa").collect(Collectors.toList());System.out.println(list);// 收集到 Set集合中Set<String> set = Stream.of("aa", "bb", "cc", "aa").collect(Collectors.toSet());System.out.println(set);// 如果需要獲取的類型為具體的實(shí)現(xiàn),比如:ArrayList HashSetArrayList<String> arrayList = Stream.of("aa", "bb", "cc", "aa")//.collect(Collectors.toCollection(() -> new ArrayList<>()));.collect(Collectors.toCollection(ArrayList::new));System.out.println(arrayList);HashSet<String> hashSet = Stream.of("aa", "bb", "cc", "aa").collect(Collectors.toCollection(HashSet::new));System.out.println(hashSet);}
輸出:
[aa, bb, cc, aa]
[aa, bb, cc]
[aa, bb, cc, aa]
[aa, bb, cc]
5.2 結(jié)果收集到數(shù)組中
Stream中提供了toArray方法來(lái)將結(jié)果放到一個(gè)數(shù)組中,返回值類型是Object[],如果我們要指定返回的類型,那么可以使用另一個(gè)重載的toArray(IntFunction f)方法
/*** Stream結(jié)果收集到數(shù)組中*/@Testpublic void test02(){Object[] objects = Stream.of("aa", "bb", "cc", "aa").toArray(); // 返回的數(shù)組中的元素是 Object類型System.out.println(Arrays.toString(objects));// 如果我們需要指定返回的數(shù)組中的元素類型String[] strings = Stream.of("aa", "bb", "cc", "aa").toArray(String[]::new);System.out.println(Arrays.toString(strings));}
5.3 對(duì)流中的數(shù)據(jù)做聚合計(jì)算
? 當(dāng)我們使用Stream流處理數(shù)據(jù)后,可以像數(shù)據(jù)庫(kù)的聚合函數(shù)一樣對(duì)某個(gè)字段進(jìn)行操作,比如獲得最大值,最小值,求和,平均值,統(tǒng)計(jì)數(shù)量。
/*** Stream流中數(shù)據(jù)的聚合計(jì)算*/@Testpublic void test03(){// 獲取年齡的最大值Optional<Person> maxAge = Stream.of(new Person("張三", 18), new Person("李四", 22), new Person("張三", 13), new Person("王五", 15), new Person("張三", 19)).collect(Collectors.maxBy((p1, p2) -> p1.getAge() - p2.getAge()));System.out.println("最大年齡:" + maxAge.get());// 獲取年齡的最小值Optional<Person> minAge = Stream.of(new Person("張三", 18), new Person("李四", 22), new Person("張三", 13), new Person("王五", 15), new Person("張三", 19)).collect(Collectors.minBy((p1, p2) -> p1.getAge() - p2.getAge()));System.out.println("最新年齡:" + minAge.get());// 求所有人的年齡之和Integer sumAge = Stream.of(new Person("張三", 18), new Person("李四", 22), new Person("張三", 13), new Person("王五", 15), new Person("張三", 19))//.collect(Collectors.summingInt(s -> s.getAge())).collect(Collectors.summingInt(Person::getAge));System.out.println("年齡總和:" + sumAge);// 年齡的平均值Double avgAge = Stream.of(new Person("張三", 18), new Person("李四", 22), new Person("張三", 13), new Person("王五", 15), new Person("張三", 19)).collect(Collectors.averagingInt(Person::getAge));System.out.println("年齡的平均值:" + avgAge);// 統(tǒng)計(jì)數(shù)量Long count = Stream.of(new Person("張三", 18), new Person("李四", 22), new Person("張三", 13), new Person("王五", 15), new Person("張三", 19)).filter(p->p.getAge() > 18).collect(Collectors.counting());System.out.println("滿足條件的記錄數(shù):" + count);}
5.4 對(duì)流中數(shù)據(jù)做分組操作
? 當(dāng)我們使用Stream流處理數(shù)據(jù)后,可以根據(jù)某個(gè)屬性將數(shù)據(jù)分組
/*** 分組計(jì)算*/@Testpublic void test04(){// 根據(jù)賬號(hào)對(duì)數(shù)據(jù)進(jìn)行分組Map<String, List<Person>> map1 = Stream.of(new Person("張三", 18, 175), new Person("李四", 22, 177), new Person("張三", 14, 165), new Person("李四", 15, 166), new Person("張三", 19, 182)).collect(Collectors.groupingBy(Person::getName));map1.forEach((k,v)-> System.out.println("k=" + k +"\t"+ "v=" + v));System.out.println("-----------");// 根據(jù)年齡分組 如果大于等于18 成年否則未成年Map<String, List<Person>> map2 = Stream.of(new Person("張三", 18, 175), new Person("李四", 22, 177), new Person("張三", 14, 165), new Person("李四", 15, 166), new Person("張三", 19, 182)).collect(Collectors.groupingBy(p -> p.getAge() >= 18 ? "成年" : "未成年"));map2.forEach((k,v)-> System.out.println("k=" + k +"\t"+ "v=" + v));}
輸出結(jié)果:
k=李四 v=[Person{name='李四', age=22, height=177}, Person{name='李四', age=15, height=166}]
k=張三 v=[Person{name='張三', age=18, height=175}, Person{name='張三', age=14, height=165}, Person{name='張三', age=19, height=182}]
-----------
k=未成年 v=[Person{name='張三', age=14, height=165}, Person{name='李四', age=15, height=166}]
k=成年 v=[Person{name='張三', age=18, height=175}, Person{name='李四', age=22, height=177}, Person{name='張三', age=19, height=182}]
多級(jí)分組: 先根據(jù)name分組然后根據(jù)年齡分組
/*** 分組計(jì)算--多級(jí)分組*/@Testpublic void test05(){// 先根據(jù)name分組,然后根據(jù)age(成年和未成年)分組Map<String,Map<Object,List<Person>>> map = Stream.of(new Person("張三", 18, 175), new Person("李四", 22, 177), new Person("張三", 14, 165), new Person("李四", 15, 166), new Person("張三", 19, 182)).collect(Collectors.groupingBy(Person::getName,Collectors.groupingBy(p->p.getAge()>=18?"成年":"未成年")));map.forEach((k,v)->{System.out.println(k);v.forEach((k1,v1)->{System.out.println("\t"+k1 + "=" + v1);});});}
輸出結(jié)果:
李四未成年=[Person{name='李四', age=15, height=166}]成年=[Person{name='李四', age=22, height=177}]
張三未成年=[Person{name='張三', age=14, height=165}]成年=[Person{name='張三', age=18, height=175}, Person{name='張三', age=19, height=182}]
5.5 對(duì)流中的數(shù)據(jù)做分區(qū)操作
Collectors.partitioningBy會(huì)根據(jù)值是否為true,把集合中的數(shù)據(jù)分割為兩個(gè)列表,一個(gè)true列表,一個(gè)false列表
/*** 分區(qū)操作*/@Testpublic void test06(){Map<Boolean, List<Person>> map = Stream.of(new Person("張三", 18, 175), new Person("李四", 22, 177), new Person("張三", 14, 165), new Person("李四", 15, 166), new Person("張三", 19, 182)).collect(Collectors.partitioningBy(p -> p.getAge() > 18));map.forEach((k,v)-> System.out.println(k+"\t" + v));}
輸出結(jié)果:
false [Person{name='張三', age=18, height=175}, Person{name='張三', age=14, height=165}, Person{name='李四', age=15, height=166}]
true [Person{name='李四', age=22, height=177}, Person{name='張三', age=19, height=182}]
5.6 對(duì)流中的數(shù)據(jù)做拼接
Collectors.joining會(huì)根據(jù)指定的連接符,將所有的元素連接成一個(gè)字符串
/*** 對(duì)流中的數(shù)據(jù)做拼接操作*/@Testpublic void test07(){String s1 = Stream.of(new Person("張三", 18, 175), new Person("李四", 22, 177), new Person("張三", 14, 165), new Person("李四", 15, 166), new Person("張三", 19, 182)).map(Person::getName).collect(Collectors.joining());// 張三李四張三李四張三System.out.println(s1);String s2 = Stream.of(new Person("張三", 18, 175), new Person("李四", 22, 177), new Person("張三", 14, 165), new Person("李四", 15, 166), new Person("張三", 19, 182)).map(Person::getName).collect(Collectors.joining("_"));// 張三_李四_張三_李四_張三System.out.println(s2);String s3 = Stream.of(new Person("張三", 18, 175), new Person("李四", 22, 177), new Person("張三", 14, 165), new Person("李四", 15, 166), new Person("張三", 19, 182)).map(Person::getName).collect(Collectors.joining("_", "###", "$$$"));// ###張三_李四_張三_李四_張三$$$System.out.println(s3);}
6. 并行的Stream流
6.1 串行的Stream流
我們前面使用的Stream流都是串行,也就是在一個(gè)線程上面執(zhí)行。
/*** 串行流*/@Testpublic void test01(){Stream.of(5,6,8,3,1,6).filter(s->{System.out.println(Thread.currentThread() + "" + s);return s > 3;}).count();}
輸出:
Thread[main,5,main]5
Thread[main,5,main]6
Thread[main,5,main]8
Thread[main,5,main]3
Thread[main,5,main]1
Thread[main,5,main]6
6.2 并行流
parallelStream其實(shí)就是一個(gè)并行執(zhí)行的流,它通過(guò)默認(rèn)的ForkJoinPool,可以提高多線程任務(wù)的速度。
6.2.1 獲取并行流
我們可以通過(guò)兩種方式來(lái)獲取并行流。
- 通過(guò)List接口中的parallelStream方法來(lái)獲取
- 通過(guò)已有的串行流轉(zhuǎn)換為并行流(parallel)
實(shí)現(xiàn):
/*** 獲取并行流的兩種方式*/@Testpublic void test02(){List<Integer> list = new ArrayList<>();// 通過(guò)List 接口 直接獲取并行流Stream<Integer> integerStream = list.parallelStream();// 將已有的串行流轉(zhuǎn)換為并行流Stream<Integer> parallel = Stream.of(1, 2, 3).parallel();}
6.2.2 并行流操作
/*** 并行流操作*/@Testpublic void test03(){Stream.of(1,4,2,6,1,5,9).parallel() // 將流轉(zhuǎn)換為并發(fā)流,Stream處理的時(shí)候就會(huì)通過(guò)多線程處理.filter(s->{System.out.println(Thread.currentThread() + " s=" +s);return s > 2;}).count();}
效果
Thread[main,5,main] s=1
Thread[ForkJoinPool.commonPool-worker-2,5,main] s=9
Thread[ForkJoinPool.commonPool-worker-6,5,main] s=6
Thread[ForkJoinPool.commonPool-worker-13,5,main] s=2
Thread[ForkJoinPool.commonPool-worker-9,5,main] s=4
Thread[ForkJoinPool.commonPool-worker-4,5,main] s=5
Thread[ForkJoinPool.commonPool-worker-11,5,main] s=1
6.3 并行流和串行流對(duì)比
我們通過(guò)for循環(huán),串行Stream流,并行Stream流來(lái)對(duì)500000000億個(gè)數(shù)字求和。來(lái)看消耗時(shí)間
package com.bobo.jdk.res;import org.junit.After;
import org.junit.Before;
import org.junit.Test;import java.util.stream.LongStream;public class Test03 {private static long times = 500000000;private long start;@Beforepublic void befor(){start = System.currentTimeMillis();}@Afterpublic void end(){long end = System.currentTimeMillis();System.out.println("消耗時(shí)間:" + (end - start));}/*** 普通for循環(huán) 消耗時(shí)間:138*/@Testpublic void test01(){System.out.println("普通for循環(huán):");long res = 0;for (int i = 0; i < times; i++) {res += i;}}/*** 串行流處理* 消耗時(shí)間:203*/@Testpublic void test02(){System.out.println("串行流:serialStream");LongStream.rangeClosed(0,times).reduce(0,Long::sum);}/*** 并行流處理 消耗時(shí)間:84*/@Testpublic void test03(){LongStream.rangeClosed(0,times).parallel().reduce(0,Long::sum);}
}
通過(guò)案例我們可以看到parallelStream的效率是最高的。
Stream并行處理的過(guò)程會(huì)分而治之,也就是將一個(gè)大的任務(wù)切分成了多個(gè)小任務(wù),這表示每個(gè)任務(wù)都是一個(gè)線程操作。
6.4 線程安全問(wèn)題
在多線程的處理下,肯定會(huì)出現(xiàn)數(shù)據(jù)安全問(wèn)題。如下:
@Testpublic void test01(){List<Integer> list = new ArrayList<>();for (int i = 0; i < 1000; i++) {list.add(i);}System.out.println(list.size());List<Integer> listNew = new ArrayList<>();// 使用并行流來(lái)向集合中添加數(shù)據(jù)list.parallelStream()//.forEach(s->listNew.add(s));.forEach(listNew::add);System.out.println(listNew.size());}
運(yùn)行效果:
839
或者直接拋異常
java.lang.ArrayIndexOutOfBoundsExceptionat sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)at java.lang.reflect.Constructor.newInstance(Constructor.java:423)at java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:598)
....
Caused by: java.lang.ArrayIndexOutOfBoundsException: 366at java.util.ArrayList.add(ArrayList.java:463)
針對(duì)這個(gè)問(wèn)題,我們的解決方案有哪些呢?
- 加同步鎖
- 使用線程安全的容器
- 通過(guò)Stream中的toArray/collect操作
實(shí)現(xiàn):
/*** 加同步鎖*/@Testpublic void test02(){List<Integer> listNew = new ArrayList<>();Object obj = new Object();IntStream.rangeClosed(1,1000).parallel().forEach(i->{synchronized (obj){listNew.add(i);}});System.out.println(listNew.size());}/*** 使用線程安全的容器*/@Testpublic void test03(){Vector v = new Vector();Object obj = new Object();IntStream.rangeClosed(1,1000).parallel().forEach(i->{synchronized (obj){v.add(i);}});System.out.println(v.size());}/*** 將線程不安全的容器轉(zhuǎn)換為線程安全的容器*/@Testpublic void test04(){List<Integer> listNew = new ArrayList<>();// 將線程不安全的容器包裝為線程安全的容器List<Integer> synchronizedList = Collections.synchronizedList(listNew);Object obj = new Object();IntStream.rangeClosed(1,1000).parallel().forEach(i->{synchronizedList.add(i);});System.out.println(synchronizedList.size());}/*** 我們還可以通過(guò)Stream中的 toArray方法或者 collect方法來(lái)操作* 就是滿足線程安全的要求*/@Testpublic void test05(){List<Integer> listNew = new ArrayList<>();Object obj = new Object();List<Integer> list = IntStream.rangeClosed(1, 1000).parallel().boxed().collect(Collectors.toList());System.out.println(list.size());}
七、Optional類
這個(gè)Optional類注意是解決空指針的問(wèn)題
1. 以前對(duì)null 的處理
@Testpublic void test01(){//String userName = "張三";String userName = null;if(userName != null){System.out.println("字符串的長(zhǎng)度:" + userName.length());}else{System.out.println("字符串為空");}}
2. Optional類
Optional是一個(gè)沒(méi)有子類的工具類,Optional是一個(gè)可以為null的容器對(duì)象,它的主要作用就是為了避免Null檢查,防止NullpointerException,
3. Optional的基本使用
Optional對(duì)象的創(chuàng)建方式
/*** Optional對(duì)象的創(chuàng)建方式*/@Testpublic void test02(){// 第一種方式 通過(guò)of方法 of方法是不支持null的Optional<String> op1 = Optional.of("zhangsan");//Optional<Object> op2 = Optional.of(null);// 第二種方式通過(guò) ofNullable方法 支持nullOptional<String> op3 = Optional.ofNullable("lisi");Optional<Object> op4 = Optional.ofNullable(null);// 第三種方式 通過(guò)empty方法直接創(chuàng)建一個(gè)空的Optional對(duì)象Optional<Object> op5 = Optional.empty();}
4. Optional的常用方法
/*** Optional中的常用方法介紹* get(): 如果Optional有值則返回,否則拋出NoSuchElementException異常* get()通常和isPresent方法一塊使用* isPresent():判斷是否包含值,包含值返回true,不包含值返回false* orElse(T t):如果調(diào)用對(duì)象包含值,就返回該值,否則返回t* orElseGet(Supplier s):如果調(diào)用對(duì)象包含值,就返回該值,否則返回 Lambda表達(dá)式的返回值*/@Testpublic void test03(){Optional<String> op1 = Optional.of("zhangsan");Optional<String> op2 = Optional.empty();// 獲取Optional中的值if(op1.isPresent()){String s1 = op1.get();System.out.println("用戶名稱:" +s1);}if(op2.isPresent()){System.out.println(op2.get());}else{System.out.println("op2是一個(gè)空Optional對(duì)象");}String s3 = op1.orElse("李四");System.out.println(s3);String s4 = op2.orElse("王五");System.out.println(s4);String s5 = op2.orElseGet(()->{return "Hello";});System.out.println(s5);}
@Testpublic void test04(){Optional<String> op1 = Optional.of("zhangsan");Optional<String> op2 = Optional.empty();// 如果存在值 就做什么op1.ifPresent(s-> System.out.println("有值:" +s));op1.ifPresent(System.out::println);}/*** 自定義一個(gè)方法,將Person對(duì)象中的 name 轉(zhuǎn)換為大寫(xiě) 并返回*/@Testpublic void test05(){Person p = new Person("zhangsan",18);Optional<Person> op = Optional.of(p);String name = getNameForOptional(op);System.out.println("name="+name);}/*** 根據(jù)Person對(duì)象 將name轉(zhuǎn)換為大寫(xiě)并返回* 通過(guò)Optional方式實(shí)現(xiàn)* @param op* @return*/public String getNameForOptional(Optional<Person> op){if(op.isPresent()){String msg = //op.map(p -> p.getName())op.map(Person::getName)//.map(p -> p.toUpperCase()).map(String::toUpperCase).orElse("空值");return msg;}return null;}/*** 根據(jù)Person對(duì)象 將name轉(zhuǎn)換為大寫(xiě)并返回* @param person* @return*/public String getName(Person person){if(person != null){String name = person.getName();if(name != null){return name.toUpperCase();}else{return null;}}else{return null;}}
八、新時(shí)間日期API
1.舊版日期時(shí)間的問(wèn)題
? 在舊版本中JDK對(duì)于日期和時(shí)間這塊的時(shí)間是非常差的。
/*** 舊版日期時(shí)間設(shè)計(jì)的問(wèn)題*/@Testpublic void test01() throws Exception{// 1.設(shè)計(jì)不合理Date date = new Date(2021,05,05);System.out.println(date);// 2.時(shí)間格式化和解析操作是線程不安全的SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");for (int i = 0; i < 50; i++) {new Thread(()->{// System.out.println(sdf.format(date));try {System.out.println(sdf.parse("2021-05-06"));} catch (ParseException e) {e.printStackTrace();}}).start();}}
- 設(shè)計(jì)不合理,在java.util和java.sql的包中都有日期類,java.util.Date同時(shí)包含日期和時(shí)間的,而java.sql.Date僅僅包含日期,此外用于格式化和解析的類在java.text包下。
- 非線程安全,java.util.Date是非線程安全的,所有的日期類都是可變的,這是java日期類最大的問(wèn)題之一。
- 時(shí)區(qū)處理麻煩,日期類并不提供國(guó)際化,沒(méi)有時(shí)區(qū)支持。
2. 新日期時(shí)間API介紹
JDK 8中增加了一套全新的日期時(shí)間API,這套API設(shè)計(jì)合理,是線程安全的。新的日期及時(shí)間API位于 java.time 包
中,下面是一些關(guān)鍵類。
- LocalDate :表示日期,包含年月日,格式為 2019-10-16
- LocalTime :表示時(shí)間,包含時(shí)分秒,格式為 16:38:54.158549300
- LocalDateTime :表示日期時(shí)間,包含年月日,時(shí)分秒,格式為 2018-09-06T15:33:56.750
- DateTimeFormatter :日期時(shí)間格式化類。
- Instant:時(shí)間戳,表示一個(gè)特定的時(shí)間瞬間。
- Duration:用于計(jì)算2個(gè)時(shí)間(LocalTime,時(shí)分秒)的距離
- Period:用于計(jì)算2個(gè)日期(LocalDate,年月日)的距離
- ZonedDateTime :包含時(shí)區(qū)的時(shí)間
Java中使用的歷法是ISO 8601日歷系統(tǒng),它是世界民用歷法,也就是我們所說(shuō)的公歷。平年有365天,閏年是366
天。此外Java 8還提供了4套其他歷法,分別是:
- ThaiBuddhistDate:泰國(guó)佛教歷
- MinguoDate:中華民國(guó)歷
- JapaneseDate:日本歷
- HijrahDate:伊斯蘭歷
2.1 日期時(shí)間的常見(jiàn)操作
? LocalDate,LocalTime以及LocalDateTime的操作。
/*** JDK8 日期時(shí)間操作*/@Testpublic void test01(){// 1.創(chuàng)建指定的日期LocalDate date1 = LocalDate.of(2021, 05, 06);System.out.println("date1 = "+date1);// 2.得到當(dāng)前的日期LocalDate now = LocalDate.now();System.out.println("now = "+now);// 3.根據(jù)LocalDate對(duì)象獲取對(duì)應(yīng)的日期信息System.out.println("年:" + now.getYear());System.out.println("月:" + now.getMonth().getValue());System.out.println("日:" + now.getDayOfMonth());System.out.println("星期:" + now.getDayOfWeek().getValue());}/*** 時(shí)間操作*/@Testpublic void test02(){// 1.得到指定的時(shí)間LocalTime time = LocalTime.of(5,26,33,23145);System.out.println(time);// 2.獲取當(dāng)前的時(shí)間LocalTime now = LocalTime.now();System.out.println(now);// 3.獲取時(shí)間信息System.out.println(now.getHour());System.out.println(now.getMinute());System.out.println(now.getSecond());System.out.println(now.getNano());}/*** 日期時(shí)間類型 LocalDateTime*/@Testpublic void test03(){// 獲取指定的日期時(shí)間LocalDateTime dateTime =LocalDateTime.of(2020, 06, 01, 12, 12, 33, 213);System.out.println(dateTime);// 獲取當(dāng)前的日期時(shí)間LocalDateTime now = LocalDateTime.now();System.out.println(now);// 獲取日期時(shí)間信息System.out.println(now.getYear());System.out.println(now.getMonth().getValue());System.out.println(now.getDayOfMonth());System.out.println(now.getDayOfWeek().getValue());System.out.println(now.getHour());System.out.println(now.getMinute());System.out.println(now.getSecond());System.out.println(now.getNano());}
2.2 日期時(shí)間的修改和比較
/*** 日期時(shí)間的修改*/@Testpublic void test01(){LocalDateTime now = LocalDateTime.now();System.out.println("now = "+now);// 修改日期時(shí)間 對(duì)日期時(shí)間的修改,對(duì)已存在的LocalDate對(duì)象,創(chuàng)建了它模板// 并不會(huì)修改原來(lái)的信息LocalDateTime localDateTime = now.withYear(1998);System.out.println("now :"+now);System.out.println("修改后的:" + localDateTime);System.out.println("月份:" + now.withMonth(10));System.out.println("天:" + now.withDayOfMonth(6));System.out.println("小時(shí):" + now.withHour(8));System.out.println("分鐘:" + now.withMinute(15));// 在當(dāng)前日期時(shí)間的基礎(chǔ)上 加上或者減去指定的時(shí)間System.out.println("兩天后:" + now.plusDays(2));System.out.println("10年后:"+now.plusYears(10));System.out.println("6個(gè)月后 = " + now.plusMonths(6));System.out.println("10年前 = " + now.minusYears(10));System.out.println("半年前 = " + now.minusMonths(6));System.out.println("一周前 = " + now.minusDays(7));}/*** 日期時(shí)間的比較*/@Testpublic void test02(){LocalDate now = LocalDate.now();LocalDate date = LocalDate.of(2020, 1, 3);// 在JDK8中要實(shí)現(xiàn) 日期的比較 isAfter isBefore isEqual 通過(guò)這幾個(gè)方法來(lái)直接比較System.out.println(now.isAfter(date)); // trueSystem.out.println(now.isBefore(date)); // falseSystem.out.println(now.isEqual(date)); // false}
注意:在進(jìn)行日期時(shí)間修改的時(shí)候,原來(lái)的LocalDate對(duì)象是不會(huì)被修改,每次操作都是返回了一個(gè)新的LocalDate對(duì)象,所以在多線程場(chǎng)景下是數(shù)據(jù)安全的。
2.3 格式化和解析操作
在JDK8中我們可以通過(guò)java.time.format.DateTimeFormatter
類可以進(jìn)行日期的解析和格式化操作
/*** 日期格式化*/@Testpublic void test01(){LocalDateTime now = LocalDateTime.now();// 指定格式 使用系統(tǒng)默認(rèn)的格式 2021-05-27T16:16:38.139DateTimeFormatter isoLocalDateTime = DateTimeFormatter.ISO_LOCAL_DATE_TIME;// 將日期時(shí)間轉(zhuǎn)換為字符串String format = now.format(isoLocalDateTime);System.out.println("format = " + format);// 通過(guò) ofPattern 方法來(lái)指定特定的格式DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");String format1 = now.format(dateTimeFormatter);// 2021-05-27 16:16:38System.out.println("format1 = " + format1);// 將字符串解析為一個(gè) 日期時(shí)間類型LocalDateTime parse = LocalDateTime.parse("1997-05-06 22:45:16", dateTimeFormatter);// parse = 1997-05-06T22:45:16System.out.println("parse = " + parse);}
2.4 Instant類
在JDK8中給我們新增一個(gè)Instant類(時(shí)間戳/時(shí)間線),內(nèi)部保存了從1970年1月1日 00:00:00以來(lái)的秒和納秒
/*** Instant 時(shí)間戳* 可以用來(lái)統(tǒng)計(jì)時(shí)間消耗*/@Testpublic void test01() throws Exception{Instant now = Instant.now();System.out.println("now = " + now);// 獲取從1970年一月一日 00:00:00 到現(xiàn)在的 納秒System.out.println(now.getNano());Thread.sleep(5);Instant now1 = Instant.now();System.out.println("耗時(shí):" + (now1.getNano() - now.getNano()));}
2.5 計(jì)算日期時(shí)間差
JDK8中提供了兩個(gè)工具類Duration/Period:計(jì)算日期時(shí)間差
- Duration:用來(lái)計(jì)算兩個(gè)時(shí)間差(LocalTime)
- Period:用來(lái)計(jì)算兩個(gè)日期差(LocalDate)
/*** 計(jì)算日期時(shí)間差*/@Testpublic void test01(){// 計(jì)算時(shí)間差LocalTime now = LocalTime.now();LocalTime time = LocalTime.of(22, 48, 59);System.out.println("now = " + now);// 通過(guò)Duration來(lái)計(jì)算時(shí)間差Duration duration = Duration.between(now, time);System.out.println(duration.toDays()); // 0System.out.println(duration.toHours()); // 6System.out.println(duration.toMinutes()); // 368System.out.println(duration.toMillis()); // 22124240// 計(jì)算日期差LocalDate nowDate = LocalDate.now();LocalDate date = LocalDate.of(1997, 12, 5);Period period = Period.between(date, nowDate);System.out.println(period.getYears()); // 23System.out.println(period.getMonths()); // 5System.out.println(period.getDays()); // 22}
2.6 時(shí)間校正器
有時(shí)候我們可以需要如下調(diào)整:將日期調(diào)整到"下個(gè)月的第一天"等操作。這時(shí)我們通過(guò)時(shí)間校正器效果可能會(huì)更好。
- TemporalAdjuster:時(shí)間校正器
- TemporalAdjusters:通過(guò)該類靜態(tài)方法提供了大量的常用TemporalAdjuster的實(shí)現(xiàn)。
/*** 時(shí)間校正器*/@Testpublic void test02(){LocalDateTime now = LocalDateTime.now();// 將當(dāng)前的日期調(diào)整到下個(gè)月的一號(hào)TemporalAdjuster adJuster = (temporal)->{LocalDateTime dateTime = (LocalDateTime) temporal;LocalDateTime nextMonth = dateTime.plusMonths(1).withDayOfMonth(1);System.out.println("nextMonth = " + nextMonth);return nextMonth;};// 我們可以通過(guò)TemporalAdjusters 來(lái)實(shí)現(xiàn)// LocalDateTime nextMonth = now.with(adJuster);LocalDateTime nextMonth = now.with(TemporalAdjusters.firstDayOfNextMonth());System.out.println("nextMonth = " + nextMonth);}
2.7 日期時(shí)間的時(shí)區(qū)
? Java8 中加入了對(duì)時(shí)區(qū)的支持,LocalDate、LocalTime、LocalDateTime是不帶時(shí)區(qū)的,帶時(shí)區(qū)的日期時(shí)間類分別為:ZonedDate、ZonedTime、ZonedDateTime。
其中每個(gè)時(shí)區(qū)都對(duì)應(yīng)著 ID,ID的格式為 “區(qū)域/城市” 。例如 :Asia/Shanghai 等。
ZoneId:該類中包含了所有的時(shí)區(qū)信息
/*** 時(shí)區(qū)操作*/@Testpublic void test01(){// 1.獲取所有的時(shí)區(qū)id// ZoneId.getAvailableZoneIds().forEach(System.out::println);// 獲取當(dāng)前時(shí)間 中國(guó)使用的 東八區(qū)的時(shí)區(qū),比標(biāo)準(zhǔn)時(shí)間早8個(gè)小時(shí)LocalDateTime now = LocalDateTime.now();System.out.println("now = " + now); // 2021-05-27T17:17:06.951// 獲取標(biāo)準(zhǔn)時(shí)間ZonedDateTime bz = ZonedDateTime.now(Clock.systemUTC());System.out.println("bz = " + bz); // 2021-05-27T09:17:06.952Z// 使用計(jì)算機(jī)默認(rèn)的時(shí)區(qū),創(chuàng)建日期時(shí)間ZonedDateTime now1 = ZonedDateTime.now();System.out.println("now1 = " + now1); //2021-05-27T17:17:06.952+08:00[Asia/Shanghai]// 使用指定的時(shí)區(qū)創(chuàng)建日期時(shí)間ZonedDateTime now2 = ZonedDateTime.now(ZoneId.of("America/Marigot"));System.out.println("now2 = " + now2);}
JDK新的日期和時(shí)間API的優(yōu)勢(shì):
- 新版日期時(shí)間API中,日期和時(shí)間對(duì)象是不可變,操作日期不會(huì)影響原來(lái)的值,而是生成一個(gè)新的實(shí)例
- 提供不同的兩種方式,有效的區(qū)分了人和機(jī)器的操作
- TemporalAdjuster可以更精確的操作日期,還可以自定義日期調(diào)整期
- 線程安全
九、其他新特性
1.重復(fù)注解
? 自從Java 5中引入 注解 以來(lái),注解開(kāi)始變得非常流行,并在各個(gè)框架和項(xiàng)目中被廣泛使用。不過(guò)注解有一個(gè)很大的限
制是:在同一個(gè)地方不能多次使用同一個(gè)注解。JDK 8引入了重復(fù)注解的概念,允許在同一個(gè)地方多次使用同一個(gè)注
解。在JDK 8中使用**@Repeatable**注解定義重復(fù)注解。
1.1 定義一個(gè)重復(fù)注解的容器
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotations {MyAnnotation[] value();
}
1.2 定義一個(gè)可以重復(fù)的注解
@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {String value();
}
1.3 配置多個(gè)重復(fù)的注解
@MyAnnotation("test1")
@MyAnnotation("test2")
@MyAnnotation("test3")
public class AnnoTest01 {@MyAnnotation("fun1")@MyAnnotation("fun2")public void test01(){}}
1.4 解析得到指定的注解
/*** 解析重復(fù)注解* @param args*/public static void main(String[] args) throws NoSuchMethodException {// 獲取類中標(biāo)注的重復(fù)注解MyAnnotation[] annotationsByType = AnnoTest01.class.getAnnotationsByType(MyAnnotation.class);for (MyAnnotation myAnnotation : annotationsByType) {System.out.println(myAnnotation.value());}// 獲取方法上標(biāo)注的重復(fù)注解MyAnnotation[] test01s = AnnoTest01.class.getMethod("test01").getAnnotationsByType(MyAnnotation.class);for (MyAnnotation test01 : test01s) {System.out.println(test01.value());}}
2.類型注解
JDK 8為@Target元注解新增了兩種類型: TYPE_PARAMETER , TYPE_USE 。
- TYPE_PARAMETER :表示該注解能寫(xiě)在類型參數(shù)的聲明語(yǔ)句中。 類型參數(shù)聲明如: 、
- TYPE_USE :表示注解可以再任何用到類型的地方使用。
TYPE_PARAMETER
@Target(ElementType.TYPE_PARAMETER)
public @interface TypeParam {
}
使用:
public class TypeDemo01 <@TypeParam T> {public <@TypeParam K extends Object> K test01(){return null;}
}
TYPE_USE
@Target(ElementType.TYPE_USE)
public @interface NotNull {
}
使用
public class TypeUseDemo01 {public @NotNull Integer age = 10;public Integer sum(@NotNull Integer a,@NotNull Integer b){return a + b;}
}