Java語法糖介紹

概述

  1. 什麼是語法糖

語法糖(Syntactic sugar),也譯爲糖衣語法,是由英國計算機科學家彼得·蘭丁發明的一個術語,指計算機語言中添加的某種語法,這種語法對語言的功能沒有影響,但是更方便程序員使用。

  1. 能夠帶來的好處

語法糖讓程序更加簡潔,有更高的可讀性

  1. 有哪些語法糖
    1. 自動拆箱、裝箱
    2. 泛型擦除
    3. 不定長參數
    4. 迭代器
    5. 枚舉
    6. switch支持枚舉和字符串
    7. 內部類
    8. try-with-resources
    9. lambda

自動拆箱、裝箱

  1. Java是面向對象編程(萬物皆對象)
  2. 對象即需要new出來的,但是想想基本數據類型(int/double/boolean...),並不需要去new
  3. why?
  4. 爲了方便去使用,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

}
  1. 思考
    1. Integer的裝箱類型能否用==比較?
      1. Integer類型的valueof方法,-128~127區間採用緩存,這部分值是可以用==比較的,區間外的是重新創建的對象,用==比較的只是引用
    2. ==、equals的區別?
    3. 爲何阿里規範中心POJO以及RPC的入參、返回值的類屬性定義都要用用包裝數據?
      1. 有時候,接收到的值可能事null類型,如果用的是基本類型,則會在拆箱的過程中發生NPE

泛型擦除

  1. Java的泛型,其實是 僞泛型 ,泛型僅僅存在於編碼期間,供編譯器進行類型檢測,編譯後,會被擦除。
  2. 運行時,通過類型強制轉換來實現的。
  3. 泛型是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);  
    }
}

枚舉

  1. 枚舉其實就是一個Java類,繼承了 java.lang.Enum ,並且其本身是不允許被繼承的
  2. 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

  1. 以前在寫訪問數據庫、流編程,通常需要將資源關閉
  2. 以前使用try-catch-finally的寫法,在finally中將資源關閉
  3. 這時候就要注意很多細節上的問題
    1. 資源是否打開,是否存在
    2. 關閉的過程中如何處理異常
    3. finally中不能寫return
  4. 自從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();
    }
}
  1. 很明顯,第二種寫法較第一種,清晰、明瞭。
  2. 下面進行源碼分析:
  3. 查看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;
}

  1. 可見最終是實現了一個JDK7才提供的接口 AutoCloseable 
  2. 我們再去查看一下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
}
  1. 思考
  2. 問題一
    1. 在第一種方案中,如果try塊中拋出了異常,該異常時可以被正常拋出記錄的
    2. 如果在finally中產生異常,然後拋出,那該異常也可以被正常記錄
    3. 如果try中先出現異常,finally中也會被正常執行,但是如果finally中也出現了異常並拋出,try中的異常還能被捕獲麼?
  3. 關於異常
    1. jdk7之前,如果不做任何處理,try中的異常就會被忽略掉,無法捕獲
    2. jdk7之後,引入了“可被抑制”的異常,finally中產生的異常,可以獲取到try中的異常
    3. jdk7之前,需要實現自己的異常類
    4. jdk7之後,已經對Throwable類進行了修改以支持這種情況。在java7中爲Throwable類增加addSuppressed方法。當一個異常被拋出的時候,可能有其他異常因爲該異常而被抑制住,從而無法正常拋出。這時可以通過addSuppressed方法把這些被抑制的方法記錄下來。被抑制的異常會出現在拋出的異常的堆棧信息中,也可以通過getSuppressed方法來獲取這些異常。這樣做的好處是不會丟失任何異常,方便開發人員進行調試。
    5. 可以查看一下上面例子反編譯後的源碼:
    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();
       }
    }
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章