概述
- 什麼是語法糖
語法糖(Syntactic sugar),也譯爲糖衣語法,是由英國計算機科學家彼得·蘭丁發明的一個術語,指計算機語言中添加的某種語法,這種語法對語言的功能沒有影響,但是更方便程序員使用。
- 能夠帶來的好處
語法糖讓程序更加簡潔,有更高的可讀性
- 有哪些語法糖
- 自動拆箱、裝箱
- 泛型擦除
- 不定長參數
- 迭代器
- 枚舉
- switch支持枚舉和字符串
- 內部類
- try-with-resources
- lambda
自動拆箱、裝箱
- Java是面向對象編程(萬物皆對象)
- 對象即需要new出來的,但是想想基本數據類型(int/double/boolean...),並不需要去new
- why?
- 爲了方便去使用,Java對這些基本數據類型提供了裝箱類型
public class Demo {
public static void main(String[] args) {
// 自動裝箱
// 相當於代碼:Integer i = Integer.valueOf(1);
Integer i = 1;
// 自動拆箱
// 相當於代碼:int j = i.intValue();
int j = i;
System.out.println(i + j);
}
}
// # 編譯
// javac Demo.java
// # 查看字節碼
// javap -c Demo
Compiled from "Demo.java"
public class Demo extends java.lang.Object{
public Demo();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_1
1: invokestatic #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
4: astore_1
5: aload_1
6: invokevirtual #3; //Method java/lang/Integer.intValue:()I
9: istore_2
10: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream;
13: aload_1
14: invokevirtual #3; //Method java/lang/Integer.intValue:()I
17: iload_2
18: iadd
19: inv// okevirtual #5; //Method java/io/PrintStream.println:(I)V
22: return
}
- 思考
- Integer的裝箱類型能否用==比較?
- Integer類型的valueof方法,-128~127區間採用緩存,這部分值是可以用==比較的,區間外的是重新創建的對象,用==比較的只是引用
- ==、equals的區別?
- 爲何阿里規範中心POJO以及RPC的入參、返回值的類屬性定義都要用用包裝數據?
- 有時候,接收到的值可能事null類型,如果用的是基本類型,則會在拆箱的過程中發生NPE
- Integer的裝箱類型能否用==比較?
泛型擦除
- Java的泛型,其實是
僞泛型
,泛型僅僅存在於編碼期間,供編譯器進行類型檢測,編譯後,會被擦除。 - 運行時,通過類型強制轉換來實現的。
- 泛型是JDK1.5之後引入
// // 1. 源碼
import java.util.*;
public class Demo {
public static void main(String[] args) {
List<String> a = new ArrayList<String>();
// 並不能編譯通過
// a.add(111);
a.add("test");
String temp = a.get(0);
System.out.println(temp);
}
}
2. 通過jd-gui反編譯後得到
import java.util.ArrayList;
public class Demo {
public static void main(String[] paramArrayOfString) {
ArrayList arrayList = new ArrayList();
arrayList.add("test");
// 通過類型強制轉換來獲取List中的對象,這裏已經由編碼期的泛型來保證了類型安全
String str = (String)arrayList.get(0);
System.out.println(str);
}
}
// 3. 如何證明泛型在編譯時被擦除,運行時並不需要這個
import java.util.*;
public class Demo {
public static void main(String[] args) {
List<Integer> a = new ArrayList<Integer>();
List<String> b = new ArrayList<String>();
// 並不能編譯通過
// a.addAll(b);
Class c1 = a.getClass();
Class c2 = b.getClass();
// 打印:
// class java.util.ArrayList
// true
// 運行時獲取a和b的類型
System.out.println(c1);
System.out.println(c1 == c2);
}
}
枚舉
- 枚舉其實就是一個Java類,繼承了
java.lang.Enum
,並且其本身是不允許被繼承的 - 用
enum
修飾的
// 枚舉源碼
public enum Demo {
ENUM_A,
ENUM_B
}
// 2. 編譯後,用javap查看字節碼文件
// javac Demo.java
// javap -c Demo
Compiled from "Demo.java"
// 此處可以看出來枚舉類,其實就是一個普普通通的Java類
// 首先,它是被final修飾的,所有不能被繼承
// 其次,默認繼承了java.lang.Enum,幷包含泛型,泛型類型爲其本身
public final class Demo extends java.lang.Enum<Demo> {
// 有兩個靜態定義的枚舉值
public static final Demo ENUM_A;
public static final Demo ENUM_B;
public static Demo[] values();
Code:
0: getstatic #1 // Field $VALUES:[LDemo;
3: invokevirtual #2 // Method "[LDemo;".clone:()Ljava/lang/Object;
6: checkcast #3 // class "[LDemo;"
9: areturn
public static Demo valueOf(java.lang.String);
Code:
0: ldc #4 // class Demo
2: aload_0
3: invokestatic #5 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
6: checkcast #4 // class Demo
9: areturn
static {};
Code:
// new一個對象,類型事Demo
0: new #4 // class Demo
3: dup
// 將常量壓入棧
4: ldc #7 // String ENUM_A
// 定義變量0
6: iconst_0
// 調用構造器,注意入參是兩個(上面入棧的兩個)
7: invokespecial #8 // Method "<init>":(Ljava/lang/String;I)V
// 賦值
10: putstatic #9 // Field ENUM_A:LDemo;
13: new #4 // class Demo
16: dup
17: ldc #10 // String ENUM_B
19: iconst_1
20: invokespecial #8 // Method "<init>":(Ljava/lang/String;I)V
23: putstatic #11 // Field ENUM_B:LDemo;
26: iconst_2
// 創建數組,類型是o
27: anewarray #4 // class Demo
// 壓棧
30: dup
// 第0位
31: iconst_0
// 取值
32: getstatic #9 // Field ENUM_A:LDemo;
// 存入對應的數組元素中
35: aastore
36: dup
37: iconst_1
38: getstatic #11 // Field ENUM_B:LDemo;
41: aastore
42: putstatic #1 // Field $VALUES:[LDemo;
45: return
}
try-with-resources
- 以前在寫訪問數據庫、流編程,通常需要將資源關閉
- 以前使用try-catch-finally的寫法,在finally中將資源關閉
- 這時候就要注意很多細節上的問題
- 資源是否打開,是否存在
- 關閉的過程中如何處理異常
- finally中不能寫return
- 自從JDK7之後,支持
try-with-resources
的寫法,下面簡單介紹一下兩種寫法的區別:
// 1. try-catch-finally寫法:
public static void copy(String src) {
InputStream in = null;
try {
System.out.println(in);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
// 2. try-with-resources寫法:
public static void copy(String src) {
try (InputStream in = new FileInputStream(src)) {
System.out.println(in);
} catch (IOException e) {
e.printStackTrace();
}
}
- 很明顯,第二種寫法較第一種,清晰、明瞭。
- 下面進行源碼分析:
- 查看JDK7版本中的
FileInputStream
源碼
// 1. FileInputStream源碼
public class FileInputStream extends InputStream {
// ...
public void close() throws IOException {
synchronized (closeLock) {
if (closed) {
return;
}
closed = true;
}
if (channel != null) {
channel.close();
}
fd.closeAll(new Closeable() {
public void close() throws IOException {
close0();
}
});
}
}
// 2. FileInputStream父類InputStream源碼
public abstract class InputStream implements Closeable {
// ...
public void close() throws IOException {}
}
// 3. InputStream實現的接口Closeable的源碼
public interface Closeable extends AutoCloseable {
public void close() throws IOException;
}
// 4. AutoCloseable的源碼
/**
* ...
* @since 1.7
*/
public interface AutoCloseable {
void close() throws Exception;
}
- 可見最終是實現了一個JDK7才提供的接口
AutoCloseable
- 我們再去查看一下
try-with-resources
寫法的字節碼文件
// javap -c Demo
Compiled from "Demo.java"
public class Demo {
public Demo();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void copy(java.lang.String);
Code:
0: new #2 // class java/io/FileInputStream
3: dup
4: aload_0
5: invokespecial #3 // Method java/io/FileInputStream."<init>":(Ljava/lang/String;)V
8: astore_1
9: aconst_null
10: astore_2
11: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
14: aload_1
15: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
18: aload_1
19: ifnull 89
22: aload_2
23: ifnull 42
26: aload_1
27: invokevirtual #6 // Method java/io/InputStream.close:()V
30: goto 89
33: astore_3
34: aload_2
35: aload_3
36: invokevirtual #8 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
39: goto 89
42: aload_1
43: invokevirtual #6 // Method java/io/InputStream.close:()V
46: goto 89
49: astore_3
50: aload_3
51: astore_2
52: aload_3
53: athrow
54: astore 4
56: aload_1
57: ifnull 86
60: aload_2
61: ifnull 82
64: aload_1
65: invokevirtual #6 // Method java/io/InputStream.close:()V
68: goto 86
71: astore 5
73: aload_2
74: aload 5
76: invokevirtual #8 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
79: goto 86
82: aload_1
83: invokevirtual #6 // Method java/io/InputStream.close:()V
86: aload 4
88: athrow
89: goto 97
92: astore_1
93: aload_1
94: invokevirtual #10 // Method java/io/IOException.printStackTrace:()V
97: return
Exception table:
from to target type
26 30 33 Class java/lang/Throwable
11 18 49 Class java/lang/Throwable
11 18 54 any
64 68 71 Class java/lang/Throwable
49 56 54 any
0 89 92 Class java/io/IOException
}
- 思考
- 問題一
- 在第一種方案中,如果try塊中拋出了異常,該異常時可以被正常拋出記錄的
- 如果在finally中產生異常,然後拋出,那該異常也可以被正常記錄
- 如果try中先出現異常,finally中也會被正常執行,但是如果finally中也出現了異常並拋出,try中的異常還能被捕獲麼?
- 關於異常
- jdk7之前,如果不做任何處理,try中的異常就會被忽略掉,無法捕獲
- jdk7之後,引入了“可被抑制”的異常,finally中產生的異常,可以獲取到try中的異常
- jdk7之前,需要實現自己的異常類
- jdk7之後,已經對Throwable類進行了修改以支持這種情況。在java7中爲Throwable類增加addSuppressed方法。當一個異常被拋出的時候,可能有其他異常因爲該異常而被抑制住,從而無法正常拋出。這時可以通過addSuppressed方法把這些被抑制的方法記錄下來。被抑制的異常會出現在拋出的異常的堆棧信息中,也可以通過getSuppressed方法來獲取這些異常。這樣做的好處是不會丟失任何異常,方便開發人員進行調試。
- 可以查看一下上面例子反編譯後的源碼:
import java.io.*; public class Demo { public Demo() { } public static void copy(String s) { FileInputStream fileinputstream; Throwable throwable; fileinputstream = new FileInputStream(s); throwable = null; try { System.out.println(fileinputstream); } catch(Throwable throwable2) { throwable = throwable2; throw throwable2; } if(fileinputstream != null) if(throwable != null) try { fileinputstream.close(); } catch(Throwable throwable1) { // 這裏就是將catch中的異常,放在可能阻斷異常拋出的異常中,然後統一在後面拋出異常,這樣throwable1就在throwable的異常棧中了 throwable.addSuppressed(throwable1); } else fileinputstream.close(); // 這一塊,源碼是finally塊,由於反編譯工具的問題,導致看到break語句 // 其實Java中的break也是支持跳出的指定label的 break MISSING_BLOCK_LABEL_97; Exception exception; exception; if(fileinputstream != null) if(throwable != null) try { fileinputstream.close(); } catch(Throwable throwable3) { throwable.addSuppressed(throwable3); } else fileinputstream.close(); throw exception; IOException ioexception; ioexception; ioexception.printStackTrace(); } }