1. 新語法結(jié)構(gòu)
新的語法結(jié)構(gòu),勾勒出了 Java 語法進化的一個趨勢,將開發(fā)者從復(fù)雜、繁瑣
的低層次抽象中逐漸解放出來,以更高層次、更優(yōu)雅的抽象,既降低代碼量
,又避免意外編程錯誤的出現(xiàn),進而提高代碼質(zhì)量和開發(fā)效率。
1.1 Java的REPL工具: jShell命令
JDK9的新特性
Java 終于擁有了像Python 和 Scala 之類語言的REPL工具(交互式編程環(huán)境,read - evaluate - print - loop):jShell
。以交互式的方式對語句和表達式進行求值。即寫即得
、快速運行
。
利用jShell在沒有創(chuàng)建類的情況下,在命令行里直接聲明變量,計算表達式,執(zhí)行語句。無需跟人解釋”public static void main(String[] args)
”這句"廢話"。
使用舉例
- 調(diào)出
jShell
- 獲取幫助
- 基本使用
- 導(dǎo)入指定的包
- 默認已經(jīng)導(dǎo)入如下的所有包:(包含java.lang包)
- 只需按下 Tab 鍵,就能自動補全代碼
- 列出當(dāng)前 session 里所有有效的代碼片段
- 查看當(dāng)前 session 下所有創(chuàng)建過的變量
- 查看當(dāng)前 session 下所有創(chuàng)建過的方法
Tips:我們還可以重新定義相同方法名和參數(shù)列表的方法,即對現(xiàn)有方法的修改(或覆蓋)。
- 使用外部代碼編輯器來編寫 Java 代碼
從外部文件加載源代碼【HelloWorld.java】
public void printHello() {
System.out.println("馬上周末了,祝大家周末快樂!");
}
printHello();
-
使用
/open
命令調(diào)用 -
退出
jShell
1.2 異常處理之try-catch資源關(guān)閉
在JDK7 之前,這樣處理資源的關(guān)閉:
@Test
public void test01() {
FileWriter fw = null;
BufferedWriter bw = null;
try {
fw = new FileWriter("d:/1.txt");
bw = new BufferedWriter(fw);
bw.write("hello");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bw != null) {
bw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fw != null) {
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
JDK7的新特性
在try
的后面可以增加一個()
,在括號中可以聲明流對象并初始化。try中的代碼執(zhí)行完畢,會自動把流對象釋放,就不用寫finally
了。
格式:
try(資源對象的聲明和初始化){
業(yè)務(wù)邏輯代碼,可能會產(chǎn)生異常
}catch(異常類型1 e){
處理異常代碼
}catch(異常類型2 e){
處理異常代碼
}
說明:
1、在try()
中聲明的資源,無論是否發(fā)生異常,無論是否處理異常,都會自動關(guān)閉資源對象,不用手動關(guān)閉了。
2、這些資源實現(xiàn)類必須實現(xiàn)AutoCloseable
或Closeable
接口,實現(xiàn)其中的close()
方法。Closeable
是AutoCloseable
的子接口。Java7幾乎把所有的“資源類”(包括文件IO的各種類、JDBC編程的Connection、Statement等接口…)都進行了改寫,改寫后資源類都實現(xiàn)了AutoCloseable
或Closeable
接口,并實現(xiàn)了close()
方法。
3、寫到try()
中的資源類的變量默認是 final 聲明的,不能修改。
舉例:
//舉例1
@Test
public void test02() {
try (
FileWriter fw = new FileWriter("d:/1.txt");
BufferedWriter bw = new BufferedWriter(fw);
) {
bw.write("hello");
} catch (IOException e) {
e.printStackTrace();
}
}
//舉例2
@Test
public void test03() {
//從d:/1.txt(utf-8)文件中,讀取內(nèi)容,寫到項目根目錄下1.txt(gbk)文件中
try (
FileInputStream fis = new FileInputStream("d:/1.txt");
InputStreamReader isr = new InputStreamReader(fis, "utf-8");
BufferedReader br = new BufferedReader(isr);
FileOutputStream fos = new FileOutputStream("1.txt");
OutputStreamWriter osw = new OutputStreamWriter(fos, "gbk");
BufferedWriter bw = new BufferedWriter(osw);
) {
String str;
while ((str = br.readLine()) != null) {
bw.write(str);
bw.newLine();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
JDK9的新特性
try的前面可以定義流對象,try后面的()中可以直接引用流對象的名稱。在try代碼執(zhí)行完畢后,流對象也可以釋放掉,也不用寫finally
了。
格式:
A a = new A();
B b = new B();
try(a;b){
可能產(chǎn)生的異常代碼
}catch(異常類名 變量名){
異常處理的邏輯
}
舉例:
@Test
public void test04() {
InputStreamReader reader = new InputStreamReader(System.in);
OutputStreamWriter writer = new OutputStreamWriter(System.out);
try (reader; writer) {
//reader是final的,不可再被賦值
// reader = null;
} catch (IOException e) {
e.printStackTrace();
}
}
1.3 局部變量類型推斷
JDK 10的新特性
局部變量的顯示類型聲明,常常被認為是不必須的,給一個好聽的名字反而可以很清楚的表達出下面應(yīng)該怎樣繼續(xù)。本新特性允許開發(fā)人員省略通常不必要的局部變量類型聲明,以增強Java語言的體驗性、可讀性。
- 使用舉例
//1.局部變量的實例化
var list = new ArrayList<String>();
var set = new LinkedHashSet<Integer>();
//2.增強for循環(huán)中的索引
for (var v : list) {
System.out.println(v);
}
//3.傳統(tǒng)for循環(huán)中
for (var i = 0; i < 100; i++) {
System.out.println(i);
}
//4. 返回值類型含復(fù)雜泛型結(jié)構(gòu)
var iterator = set.iterator();
//Iterator<Map.Entry<Integer, Student>> iterator = set.iterator();
- 不適用場景
- 聲明一個成員變量
- 聲明一個數(shù)組變量,并為數(shù)組靜態(tài)初始化(省略new的情況下)
- 方法的返回值類型
- 方法的參數(shù)類型
- 沒有初始化的方法內(nèi)的局部變量聲明
- 作為catch塊中異常類型
- Lambda表達式中函數(shù)式接口的類型
- 方法引用中函數(shù)式接口的類型
代碼舉例:
聲明一個成員變量,并初始化值為null
聲明一個數(shù)組變量,并為數(shù)組靜態(tài)初始化(省略new的情況下)
沒有初始化的方法內(nèi)的局部變量聲明
方法的返回值類型
方法的參數(shù)類型
構(gòu)造器的參數(shù)類型
作為catch
塊中異常類型
Lambda表達式中函數(shù)式接口的類型
方法引用中函數(shù)式接口的類型
注意:
-
var不是一個關(guān)鍵字,而是一個類型名,將它作為變量的類型。不能使用 var 作為類名。
-
這不是JavaScript。var并不會改變 Java是一門靜態(tài)類型語言的事實。編譯器負責(zé)推斷出類型,并把結(jié)果寫入字節(jié)碼文件,就好像是開發(fā)人員自己敲入類型一樣。
1.4 instanceof的模式匹配
JDK14中預(yù)覽特性:
instanceof
模式匹配通過提供更為簡便的語法,來提高生產(chǎn)力。有了該功能,可以減少Java程序中顯式強制轉(zhuǎn)換的數(shù)量,實現(xiàn)更精確、簡潔的類型安全的代碼。
Java 14之前舊寫法:
if(obj instanceof String){
String str = (String)obj; //需要強轉(zhuǎn)
.. str.contains(..)..
}else{
...
}
Java 14新特性寫法:
if(obj instanceof String str){
.. str.contains(..)..
}else{
...
}
舉例:
/**
* instanceof的模式匹配(預(yù)覽)
*
* @author shkstart
* @create 上午 11:32
*/
public class Feature01 {
@Test
public void test1(){
Object obj = new String("hello,Java14");
obj = null;//在使用null 匹配instanceof 時,返回都是false.
if(obj instanceof String){
String str = (String) obj;
System.out.println(str.contains("Java"));
}else{
System.out.println("非String類型");
}
//舉例1:
if(obj instanceof String str){ //新特性:省去了強制類型轉(zhuǎn)換的過程
System.out.println(str.contains("Java"));
}else{
System.out.println("非String類型");
}
}
}
// 舉例2
class InstanceOf{
String str = "abc";
public void test(Object obj){
if(obj instanceof String str){//此時的str的作用域僅限于if結(jié)構(gòu)內(nèi)。
System.out.println(str.toUpperCase());
}else{
System.out.println(str.toLowerCase());
}
}
}
//舉例3:
class Monitor{
private String model;
private double price;
// public boolean equals(Object o){
// if(o instanceof Monitor other){
// if(model.equals(other.model) && price == other.price){
// return true;
// }
// }
// return false;
// }
public boolean equals(Object o){
return o instanceof Monitor other && model.equals(other.model) && price == other.price;
}
}
JDK15中第二次預(yù)覽:
沒有任何更改。
JDK16中轉(zhuǎn)正特性:
在Java16中轉(zhuǎn)正。
1.5 switch表達式
傳統(tǒng)switch
聲明語句的弊端:
- 匹配是自上而下的,如果忘記寫
break
,后面的case
語句不論匹配與否都會執(zhí)行; —>俗稱:case穿透 - 所有的case語句共用一個塊范圍,在不同的case語句定義的變量名不能重復(fù);
- 不能在一個
case
里寫多個執(zhí)行結(jié)果一致的條件; - 整個
switch
不能作為表達式返回值;
//常見錯誤實現(xiàn)
switch(month){
case 3|4|5://3|4|5 用了位運算符,11 | 100 | 101結(jié)果是 111是7
System.out.println("春季");
break;
case 6|7|8://6|7|8用了位運算符,110 | 111 | 1000結(jié)果是1111是15
System.out.println("夏季");
break;
case 9|10|11://9|10|11用了位運算符,1001 | 1010 | 1011結(jié)果是1011是11
System.out.println("秋季");
break;
case 12|1|2://12|1|2 用了位運算符,1100 | 1 | 10 結(jié)果是1111,是15
System.out.println("冬季");
break;
default:
System.out.println("輸入有誤");
}
JDK12中預(yù)覽特性:
-
Java 12將會對
switch
聲明語句進行擴展,使用case L ->
來替代以前的break;
,省去了break
語句,避免了因少寫 break 而出錯。 -
同時將多個
case
合并到一行,顯得簡潔、清晰,也更加優(yōu)雅的表達邏輯分支。 -
為了保持兼容性,case 條件語句中依然可以使用字符
:
,但是同一個 switch 結(jié)構(gòu)里不能混用->
和:
,否則編譯錯誤。
舉例:
Java 12之前
public class SwitchTest {
public static void main(String[] args) {
int numberOfLetters;
Fruit fruit = Fruit.APPLE;
switch (fruit) {
case PEAR:
numberOfLetters = 4;
break;
case APPLE:
case GRAPE:
case MANGO:
numberOfLetters = 5;
break;
case ORANGE:
case PAPAYA:
numberOfLetters = 6;
break;
default:
throw new IllegalStateException("No Such Fruit:" + fruit);
}
System.out.println(numberOfLetters);
}
}
enum Fruit {
PEAR, APPLE, GRAPE, MANGO, ORANGE, PAPAYA;
}
switch
語句如果漏寫了一個 break
,那么邏輯往往就跑偏了,這種方式既繁瑣,又容易出錯。
Java 12中:
public class SwitchTest1 {
public static void main(String[] args) {
Fruit fruit = Fruit.GRAPE;
switch(fruit){
case PEAR -> System.out.println(4);
case APPLE,MANGO,GRAPE -> System.out.println(5);
case ORANGE,PAPAYA -> System.out.println(6);
default -> throw new IllegalStateException("No Such Fruit:" + fruit);
};
}
}
更進一步:
public class SwitchTest2 {
public static void main(String[] args) {
Fruit fruit = Fruit.GRAPE;
int numberOfLetters = switch(fruit){
case PEAR -> 4;
case APPLE,MANGO,GRAPE -> 5;
case ORANGE,PAPAYA -> 6;
default -> throw new IllegalStateException("No Such Fruit:" + fruit);
};
System.out.println(numberOfLetters);
}
}
JDK13中二次預(yù)覽特性:
JDK13中引入了yield
語句,用于返回值。這意味著,switch
表達式(返回值)應(yīng)該使用yield
,switch
語句(不返回值)應(yīng)該使用break
。
yield
和return
的區(qū)別在于:return
會直接跳出當(dāng)前循環(huán)或者方法,而yield
只會跳出當(dāng)前switch
塊。
在以前:
@Test
public void testSwitch1(){
String x = "3";
int i;
switch (x) {
case "1":
i=1;
break;
case "2":
i=2;
break;
default:
i = x.length();
break;
}
System.out.println(i);
}
在JDK13中:
@Test
public void testSwitch2(){
String x = "3";
int i = switch (x) {
case "1" -> 1;
case "2" -> 2;
default -> {
yield 3;
}
};
System.out.println(i);
}
或者
@Test
public void testSwitch3() {
String x = "3";
int i = switch (x) {
case "1":
yield 1;
case "2":
yield 2;
default:
yield 3;
};
System.out.println(i);
}
JDK14中轉(zhuǎn)正特性:
這是JDK 12和JDK 13中的預(yù)覽特性,現(xiàn)在是正式特性了。
JDK17的預(yù)覽特性:switch的模式匹配
舊寫法:
static String formatter(Object o) {
String formatted = "unknown";
if (o instanceof Integer i) {
formatted = String.format("int %d", i);
} else if (o instanceof Long l) {
formatted = String.format("long %d", l);
} else if (o instanceof Double d) {
formatted = String.format("double %f", d);
} else if (o instanceof String s) {
formatted = String.format("String %s", s);
}
return formatted;
}
模式匹配新寫法:
static String formatterPatternSwitch(Object o) {
return switch (o) {
case Integer i -> String.format("int %d", i);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
case String s -> String.format("String %s", s);
default -> o.toString();
};
}
直接在 switch
上支持 Object
類型,這就等于同時支持多種類型,使用模式匹配得到具體類型,大大簡化了語法量,這個功能很實用。
1.6 文本塊
現(xiàn)實問題:
在Java中,通常需要使用String
類型表達 HTML,XML,SQL 或 JSON 等格式的字符串,在進行字符串賦值時需要進行轉(zhuǎn)義和連接操作,然后才能編譯該代碼,這種表達方式難以閱讀并且難以維護。
JDK13的新特性
使用"""
作為文本塊的開始符和結(jié)束符,在其中就可以放置多行的字符串,不需要進行任何轉(zhuǎn)義。因此,文本塊將提高Java程序的可讀性和可寫性。
基本使用:
"""
line1
line2
line3
"""
相當(dāng)于:
"line1\nline2\nline3\n"
或者一個連接的字符串:
"line1\n" +
"line2\n" +
"line3\n"
如果字符串末尾不需要行終止符,則結(jié)束分隔符可以放在最后一行內(nèi)容上。例如:
"""
line1
line2
line3"""
相當(dāng)于
"line1\nline2\nline3"
文本塊可以表示空字符串,但不建議這樣做,因為它需要兩行源代碼:
String empty = """
""";
舉例1:普通文本
原有寫法:
String text1 = "The Sound of silence\n" +
"Hello darkness, my old friend\n" +
"I've come to talk with you again\n" +
"Because a vision softly creeping\n" +
"Left its seeds while I was sleeping\n" +
"And the vision that was planted in my brain\n" +
"Still remains\n" +
"Within the sound of silence";
System.out.println(text1);
使用新特性:
String text2 = """
The Sound of silence
Hello darkness, my old friend
I've come to talk with you again
Because a vision softly creeping
Left its seeds while I was sleeping
And the vision that was planted in my brain
Still remains
Within the sound of silence
""";
System.out.println(text2);
舉例2:HTML語句
<html>
<body>
<p>Hello, JDK</p>
</body>
</html>
將其復(fù)制到Java的字符串中,會展示成以下內(nèi)容:
"<html>\n" +
" <body>\n" +
" <p>Hello, JDK</p>\n" +
" </body>\n" +
"</html>\n";
即被自動進行了轉(zhuǎn)義,這樣的字符串看起來不是很直觀,在JDK 13中:
"""
<html>
<body>
<p>Hello, world</p>
</body>
</html>
""";
舉例3:SQL語句
select employee_id,last_name,salary,department_id
from employees
where department_id in (40,50,60)
order by department_id asc
原有方式:
String sql = "SELECT id,NAME,email\n" +
"FROM customers\n" +
"WHERE id > 4\n" +
"ORDER BY email DESC";
使用新特性:
String sql1 = """
SELECT id,NAME,email
FROM customers
WHERE id > 4
ORDER BY email DESC
""";
舉例4:JSON字符串
原有方式:
String myJson = "{\n" +
" \"name\":\"Song Hongkang\",\n" +
" \"address\":\"xy5462.blog.csdn.net\",\n" +
" \"email\":\"shkstart@126.com\"\n" +
"}";
System.out.println(myJson);
使用新特性:
String myJson1 = """
{
"name":"Song Hongkang",
"address":"xy5462.blog.csdn.net",
"email":"shkstart@126.com"
}""";
System.out.println(myJson1);
JDK14中二次預(yù)覽特性
JDK14的版本主要增加了兩個escape
sequences
,分別是 \ <line-terminator>
與\s escape sequence
。
舉例:
public class Feature05 {
//jdk14新特性
@Test
public void test5(){
String sql1 = """
SELECT id,NAME,email
FROM customers
WHERE id > 4
ORDER BY email DESC
""";
System.out.println(sql1);
// \:取消換行操作
// \s:表示一個空格
String sql2 = """
SELECT id,NAME,email \
FROM customers\s\
WHERE id > 4 \
ORDER BY email DESC
""";
System.out.println(sql2);
}
}
JDK15中功能轉(zhuǎn)正
1.7 Record
背景
早在2019年2月份,Java 語言架構(gòu)師 Brian Goetz,曾寫文抱怨“Java太啰嗦
”或有太多的“繁文縟節(jié)”。他提到:開發(fā)人員想要創(chuàng)建純數(shù)據(jù)載體類(plain data carriers)通常都必須編寫大量低價值、重復(fù)的、容易出錯的代碼。如:構(gòu)造函數(shù)、getter/setter
、equals()
、hashCode()
以及toString()
等。
以至于很多人選擇使用IDE的功能來自動生成這些代碼。還有一些開發(fā)會選擇使用一些第三方類庫,如Lombok
等來生成這些方法。
JDK14中預(yù)覽特性:神說要用record,于是就有了。 實現(xiàn)一個簡單的數(shù)據(jù)載體類,為了避免編寫:構(gòu)造函數(shù),訪問器,equals()
,hashCode ()
,toString ()
等,Java 14推出 record。
record
是一種全新的類型,它本質(zhì)上是一個 final
類,同時所有的屬性都是 final
修飾,它會自動編譯出 public get
、hashcode
、equals
、toString
、構(gòu)造器等結(jié)構(gòu),減少了代碼編寫量。
具體來說:當(dāng)你用record
聲明一個類時,該類將自動擁有以下功能:
- 獲取成員變量的簡單方法,比如下面示例中的
name()
和partner()
。注意區(qū)別于我們平常getter()
的寫法。 - 一個
equals
方法的實現(xiàn),執(zhí)行比較時會比較該類的所有成員屬性。 - 重寫
hashCode()
方法。 - 一個可以打印該類所有成員屬性的
toString()
方法。 - 只有一個構(gòu)造方法。
此外:
-
還可以在
record
聲明的類中定義 靜態(tài)字段、靜態(tài)方法、構(gòu)造器 或 實例方法。 -
不能 在
record
聲明的類中 定義實例字段;類不能聲明為abstract;不能聲明顯式的父類等。
舉例1(舊寫法):
class Point {
private final int x;
private final int y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
int x() {
return x;
}
int y() {
return y;
}
public boolean equals(Object o) {
if (!(o instanceof Point)) return false;
Point other = (Point) o;
return other.x == x && other.y == y;
}
public int hashCode() {
return Objects.hash(x, y);
}
@Override
public String toString() {
return "Point{" +
"x=" + x +
", y=" + y +
'}';
}
}
舉例1(新寫法):
record Point(int x, int y) { }
舉例1:
public record Dog(String name, Integer age) {
}
public class Java14Record {
public static void main(String[] args) {
Dog dog1 = new Dog("牧羊犬", 1);
Dog dog2 = new Dog("田園犬", 2);
Dog dog3 = new Dog("哈士奇", 3);
System.out.println(dog1);
System.out.println(dog2);
System.out.println(dog3);
}
}
舉例2:
public class Feature07 {
@Test
public void test1(){
//測試構(gòu)造器
Person p1 = new Person("羅密歐",new Person("zhuliye",null));
//測試toString()
System.out.println(p1);
//測試equals():
Person p2 = new Person("羅密歐",new Person("zhuliye",null));
System.out.println(p1.equals(p2));
//測試hashCode()和equals()
HashSet<Person> set = new HashSet<>();
set.add(p1);
set.add(p2);
for (Person person : set) {
System.out.println(person);
}
//測試name()和partner():類似于getName()和getPartner()
System.out.println(p1.name());
System.out.println(p1.partner());
}
@Test
public void test2(){
Person p1 = new Person("zhuyingtai");
System.out.println(p1.getNameInUpperCase());
Person.nation = "CHN";
System.out.println(Person.showNation());
}
}
public record Person(String name,Person partner) {
//還可以聲明靜態(tài)的屬性、靜態(tài)的方法、構(gòu)造器、實例方法
public static String nation;
public static String showNation(){
return nation;
}
public Person(String name){
this(name,null);
}
public String getNameInUpperCase(){
return name.toUpperCase();
}
//不可以聲明非靜態(tài)的屬性
// private int id;//報錯
}
//不可以將record定義的類聲明為abstract的
//abstract record Order(){
//
//}
//不可以給record定義的類聲明顯式的父類(非Record類)
//record Order() extends Thread{
//
//}
JDK15中第二次預(yù)覽特性
JDK16中轉(zhuǎn)正特性
最終到JDK16中轉(zhuǎn)正。
記錄不適合哪些場景
record
的設(shè)計目標(biāo)是提供一種將數(shù)據(jù)建模為數(shù)據(jù)的好方法。它也不是 JavaBeans 的直接替代品,因為 record的方法不符合 JavaBeans 的 get 標(biāo)準(zhǔn)。另外 JavaBeans 通常是可變的,而記錄是不可變的。盡管它們的用途有點像,但記錄并不會以某種方式取代 JavaBean。
1.8 密封類
背景:
在 Java 中如果想讓一個類不能被繼承和修改,這時我們應(yīng)該使用 final
關(guān)鍵字對類進行修飾。不過這種要么可以繼承,要么不能繼承的機制不夠靈活,有些時候我們可能想讓某個類可以被某些類型繼承,但是又不能隨意繼承,是做不到的。Java 15 嘗試解決這個問題,引入了 sealed
類,被 sealed
修飾的類可以指定子類。這樣這個類就只能被指定的類繼承。
JDK15的預(yù)覽特性:
通過密封的類和接口來限制超類的使用,密封的類和接口限制其它可能繼承或?qū)崿F(xiàn)它們的其它類或接口。
具體使用:
-
使用修飾符
sealed
,可以將一個類聲明為密封類。密封的類使用保留關(guān)鍵字permits
列出可以直接擴展(即extends)它的類。 -
sealed
修飾的類的機制具有傳遞性,它的子類必須使用指定的關(guān)鍵字進行修飾,且只能是final
、sealed
、non-sealed
三者之一。
舉例:
public abstract sealed class Shape permits Circle, Rectangle, Square {...}
public final class Circle extends Shape {...} //final表示Circle不能再被繼承了
public sealed class Rectangle extends Shape permits TransparentRectangle, FilledRectangle {...}
public final class TransparentRectangle extends Rectangle {...}
public final class FilledRectangle extends Rectangle {...}
public non-sealed class Square extends Shape {...} //non-sealed表示可以允許任何類繼承
JDK16二次預(yù)覽特性
JDK17中轉(zhuǎn)正特性
2. API的變化
2.1 Optional類
JDK8的新特性
到目前為止,臭名昭著的空指針異常是導(dǎo)致Java應(yīng)用程序失敗的最常見原因。以前,為了解決空指針異常,Google在著名的Guava項目引入了Optional類,通過檢查空值的方式避免空指針異常。受到Google的啟發(fā),Optional類已經(jīng)成為Java 8類庫的一部分。
Optional<T>
類(java.util.Optional) 是一個容器類,它可以保存類型T的值,代表這個值存在。或者僅僅保存null,表示這個值不存在。如果值存在,則isPresent()
方法會返回true
,調(diào)用get()
方法會返回該對象。
Optional提供很多有用的方法,這樣我們就不用顯式進行空值檢測。
-
創(chuàng)建Optional類對象的方法:
-
static <T> Optional<T> empty()
:用來創(chuàng)建一個空的Optional實例-
static <T> Optional<T> of(T value)
:用來創(chuàng)建一個Optional實例,value必須非空 -
static <T> Optional<T> ofNullable(T value)
:用來創(chuàng)建一個Optional實例,value可能是空,也可能非空
-
-
判斷Optional容器中是否包含對象:
-
boolean isPresent()
: 判斷Optional容器中的值是否存在 -
void ifPresent(Consumer<? super T> consumer)
:判斷Optional容器中的值是否存在,如果存在,就對它進行Consumer指定的操作,如果不存在就不做
-
-
獲取Optional容器的對象:
-
T get()
: 如果調(diào)用對象包含值,返回該值。否則拋異常。T get()
與of(T value)
配合使用 -
T orElse(T other)
:orElse(T other)
與ofNullable(T value)
配合使用,如果Optional
容器中非空,就返回所包裝值,如果為空,就用orElse(T other)
other指定的默認值(備胎)代替 -
T orElseGet(Supplier<? extends T> other)
:如果Optional
容器中非空,就返回所包裝值,如果為空,就用Supplier
接口的Lambda表達式提供的值代替 -
T orElseThrow(Supplier<? extends X> exceptionSupplier)
:如果Optional
容器中非空,就返回所包裝值,如果為空,就拋出你指定的異常類型代替原來的NoSuchElementException
舉例:
import java.util.Optional;
import org.junit.Test;
public class TestOptional {
@Test
public void test1(){
String str = "hello";
Optional<String> opt = Optional.of(str);
System.out.println(opt);
}
@Test
public void test2(){
Optional<String> opt = Optional.empty();
System.out.println(opt);
}
@Test
public void test3(){
String str = null;
Optional<String> opt = Optional.ofNullable(str);
System.out.println(opt);
}
@Test
public void test4(){
String str = "hello";
Optional<String> opt = Optional.of(str);
String string = opt.get();
System.out.println(string);
}
@Test
public void test5(){
String str = null;
Optional<String> opt = Optional.ofNullable(str);
// System.out.println(opt.get());//java.util.NoSuchElementException: No value present
}
@Test
public void test6(){
String str = "hello";
Optional<String> opt = Optional.ofNullable(str);
String string = opt.orElse("csdn");
System.out.println(string);
}
@Test
public void test7(){
String str = null;
Optional<String> opt = Optional.ofNullable(str);
String string = opt.orElseGet(String::new);
System.out.println(string);
}
@Test
public void test8(){
String str = null;
Optional<String> opt = Optional.ofNullable(str);
String string = opt.orElseThrow(()->new RuntimeException("值不存在"));
System.out.println(string);
}
@Test
public void test9(){
String str = "Hello1";
Optional<String> opt = Optional.ofNullable(str);
//判斷是否是純字母單詞,如果是,轉(zhuǎn)為大寫,否則保持不變
String result = opt.filter(s->s.matches("[a-zA-Z]+"))
.map(s -> s.toUpperCase()).orElse(str);
System.out.println(result);
}
}
這是JDK9-11的新特性
新增方法 | 描述 | 新增的版本 |
---|---|---|
boolean isEmpty() |
判斷value是否為空 | JDK 11 |
ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) |
value非空,執(zhí)行參數(shù)1功能;如果value為空,執(zhí)行參數(shù)2功能 | JDK 9 |
Optional<T> or(Supplier<? extends Optional<? extends T>> supplier) |
value非空,返回對應(yīng)的Optional;value為空,返回形參封裝的Optional | JDK 9 |
Stream<T> stream() |
value非空,返回僅包含此value的Stream;否則,返回一個空的Stream | JDK 9 |
T orElseThrow() |
value非空,返回value;否則拋異常NoSuchElementException | JDK 10 |
2.2 String存儲結(jié)構(gòu)和API變更
這是JDK9的新特性。
產(chǎn)生背景:
Motivation
The current implementation of the String class stores characters in a char array, using two bytes (sixteen bits) for each character. Data gathered from many different applications indicates that strings are a major component of heap usage and, moreover, that most String objects contain only Latin-1 characters. Such characters require only one byte of storage, hence half of the space in the internal char arrays of such String objects is going unused.背景
String類的當(dāng)前實現(xiàn)將字符存儲在字符數(shù)組中,每個字符使用兩個字節(jié)(16位)。從許多不同的應(yīng)用程序收集的數(shù)據(jù)表明,字符串是堆使用的主要組成部分,而且,大多數(shù)String對象只包含Latin-1字符。這些字符只需要一個字節(jié)的存儲空間,因此這些String對象的內(nèi)部字符數(shù)組中有一半的空間是未使用的。
使用說明:
Description
We propose to change the internal representation of the String class from a UTF-16 char array to a byte array plus an encoding-flag field. The new String class will store characters encoded either as ISO-8859-1/Latin-1 (one byte per character), or as UTF-16 (two bytes per character), based upon the contents of the string. The encoding flag will indicate which encoding is used.
描述:
我們建議將String類的內(nèi)部表示形式從UTF-16字符數(shù)組更改為字節(jié)數(shù)組加上編碼標(biāo)志字段。新的String類將根據(jù)字符串的內(nèi)容存儲編碼為ISO-8859-1/Latin-1(每個字符一個字節(jié))或UTF-16(每個字符兩個字節(jié))的字符。編碼標(biāo)志將指示使用哪種編碼
結(jié)論:String
再也不用 char[]
來存儲啦,改成了 byte[]
加上編碼標(biāo)記,節(jié)約了一些空間。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
@Stable
private final byte[] value;
...
}
拓展:StringBuffer
與 StringBuilder
那StringBuffer 和 StringBuilder 是否仍無動于衷呢?
String-related classes such as AbstractStringBuilder, StringBuilder, and StringBuffer will be updated to use the same representation, as will the HotSpot VM’s intrinsic string operations.
翻譯
與字符串相關(guān)的類,如AbstractStringBuilder、StringBuilder和StringBuffer,將被更新為使用相同的表示,HotSpot VM的固有字符串操作也是如此。
JDK11新特性:新增了一系列字符串處理方法
描述 | 舉例 |
---|---|
判斷字符串是否為空白 |
" ".isBlank() ; // true |
去除首尾空白 |
" Javastack ".strip(); // “Javastack” |
去除尾部空格 |
" Javastack ".stripTrailing(); // " Javastack" |
去除首部空格 |
" Javastack ".stripLeading(); // "Javastack " |
復(fù)制字符串 |
"Java".repeat(3); // “JavaJavaJava” |
行數(shù)統(tǒng)計 |
"A\nB\nC".lines().count(); // 3 |
JDK12新特性:String 實現(xiàn)了 Constable 接口
String源碼:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence,Constable, ConstantDesc {
java.lang.constant.Constable
接口定義了抽象方法:
public interface Constable {
Optional<? extends ConstantDesc> describeConstable();
}
Java 12 String 的實現(xiàn)源碼:
/**
* Returns an {@link Optional} containing the nominal descriptor for this
* instance, which is the instance itself.
*
* @return an {@link Optional} describing the {@linkplain String} instance
* @since 12
*/
@Override
public Optional<String> describeConstable() {
return Optional.of(this);
}
很簡單,其實就是調(diào)用 Optional.of
方法返回一個 Optional 類型。
舉例:
private static void testDescribeConstable() {
String name = "Java高級工程師";
Optional<String> optional = name.describeConstable();
System.out.println(optional.get());
}
結(jié)果輸出:
Java高級工程師
JDK12新特性:String新增方法
String的transform(Function)
var result = "foo".transform(input -> input + " bar");
System.out.println(result); //foo bar
或者
var result = "foo".transform(input -> input + " bar").transform(String::toUpperCase)
System.out.println(result); //FOO BAR
對應(yīng)的源碼:
/**
* This method allows the application of a function to {@code this}
* string. The function should expect a single String argument
* and produce an {@code R} result.
* @since 12
*/
public <R> R transform(Function<? super String, ? extends R> f) {
return f.apply(this);
}
在某種情況下,該方法應(yīng)該被稱為map()
。
舉例:
private static void testTransform() {
System.out.println("======test java 12 transform======");
List<String> list1 = List.of("Java", " Python", " C++ ");
List<String> list2 = new ArrayList<>();
list1.forEach(element -> list2.add(element.transform(String::strip)
.transform(String::toUpperCase)
.transform((e) -> "Hi," + e))
);
list2.forEach(System.out::println);
}
結(jié)果輸出:
======test java 12 transform======
Hi,JAVA
Hi,PYTHON
Hi,C++
如果使用Java 8的Stream特性,可以如下實現(xiàn):
private static void testTransform1() {
System.out.println("======test before java 12 ======");
List<String> list1 = List.of("Java ", " Python", " C++ ");
Stream<String> stringStream = list1.stream().map(element -> element.strip()).map(String::toUpperCase).map(element -> "Hello," + element);
List<String> list2 = stringStream.collect(Collectors.toList());
list2.forEach(System.out::println);
}
2.3 JDK17:標(biāo)記刪除Applet API
Applet API 提供了一種將 Java AWT
/Swing
控件嵌入到瀏覽器網(wǎng)頁中的方法。不過,目前 Applet 已經(jīng)被淘汰。大部分人可能壓根就沒有用過 Applet。
Applet API 實際上是無用的,因為所有 Web 瀏覽器供應(yīng)商都已刪除或透露計劃放棄對 Java 瀏覽器插件的支持。Java 9 的時候,Applet API 已經(jīng)被標(biāo)記為過時,Java 17 的時候終于標(biāo)記為刪除了。
具體如下:
java.applet.Applet
java.applet.AppletStub
java.applet.AppletContext
java.applet.AudioClip
javax.swing.JApplet
java.beans.AppletInitializer
3. 其它結(jié)構(gòu)變化
3.1 JDK9:UnderScore(下劃線)使用的限制
在java 8 中,標(biāo)識符可以獨立使用“_”來命名:
String _ = "hello";
System.out.println(_);
但是,在java 9 中規(guī)定“_”不再可以單獨命名標(biāo)識符了,如果使用,會報錯:
3.2 JDK11:更簡化的編譯運行程序
看下面的代碼。
// 編譯
javac JavaStack.java
// 運行
java JavaStack
我們的認知里,要運行一個 Java 源代碼必須先編譯,再運行。而在 Java 11 版本中,通過一個 java 命令就直接搞定了,如下所示:
java JavaStack.java
注意點:
- 執(zhí)行源文件中的第一個類,第一個類必須包含主方法。
3.3 GC方面新特性
GC是Java主要優(yōu)勢之一。 然而,當(dāng)GC停頓太長,就會開始影響應(yīng)用的響應(yīng)時間。隨著現(xiàn)代系統(tǒng)中內(nèi)存不斷增長,用戶和程序員希望JVM能夠以高效的方式充分利用這些內(nèi)存, 并且無需長時間的GC暫停時間。
3.3.1 G1 GC
JDK9以后默認的垃圾回收器是 G1GC。
JDK10 : 為G1提供并行的 Full GC
G1最大的亮點就是可以盡量的避免full gc。但畢竟是“盡量”,在有些情況下,G1就要進行full gc了,比如如果它無法足夠快的回收內(nèi)存的時候,它就會強制停止所有的應(yīng)用線程然后清理。
在Java10之前,一個單線程版的標(biāo)記-清除-壓縮算法被用于full gc。為了盡量減少full gc帶來的影響,在Java10中,就把之前的那個單線程版的標(biāo)記-清除-壓縮的full gc算法改成了 支持多個線程 同時full gc。這樣也算是減少了full gc所帶來的停頓,從而提高性能。
可以通過-XX:ParallelGCThreads
參數(shù)來指定用于并行GC的線程數(shù)。
JDK12:可中斷的 G1 Mixed GC
JDK12:增強G1,自動返回未用堆內(nèi)存給操作系統(tǒng)
3.3.2 Shenandoah GC
JDK12:Shenandoah GC:低停頓時間的GC
Shenandoah 垃圾回收器是 Red Hat 在 2014 年宣布進行的一項垃圾收集器研究項目 Pauseless GC 的實現(xiàn),旨在針對 JVM 上的內(nèi)存收回實現(xiàn)低停頓的需求。
據(jù) Red Hat 研發(fā) Shenandoah 團隊對外宣稱,Shenandoah 垃圾回收器的暫停時間與堆大小無關(guān),這意味著無論將堆設(shè)置為 200 MB 還是 200 GB,都將擁有一致的系統(tǒng)暫停時間,不過實際使用性能將取決于實際工作堆的大小和工作負載。
Shenandoah GC 主要目標(biāo)是 99.9% 的暫停小于 10ms,暫停與堆大小無關(guān) 等。
這是一個實驗性功能,不包含在默認(Oracle)的OpenJDK版本中。
Shenandoah開發(fā)團隊在實際應(yīng)用中的測試數(shù)據(jù):
JDK15:Shenandoah垃圾回收算法轉(zhuǎn)正
Shenandoah垃圾回收算法終于從實驗特性轉(zhuǎn)變?yōu)楫a(chǎn)品特性,這是一個從 JDK 12 引入的回收算法,該算法通過與正在運行的 Java 線程同時進行疏散工作來減少 GC 暫停時間。Shenandoah 的暫停時間與堆大小無關(guān),無論堆棧是 200 MB 還是 200 GB,都具有相同的一致暫停時間。
Shenandoah在JDK12被作為experimental
引入,在JDK15變?yōu)?code>Production;之前需要通過-XX:+UnlockExperimentalVMOptions
-XX:+UseShenandoahGC
來啟用,現(xiàn)在只需要-XX:+UseShenandoahGC
即可啟用
3.3.3 革命性的 ZGC
JDK11:引入革命性的 ZGC
ZGC,這應(yīng)該是JDK11最為矚目的特性,沒有之一。
ZGC是一個并發(fā)、基于region、壓縮型的垃圾收集器。
ZGC的設(shè)計目標(biāo)是:支持TB級內(nèi)存容量,暫停時間低(<10ms),對整個程序吞吐量的影響小于15%。 將來還可以擴展實現(xiàn)機制,以支持不少令人興奮的功能,例如多層堆(即熱對象置于DRAM和冷對象置于NVMe閃存),或壓縮堆。
JDK13:ZGC:將未使用的堆內(nèi)存歸還給操作系統(tǒng)
JDK14:ZGC on macOS和windows
-
JDK14之前,ZGC僅Linux才支持?,F(xiàn)在mac或Windows上也能使用ZGC了,示例如下:
-XX:+UnlockExperimentalVMOptions -XX:+UseZGC
-
ZGC與Shenandoah目標(biāo)高度相似,在盡可能對吞吐量影響不大的前提下,實現(xiàn)在任意堆內(nèi)存大小下都可以把垃圾收集的停頓時間限制在
十毫秒以內(nèi)
的低延遲。
JDK15:ZGC 功能轉(zhuǎn)正
ZGC是Java 11引入的新的垃圾收集器,經(jīng)過了多個實驗階段,自此終于成為正式特性。
但是這并不是替換默認的GC,默認的GC仍然還是G1;之前需要通過-XX:+UnlockExperimentalVMOptions
、 -XX:+UseZGC
來啟用ZGC,現(xiàn)在只需要-XX:+UseZGC
就可以。相信不久的將來它必將成為默認的垃圾回收器。
ZGC的性能已經(jīng)相當(dāng)亮眼,用“令人震驚、革命性”來形容,不為過。未來將成為服務(wù)端、大內(nèi)存、低延遲應(yīng)用的首選垃圾收集器。
怎么形容Shenandoah和ZGC的關(guān)系呢?異同點大概如下:
- 相同點:性能幾乎可認為是相同的
- 不同點:ZGC是Oracle JDK的,根正苗紅。而Shenandoah只存在于OpenJDK中,因此使用時需注意你的JDK版本
JDK16:ZGC 并發(fā)線程處理
在線程的堆棧處理過程中,總有一個制約因素就是safepoints
。在safepoints這個點,Java的線程是要暫停執(zhí)行的,從而限制了GC的效率。
回顧:
我們都知道,在之前,需要 GC 的時候,為了進行垃圾回收,需要所有的線程都暫停下來,這個暫停的時間我們稱為 Stop The World。
而為了實現(xiàn) STW 這個操作, JVM 需要為每個線程選擇一個點停止運行,這個點就叫做安全點(Safepoints)。
而 ZGC的并發(fā)線程堆棧處理可以保證Java線程可以在GC safepoints的同時可以并發(fā)執(zhí)行。它有助于提高所開發(fā)的Java軟件應(yīng)用程序的性能和效率。
4. 小結(jié)與展望
隨著云計算和 AI 等技術(shù)浪潮,當(dāng)前的計算模式和場景正在發(fā)生翻天覆地的變化,不僅對 Java 的發(fā)展速度提出了更高要求,也深刻影響著 Java 技術(shù)的發(fā)展方向。傳統(tǒng)的大型企業(yè)或互聯(lián)網(wǎng)應(yīng)用,正在被云端、容器化應(yīng)用、模塊化的微服務(wù)甚至是函數(shù)(FaaS, Function-as-a-Service)所替代。
Java 需要在新的計算場景下,改進開發(fā)效率。 比如,Java 代碼雖然進行了一些類型推斷等改進,更易用的集合 API 等,但仍然給開發(fā)者留下了過于刻板、形式主義的印象,這是一個長期的改進方向。文章來源:http://www.zghlxwxcb.cn/news/detail-463371.html
Java雖然標(biāo)榜面向?qū)ο缶幊?,卻毫不顧忌的加入面向接口編程思想
,又扯出匿名對象
的概念,每增加一個新的東西,對Java的根本(面向?qū)ο笏枷耄┑囊淮螞_擊。文章來源地址http://www.zghlxwxcb.cn/news/detail-463371.html
到了這里,關(guān)于JDK8-JDK17中的新特性(var類型推斷、模式匹配、Record、密封類)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!