浅析java中的语法糖
概述
編譯器是一種計(jì)算機(jī)程序, 它主要的目的是將便于人編寫、閱讀、維護(hù)的高級(jí)計(jì)算機(jī)語(yǔ)言所寫的源代碼程序, 翻譯為計(jì)算機(jī)能解讀、運(yùn)行的低階機(jī)器語(yǔ)言的程序, 即可執(zhí)行文件。而 javac 就是java語(yǔ)言中的編譯器, 它用于將 .java 文件轉(zhuǎn)換成JVM能識(shí)別的 .class 字節(jié)碼文件, 反編譯則是將 .class 文件轉(zhuǎn)換成 .java 文件。
語(yǔ)法糖(Syntactic sugar),也譯為糖衣語(yǔ)法,是由英國(guó)計(jì)算機(jī)科學(xué)家彼得·蘭丁發(fā)明的一個(gè)術(shù)語(yǔ),指計(jì)算機(jī)語(yǔ)言中添加的某種語(yǔ)法,這種語(yǔ)法對(duì)語(yǔ)言的功能沒有影響,但是更方便程序員使用。語(yǔ)法糖讓程序更加簡(jiǎn)潔,有更高的可讀性。
java中的語(yǔ)法糖只存在于編譯期, 在編譯器將 .java 源文件編譯成 .class 字節(jié)碼時(shí), 會(huì)進(jìn)行解語(yǔ)法糖操作, 還原最原始的基礎(chǔ)語(yǔ)法結(jié)構(gòu)。這些語(yǔ)法糖包含條件編譯、斷言、Switch語(yǔ)句與枚舉及字符串結(jié)合、可變參數(shù)、自動(dòng)裝箱/拆箱、枚舉、內(nèi)部類、泛型擦除、增強(qiáng)for循環(huán)、lambda表達(dá)式、try-with-resources語(yǔ)句、JDK10的局部變量類型推斷等等。
關(guān)于反編譯工具, 其實(shí)在JDK中自帶了一個(gè)javap命令, 在以前的文章JDK的命令行工具系列 (二) javap、jinfo、jmap中也有提及到, 但是日常中很少會(huì)用到j(luò)avap, 所以這次我們借助另一個(gè)反編譯工具 CFR?來分析java中的語(yǔ)法糖, 這里我下載的是最新的cfr_0_132.jar。
字符串拼接
/*** 字符串拼接* option: --stringbuilder false*/ public void stringBuilderTest(int end) {char[] foo = new char[]{'@', 'a', '*'};char ch;int x = 0;while ((ch = foo[++x]) != '*') {System.out.println("" + x + ": " + ch);} }命令行:?java -jar cfr_0_132.jar CFRDecompilerDemo.class --stringbuilder false
從反編譯后的代碼中能看出, 當(dāng)我們使用+號(hào)進(jìn)行字符串拼接操作時(shí), 編譯時(shí)會(huì)自動(dòng)創(chuàng)建一個(gè)StringBuilder對(duì)象。所以當(dāng)在循環(huán)中拼接字符串時(shí), 應(yīng)避免使用+號(hào)操作, 否則每次循環(huán)都會(huì)創(chuàng)建一個(gè)StringBuilder對(duì)象再回收, 造成較大的開銷。
條件編譯
/*** 條件編譯* option: 不需要參數(shù)*/ public void ifCompilerTest() {if(false) {System.out.println("false if");}else {System.out.println("true else");} }命令行:?java -jar cfr_0_132.jar CFRDecompilerDemo.class
很明顯, javac編譯器在編譯時(shí)期的解語(yǔ)法糖階段, 會(huì)將條件分支不成立的代碼進(jìn)行消除。
斷言
/*** 斷言, JDK1.4開始支持* option: --sugarasserts false*/ public void assertTest(String s) {assert (!s.equals("Fred"));System.out.println(s); }命令行:?java -jar cfr_0_132.jar CFRDecompilerDemo.class?--sugarasserts false
如上, 當(dāng)斷言結(jié)果為true時(shí), 程序繼續(xù)正常執(zhí)行, 當(dāng)斷言結(jié)果為false時(shí), 則拋出AssertionError異常來打斷程序的執(zhí)行。
枚舉與Switch語(yǔ)句
/*** 枚舉與Switch語(yǔ)句* option: --decodeenumswitch false*/ public int switchEnumTest(EnumTest e) {switch (e) {case FOO:return 1;case BAP:return 2;}return 0; }/*** 枚舉, JDK1.5開始支持* option: --sugarenums false*/ public enum EnumTest {FOO,BAR,BAP }命令行:?java -jar cfr_0_132.jar CFRDecompilerDemo.class?--decodeenumswitch false
switch支持枚舉是通過調(diào)用枚舉類默認(rèn)繼承的父類Enum中的ordinal()方法來實(shí)現(xiàn)的, 這個(gè)方法會(huì)返回枚舉常量的序數(shù)。由于筆者的經(jīng)驗(yàn)尚淺, 具體的實(shí)現(xiàn)細(xì)節(jié)還不是很清楚(比如枚舉常量FOO的序數(shù)是0, 而case FOO語(yǔ)句編譯后的 case 1, 這個(gè)1是什么? 另外switchEnumTest()方法傳入一個(gè)FOO, 調(diào)用ordinal()方法得到的序數(shù)為0, 那么他又是如何與case 1進(jìn)行匹配的呢?), 歡迎讀者在留言區(qū)一起討論。
字符串與Switch語(yǔ)句
/** * 字符串與Switch語(yǔ)句* option: --decodestringswitch false*/ public int switchStringTest(String s) {switch (s) {default:System.out.println("Test");break;case "BB": // BB and Aa have the same hashcode.return 12;case "Aa":case "FRED":return 13;}System.out.println("Here");return 0; }命令行:?java -jar cfr_0_132.jar CFRDecompilerDemo.class?--decodestringswitch false
switch支持字符串是通過hashCode()和equals()方法來實(shí)現(xiàn)的, 先通過hashCode()返回的哈希值進(jìn)行switch, 然后通過equals()方法比較進(jìn)行安全檢查, 調(diào)用equals()是為了防止可能發(fā)生的哈希碰撞。
另外switch還支持byte、short、int、char這幾種基本數(shù)據(jù)類型, 其中支持char類型是通過比較它們的ascii碼(ascii碼是整型)來實(shí)現(xiàn)的。所以switch其實(shí)只支持一種數(shù)據(jù)類型, 也就是整型, 其他諸如String、枚舉類型都是轉(zhuǎn)換成整型之后再使用switch的。
可變參數(shù)
/*** 可變參數(shù)* option: --arrayiter false*/ public void varargsTest(String ... arr) {for (String s : arr) {System.out.println(s);} }命令行:?java -jar cfr_0_132.jar CFRDecompilerDemo.class?--arrayiter false
可變參數(shù)其實(shí)就是一個(gè)不定長(zhǎng)度的數(shù)組, 數(shù)組長(zhǎng)度隨傳入方法的對(duì)應(yīng)參數(shù)個(gè)數(shù)來決定。可變參數(shù)只能在參數(shù)列表的末位使用。
自動(dòng)裝箱/拆箱
/*** 自動(dòng)裝箱/拆箱* option: --sugarboxing false*/ public Double autoBoxingTest(Integer i, Double d) {return d + i; }命令行:?java -jar cfr_0_132.jar CFRDecompilerDemo.class?--sugarboxing false
首先我們知道,?基本類型與包裝類型在某些操作符的作用下, 包裝類型調(diào)用valueOf()方法的過程叫做裝箱, 調(diào)用xxxValue()方法的過程叫做拆箱。所以上面的結(jié)果很容易看出, 先對(duì)兩個(gè)包裝類進(jìn)行拆箱, 再對(duì)運(yùn)算結(jié)果進(jìn)行裝箱。
枚舉
/*** 枚舉, JDK1.5開始支持* option: --sugarenums false*/ public enum EnumTest {FOO,BAR,BAP }命令行:?java -jar cfr_0_132.jar CFRDecompilerDemo.class?--sugarenums false
當(dāng)我們自定義一個(gè)枚舉類型時(shí), 編譯器會(huì)自動(dòng)創(chuàng)建一個(gè)被final修飾的枚舉類來繼承Enum, 所以自定義枚舉類型是無(wú)法繼承和被繼承的。當(dāng)枚舉類初始化時(shí), 枚舉字段引用該枚舉類的一個(gè)靜態(tài)常量對(duì)象, 并且所有的枚舉字段都用常量數(shù)組$VALUES來存儲(chǔ)。values()方法內(nèi)則調(diào)用Object的clone()方法, 參照$VALUES數(shù)組對(duì)象復(fù)制一個(gè)新的數(shù)組, 新數(shù)組會(huì)有所有的枚舉字段。
內(nèi)部類
import java.util.*; import java.io.*;public class CFRDecompilerDemo {int x = 3;/*** 內(nèi)部類* option: --removeinnerclasssynthetics false*/public void innerClassTest() {new InnerClass().getSum(6);}public class InnerClass {public int getSum(int y) {x += y;return x;}} }命令行:?java -jar cfr_0_132.jar CFRDecompilerDemo.class?--removeinnerclasssynthetics false
首先我們要明確, 上述innerClassTest()方法中的this是外部類當(dāng)前對(duì)象的引用, 而InnerClass類中的this則是內(nèi)部類當(dāng)前對(duì)象的引用。編譯過程中, 編譯器會(huì)自動(dòng)在內(nèi)部類定義一個(gè)外部類的常量引用this$0, 并且在內(nèi)部類的構(gòu)造器中初始化this$0, 當(dāng)外部類訪問內(nèi)部類時(shí), 會(huì)把當(dāng)前外部類的對(duì)象引用this傳給內(nèi)部類的構(gòu)造器用于初始化, 這樣內(nèi)部類就能通過所持有的外部類的對(duì)象引用, 來訪問外部類的所有公有及私有成員。
泛型擦除
/*** 泛型擦除* option: */ public void genericEraseTest() {List<String> list = new ArrayList<String>(); }命令行:?java -jar cfr_0_132.jar CFRDecompilerDemo.class
在JVM中沒有泛型這一概念,? 只有普通方法和普通類, 所有泛型類的泛型參數(shù)都會(huì)在編譯時(shí)期被擦除, 所以泛型類并沒有自己獨(dú)有的Class類對(duì)象比如List<Integer>.class, 而只有List.class對(duì)象。
增強(qiáng)for循環(huán)
/*** 增強(qiáng)for循環(huán)* option: --collectioniter false*/ public void forLoopTest() {String[] qingshanli = {"haha", "qingshan", "helloworld", "ceshi"}; List<String> list = Arrays.asList(qingshanli);for (Object s : list) {System.out.println(s);} }命令行:?java -jar cfr_0_132.jar CFRDecompilerDemo.class?--collectioniter false
很明顯, 增強(qiáng)for循環(huán)的底層其實(shí)還是通過迭代器來實(shí)現(xiàn)的, 這也就解釋了為什么增強(qiáng)for循環(huán)中不能進(jìn)行增刪改操作。
lambda表達(dá)式
/*** lambda表達(dá)式* option: --decodelambdas false*/ public void lambdaTest() {String[] qingshanli = {"haha", "qingshan", "helloworld", "ceshi"}; List<String> list = Arrays.asList(qingshanli);// 使用lambda表達(dá)式以及函數(shù)操作list.forEach((str) -> System.out.print(str + "; "));// 在JDK8中使用雙冒號(hào)操作符list.forEach(System.out::println); }命令行:?java -jar cfr_0_132.jar CFRDecompilerDemo.class?--decodelambdas false
這里筆者經(jīng)驗(yàn)尚淺, 關(guān)于lambda表達(dá)式的實(shí)現(xiàn)原理暫不做闡述, 以免誤人子弟, 歡迎有興趣的讀者在留言區(qū)一起討論。
try-with-resources語(yǔ)句
/*** try-with-resources語(yǔ)句* option: --tryresources false*/ public void tryWithResourcesTest() throws IOException {try (final StringWriter writer = new StringWriter();final StringWriter writer2 = new StringWriter()) {writer.write("This is qingshanli1");writer2.write("this is qingshanli2");} }命令行:?java -jar cfr_0_132.jar CFRDecompilerDemo.class?--tryresources false
在JDK7之前, 如IO流、數(shù)據(jù)庫(kù)連接等資源用完后, 都是通過finally代碼塊來釋放資源。而try-with-resources語(yǔ)法糖則幫我們省去了釋放資源這一操作, 編譯器在解語(yǔ)法糖階段時(shí)會(huì)將它還原成原始的語(yǔ)法結(jié)構(gòu)。
JDK10的局部變量類型推斷
/*** 局部變量類型推斷, JDK10開始支持* option: 不需要參數(shù)*/ public void varTest() {//初始化局部變量 var string = "qingshanli";//初始化局部變量 var stringList = new ArrayList<String>();stringList.add("九幽陰?kù)`,諸天神魔,以我血軀,奉為犧牲。");stringList.add("三生七世,永墮閻羅,只為情故,雖死不悔!");stringList.add("blog:http://www.cnblogs.com/qingshanli/");//增強(qiáng)for循環(huán)的索引for (var s : stringList){System.out.println(s);}//傳統(tǒng)for循環(huán)的局部變量定義for (var i = 0; i < stringList.size(); i++){System.out.println(stringList.get(i));} }JDK10環(huán)境下編譯:?/home/qingshanli/Downloads/jdk-10.0.2/bin/javac CFRDecompilerDemo.java
命令行:?java -jar cfr_0_132.jar CFRDecompilerDemo.class?--collectioniter false
可以看出, 局部變量類型推斷其實(shí)也是一個(gè)語(yǔ)法糖。在編譯過程的解語(yǔ)法糖階段, 會(huì)使用變量真正的類型來替代var類型。所以java由始至終是一種強(qiáng)類型語(yǔ)言, java中的var和弱類型語(yǔ)言JavaScript中的var是完全不一樣的, 例如下圖?var i = "10" - 6?這樣的語(yǔ)法運(yùn)算在JavaScript中可以的, 而在Java語(yǔ)言中則不被允許。
另外目前已知的允許使用var聲明變量的幾個(gè)場(chǎng)景有初始化局部變量、增強(qiáng)for循環(huán)的索引、傳統(tǒng)for循環(huán)的局部變量定義。而諸如方法的形參、構(gòu)造器的形參、方法的返回值類型、對(duì)象的成員變量、只進(jìn)行定義而不初始化的變量等則不支持這種用法。對(duì)于后面的幾種不支持, 我的猜想是因?yàn)樗鼈儠?huì)被外部訪問而導(dǎo)致充滿了不確定性, 舉個(gè)栗子, 比如對(duì)象的成員變量X, 被對(duì)象A訪問并賦值A(chǔ)rrayList類型, 被對(duì)象B訪問并賦值HashMap類型, 那么問題來了, 對(duì)象A和對(duì)象B都是同一個(gè)類的實(shí)例, 這就產(chǎn)生了沖突, 此時(shí)虛擬機(jī)又如何區(qū)分這個(gè)對(duì)象的成員變量X到底是什么類型呢??
源代碼
import java.util.*; import java.io.*;public class CFRDecompilerDemo {int x = 3;/*** 字符串拼接* option: --stringbuilder false*/public void stringBuilderTest(int end) {char[] foo = new char[]{'@', 'a', '*'};char ch;int x = 0;while ((ch = foo[++x]) != '*') {System.out.println("" + x + ": " + ch);}}/*** 條件編譯* option: 不需要參數(shù)*/public void ifCompilerTest() {if(false) {System.out.println("false if");}else {System.out.println("true else");}}/*** 斷言, JDK1.4開始支持* option: --sugarasserts false*/public void assertTest(String s) {assert (!s.equals("Fred"));System.out.println(s);}/*** 枚舉與Switch語(yǔ)句* option: --decodeenumswitch false*/public int switchEnumTest(EnumTest e) {switch (e) {case FOO:return 1;case BAP:return 2;}return 0;}/** * 字符串與Switch語(yǔ)句* option: --decodestringswitch false*/public int switchStringTest(String s) {switch (s) {default:System.out.println("Test");break;case "BB": // BB and Aa have the same hashcode.return 12;case "Aa":case "FRED":return 13;}System.out.println("Here");return 0;}/*** 可變參數(shù)* option: --arrayiter false*/public void varargsTest(String ... arr) {for (String s : arr) {System.out.println(s);}}/*** 自動(dòng)裝箱/拆箱* option: --sugarboxing false*/public Double autoBoxingTest(Integer i, Double d) {return d + i;}/*** 枚舉, JDK1.5開始支持* option: --sugarenums false*/public enum EnumTest {FOO,BAR,BAP}/*** 內(nèi)部類* option: --removeinnerclasssynthetics false*/public void innerClassTest() {new InnerClass().getSum(6);}public class InnerClass {public int getSum(int y) {x += y;return x;}}/*** 泛型擦除* option: */public void genericEraseTest() {List<String> list = new ArrayList<String>();}/*** 增強(qiáng)for循環(huán)* option: --collectioniter false*/public void forLoopTest() {String[] qingshanli = {"haha", "qingshan", "helloworld", "ceshi"}; List<String> list = Arrays.asList(qingshanli);for (Object s : list) {System.out.println(s);}}/*** lambda表達(dá)式* option: --decodelambdas false*/public void lambdaTest() {String[] qingshanli = {"haha", "qingshan", "helloworld", "ceshi"}; List<String> list = Arrays.asList(qingshanli);// 使用lambda表達(dá)式以及函數(shù)操作list.forEach((str) -> System.out.print(str + "; "));// 在JDK8中使用雙冒號(hào)操作符list.forEach(System.out::println); }/*** try-with-resources語(yǔ)句* option: --tryresources false*/public void tryWithResourcesTest() throws IOException {try (final StringWriter writer = new StringWriter();final StringWriter writer2 = new StringWriter()) {writer.write("This is qingshanli1");writer2.write("this is qingshanli2");}}/*** 局部變量類型推斷, JDK10開始支持* option: 不需要參數(shù)*/public void varTest() {//初始化局部變量 var string = "qingshanli";//初始化局部變量 var stringList = new ArrayList<String>();stringList.add("九幽陰?kù)`,諸天神魔,以我血軀,奉為犧牲。");stringList.add("三生七世,永墮閻羅,只為情故,雖死不悔!");stringList.add("blog:http://www.cnblogs.com/qingshanli/");//增強(qiáng)for循環(huán)的索引for (var s : stringList){System.out.println(s);}//傳統(tǒng)for循環(huán)的局部變量定義for (var i = 0; i < stringList.size(); i++){System.out.println(stringList.get(i));}} }參數(shù)資料
Java的編譯原理
Java代碼的編譯與反編譯那些事兒-HollisChuang's Blog
我反編譯了Java 10的本地變量類型推斷-HollisChuang's Blog
Java中的Switch對(duì)整型、字符型、字符串型的具體實(shí)現(xiàn)細(xì)節(jié)-HollisChuang's Blo...
一些防止java代碼被反編譯的方法
?
from:?https://www.cnblogs.com/qingshanli/p/9375040.html
總結(jié)
以上是生活随笔為你收集整理的浅析java中的语法糖的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java语法糖设计
- 下一篇: java实现条件编译