JAVA語法糖和語法糖編譯

Jack2k發表於2021-09-09


先簡單瞭解下定義

語法糖

語法糖(Syntactic Sugar),也叫糖衣語法,是英國電腦科學家彼得·約翰·蘭達(Peter J. Landin)發明的一個術語。指的是,在計算機語言中新增某種語法,這種語法能使程式設計師更方便的使用語言開發程式,同時增強程式程式碼的可讀性,避免出錯的機會。
幾乎每種語言都提供語法糖,它只是編譯器實現的一些小把戲罷了,編譯期間以特定的位元組碼或者特定的方式對這些語法做一些處理,開發者就可以直接方便地使用了。這些語法糖雖然不會提供實質性的功能改進,但是它們或能提高效能、或能提升語法的嚴謹性、或能減少編碼出錯的機會。Java提供給了使用者大量的語法糖,比如泛型、自動裝箱/拆箱、foreach迴圈、變長引數、內部類、列舉類、斷言、JAVA8新特性(lambda、stream、方法引用等)......

解語法糖

語法糖的存在主要是方便開發人員使用。但其實,Java 虛擬機器並不支援這些語法糖,這些語法糖在編譯階段就會被還原成簡單的基礎語法結構,這個過程就是解語法糖。
說到編譯,大家肯定都知道,Java 語言中,javac命令可以將字尾名為.java的原始檔編譯為字尾名為.class的可以執行於 Java 虛擬機器的位元組碼。
如果你去看com.sun.tools.javac.main.JavaCompiler的原始碼,你會發現在compile()中有一個步驟就是呼叫desugar(),這個方法就是負責解語法糖的實現的。

學習語法糖原理最好的辦法就是反編譯看原始碼~

反編譯工具:

IDEA預設反編譯內建外掛: JD-IntelliJ

對java8支援良好的反編譯工具: procyon-decompiler

使用方法 :  java -jar (jar包路徑)procyon-decompiler-0.5.30.jar(class檔案路徑)*.class

只支援到jdk1.5的反編譯工具: jad

使用方法 :  jad -o -8 -r -d(輸出反編譯檔案路徑) -sjava (class檔案路徑)

下面看看語法糖和三種反編譯器編譯後的程式碼

可變長度引數

可變引數由陣列實現
Ps:可變長度引數必須作為方法引數列表中的的最後一個引數且方法引數列表中只能有一個可變長度引數

foreach迴圈原理

對於陣列,foreach是用普通for迴圈實現的。
說明在對有實現Iterable介面的物件採用foreach語法糖的話,編譯器會將這個for關鍵字轉化為對目標的迭代器使用。
所以如果想要自己自定義的類可以採用foreach語法糖就要實現Iterable介面了。

自動裝箱/拆箱

可以看到在自動裝箱的時候,Java虛擬機器會自動呼叫Integer的valueOf方法;
在自動拆箱的時候,Java虛擬機器會自動呼叫Integer的intValue方法。這就是自動拆箱和自動裝箱的原理
程式碼:

圖片描述

IDEA反編譯:

圖片描述

procyon-decompiler反編譯:

圖片描述

jad反編譯:

圖片描述

泛型與型別擦除

對於java虛擬機器來說,他根本不認識Map map這樣的語法。需要在編譯階段透過型別擦除的方式進行解語法糖。
型別擦除的主要過程如下:

將所有的泛型引數用其最左邊界(最頂級的父型別)型別替換。 移除所有的型別引數。

程式碼:

圖片描述

IDEA反編譯:

圖片描述

procyon-decompiler反編譯:

圖片描述

jad反編譯:

圖片描述

泛型與過載

泛型編譯出來的程式碼是會把型別擦除的,所以如下的程式碼是不能編譯的,是因為引數List和List編譯之後都被擦除了,變成了一樣的原生型別List,擦除動作導致這兩個方法的特徵簽名變得一模一樣,或者說兩個一模一樣的方法不能共存在一個class檔案裡

圖片描述

那麼如果加上返回型別呢?

圖片描述

上面這段程式碼,IDE無法編譯透過,javac編譯可以透過。
網上找到一段引用:

在《Java虛擬機器規範第二版》(JDK 1.5修改後的版本)的“§4.4.4
Signatures”章節及《Java語言規範第三版》的“§8.4.2 Method
Signature”章節中分別都定義了位元組碼層面的方法特徵簽名,以及Java程式碼層面的方法特徵簽名,特徵簽名最重要的任務就是作為方法獨一無二不可重複的ID,在Java程式碼中的方法特徵簽名只包括了方法名稱、引數順序及引數型別,而在位元組碼中的特徵簽名還包括方法返回值及受查異常表。

根據上面的例子說明:由於List和List擦除後是同一個型別,只能新增兩個並不需要實際使用到的返回值才能完成過載。這是否是一種引入泛型後的折中的解決方案呢?

列舉

Java列舉編譯後實際上是生成了一個類,該類繼承了 java.lang.Enum,並新增了一個返回列舉陣列的values()方法和valueOf()方法。
程式碼:

圖片描述

IDEA反編譯:

圖片描述

procyon-decompiler反編譯:

圖片描述

jad反編譯:

圖片描述

內部類

Java的內部類也是一個語法糖,它僅僅是一個編譯時的概念,outer.java裡面定義了一個內部類inner,一旦編譯成功,就會生成兩個完全不同的.class檔案了,分別是outer.class和outer$inner.class。所以內部類的名字完全可以和它的外部類名字相同。
程式碼:

圖片描述

IDEA反編譯:

圖片描述

procyon-decompiler反編譯:

圖片描述

jad反編譯:

Parsing /Users/dasouche/Downloads/product/springboot-demo/target/classes/com/example/demo/DemoOutClass.class...Parsing inner class /Users/dasouche/Downloads/product/springboot-demo/target/classes/com/example/demo/DemoOutClass$InnerClass.class... Generating /Users/dasouche/Desktop/jad158g.mac.intel/com/example/demo/DemoOutClass.java

圖片描述

斷言

程式碼:

圖片描述

IDE反編譯:

圖片描述

procyon-decompiler反編譯:

圖片描述

jad反編譯:

圖片描述

JAVA8新特性中語法糖

Lambda表示式在Java 8中首先會生成一個私有的靜態函式,這個私有的靜態函式乾的就是Lambda表示式裡面的內容
程式碼:

圖片描述

IDEA反編譯:

圖片描述

procyon-decompiler反編譯:

圖片描述

jad反編譯 報錯:

圖片描述

圖片描述

用javap反編譯後:

圖片描述

原文連結:

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/36/viewspace-2812223/,如需轉載,請註明出處,否則將追究法律責任。

相關文章