<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          不了解這12個(gè)語法糖,別說你會(huì)Java!

          共 6878字,需瀏覽 14分鐘

           ·

          2020-11-25 08:24


          本文從 Java 編譯原理角度,深入字節(jié)碼及 class 文件,抽絲剝繭,了解 Java 中的語法糖原理及用法,幫助大家在學(xué)會(huì)如何使用 Java 語法糖的同時(shí),了解這些語法糖背后的原理


          語法糖

          語法糖(Syntactic Sugar),也稱糖衣語法,是由英國計(jì)算機(jī)學(xué)家 Peter.J.Landin 發(fā)明的一個(gè)術(shù)語,指在計(jì)算機(jī)語言中添加的某種語法,這種語法對語言的功能并沒有影響,但是更方便程序員使用。簡而言之,語法糖讓程序更加簡潔,有更高的可讀性。

          有意思的是,在編程領(lǐng)域,除了語法糖,還有語法鹽和語法糖精的說法,篇幅有限這里不做擴(kuò)展了。

          我們所熟知的編程語言中幾乎都有語法糖。作者認(rèn)為,語法糖的多少是評判一個(gè)語言夠不夠牛逼的標(biāo)準(zhǔn)之一。

          很多人說Java是一個(gè)“低糖語言”,其實(shí)從Java 7開始Java語言層面上一直在添加各種糖,主要是在“Project Coin”項(xiàng)目下研發(fā)。盡管現(xiàn)在Java有人還是認(rèn)為現(xiàn)在的Java是低糖,未來還會(huì)持續(xù)向著“高糖”的方向發(fā)展。


          解語法糖

          前面提到過,語法糖的存在主要是方便開發(fā)人員使用。但其實(shí),Java虛擬機(jī)并不支持這些語法糖。這些語法糖在編譯階段就會(huì)被還原成簡單的基礎(chǔ)語法結(jié)構(gòu),這個(gè)過程就是解語法糖。

          說到編譯,大家肯定都知道,Java語言中,javac命令可以將后綴名為.java的源文件編譯為后綴名為.class的可以運(yùn)行于Java虛擬機(jī)的字節(jié)碼。

          如果你去看com.sun.tools.javac.main.JavaCompiler的源碼,你會(huì)發(fā)現(xiàn)在compile()中有一個(gè)步驟就是調(diào)用desugar(),這個(gè)方法就是負(fù)責(zé)解語法糖的實(shí)現(xiàn)的。

          Java 中最常用的語法糖主要有泛型、變長參數(shù)、條件編譯、自動(dòng)拆裝箱、內(nèi)部類等。本文主要來分析下這些語法糖背后的原理。一步一步剝?nèi)ヌ且拢纯雌浔举|(zhì)。


          糖塊一、 switch 支持 String 與枚舉

          前面提到過,從Java 7 開始,Java語言中的語法糖在逐漸豐富,其中一個(gè)比較重要的就是Java 7中switch開始支持String。

          在開始coding之前先科普下,Java中的swith自身原本就支持基本類型。比如int、char等。

          對于int類型,直接進(jìn)行數(shù)值的比較。對于char類型則是比較其ascii碼。

          所以,對于編譯器來說,switch中其實(shí)只能使用整型,任何類型的比較都要轉(zhuǎn)換成整型。比如byte。short,char(ackii碼是整型)以及int。

          那么接下來看下switch對String得支持,有以下代碼:

          public?class?switchDemoString?{
          ????public?static?void?main(String[]?args)?{
          ????????String?str?=?"world";
          ????????switch?(str)?{
          ????????case?"hello":
          ????????????System.out.println("hello");
          ????????????break;
          ????????case?"world":
          ????????????System.out.println("world");
          ????????????break;
          ????????default:
          ????????????break;
          ????????}
          ????}
          }

          反編譯后內(nèi)容如下:

          public?class?switchDemoString
          {
          ????public?switchDemoString()
          ????
          {
          ????}
          ????public?static?void?main(String?args[])
          ????
          {
          ????????String?str?=?"world";
          ????????String?s;
          ????????switch((s?=?str).hashCode())
          ????????{
          ????????default:
          ????????????break;
          ????????case?99162322:
          ????????????if(s.equals("hello"))
          ????????????????System.out.println("hello");
          ????????????break;
          ????????case?113318802:
          ????????????if(s.equals("world"))
          ????????????????System.out.println("world");
          ????????????break;
          ????????}
          ????}
          }

          看到這個(gè)代碼,你知道原來字符串的switch是通過equals()和hashCode()方法來實(shí)現(xiàn)的。還好hashCode()方法返回的是int,而不是long。

          仔細(xì)看下可以發(fā)現(xiàn),進(jìn)行switch的實(shí)際是哈希值,然后通過使用equals方法比較進(jìn)行安全檢查,這個(gè)檢查是必要的,因?yàn)楣?赡軙?huì)發(fā)生碰撞。因此它的性能是不如使用枚舉進(jìn)行switch或者使用純整數(shù)常量,但這也不是很差。



          糖塊二、 泛型

          我們都知道,很多語言都是支持泛型的,但是很多人不知道的是,不同的編譯器對于泛型的處理方式是不同的。

          通常情況下,一個(gè)編譯器處理泛型有兩種方式:Code specialization和Code sharing。

          C++和C#是使用Code specialization的處理機(jī)制,而Java使用的是Code sharing的機(jī)制。

          Code sharing方式為每個(gè)泛型類型創(chuàng)建唯一的字節(jié)碼表示,并且將該泛型類型的實(shí)例都映射到這個(gè)唯一的字節(jié)碼表示上。將多種泛型類形實(shí)例映射到唯一的字節(jié)碼表示是通過類型擦除(type erasue)實(shí)現(xiàn)的。

          也就是說,對于Java虛擬機(jī)來說,他根本不認(rèn)識Map map這樣的語法。需要在編譯階段通過類型擦除的方式進(jìn)行解語法糖。

          類型擦除的主要過程如下:

          • ?1.將所有的泛型參數(shù)用其最左邊界(最頂級的父類型)類型替換。

          • ?2.移除所有的類型參數(shù)。

          以下代碼:

          Map<String,?String>?map?=?new?HashMap<String,?String>();??
          map.put("name",?"wupx");??
          map.put("wechat",?"wupx");??
          map.put("blog",?"www.tianheyu.top");??

          解語法糖之后會(huì)變成:

          Map?map?=?new?HashMap();??
          map.put("name",?"wupx");??
          map.put("wechat",?"wupx");??
          map.put("blog",?"www.tianheyu.top");??

          以下代碼:

          public?static?extends?Comparable>?A?max(Collection?xs)?{
          ????Iterator
          ?xi?=?xs.iterator();
          ????A?w?=?xi.next();
          ????while?(xi.hasNext())?{
          ????????A?x?=?xi.next();
          ????????if?(w.compareTo(x)?0)
          ????????????w?=?x;
          ????}
          ????return?w;
          }

          類型擦除后會(huì)變成:

          ?public?static?Comparable?max(Collection?xs){
          ????Iterator?xi?=?xs.iterator();
          ????Comparable?w?=?(Comparable)xi.next();
          ????while(xi.hasNext())
          ????{
          ????????Comparable?x?=?(Comparable)xi.next();
          ????????if(w.compareTo(x)?0
          )
          ????????????w?=?x;
          ????}
          ????return?w;
          }

          虛擬機(jī)中沒有泛型,只有普通類和普通方法,所有泛型類的類型參數(shù)在編譯時(shí)都會(huì)被擦除,泛型類并沒有自己獨(dú)有的Class類對象。比如并不存在List.class或是List.class,而只有List.class。


          糖塊三、 自動(dòng)裝箱與拆箱

          自動(dòng)裝箱就是Java自動(dòng)將原始類型值轉(zhuǎn)換成對應(yīng)的對象,比如將int的變量轉(zhuǎn)換成Integer對象,這個(gè)過程叫做裝箱,反之將Integer對象轉(zhuǎn)換成int類型值,這個(gè)過程叫做拆箱。

          因?yàn)檫@里的裝箱和拆箱是自動(dòng)進(jìn)行的非人為轉(zhuǎn)換,所以就稱作為自動(dòng)裝箱和拆箱。

          原始類型byte, short, char, int, long, float, double 和 boolean 對應(yīng)的封裝類為Byte, Short, Character, Integer, Long, Float, Double, Boolean。

          先來看個(gè)自動(dòng)裝箱的代碼:

          ?public?static?void?main(String[]?args)?{
          ????int?i?=?10;
          ????Integer?n?=?i;
          }

          反編譯后代碼如下:

          public?static?void?main(String?args[])
          {
          ????int?i?=?10;
          ????Integer?n?=?Integer.valueOf(i);
          }

          再來看個(gè)自動(dòng)拆箱的代碼:

          public?static?void?main(String[]?args)?{

          ????Integer?i?=?10;
          ????int?n?=?i;
          }

          反編譯后代碼如下:

          public?static?void?main(String?args[])
          {
          ????Integer?i?=?Integer.valueOf(10);
          ????int?n?=?i.intValue();
          }

          從反編譯得到內(nèi)容可以看出,在裝箱的時(shí)候自動(dòng)調(diào)用的是Integer的valueOf(int)方法。而在拆箱的時(shí)候自動(dòng)調(diào)用的是Integer的intValue方法。

          所以,裝箱過程是通過調(diào)用包裝器的valueOf方法實(shí)現(xiàn)的,而拆箱過程是通過調(diào)用包裝器的 xxxValue方法實(shí)現(xiàn)的。


          糖塊四 、 方法變長參數(shù)

          可變參數(shù)(variable arguments)是在Java 1.5中引入的一個(gè)特性。它允許一個(gè)方法把任意數(shù)量的值作為參數(shù)。

          看下以下可變參數(shù)代碼,其中print方法接收可變參數(shù):

          public?static?void?main(String[]?args)
          ????
          {
          ????????print("wupx",?"公眾號:wupx",?"博客:www.tianheyu.top",?"QQ:928403374");
          ????}

          public?static?void?print(String...?strs)
          {
          ????for?(int?i?=?0;?i?????{
          ????????System.out.println(strs[i]);
          ????}
          }

          反編譯后代碼:

          ?public?static?void?main(String?args[])
          {
          ????print(new?String[]?{
          ????????"wupx",?"\u516C\u4F17\u53F7:wupx",?"\u535A\u5BA2\uFF1Awww.tianheyu.top",?"QQ\uFF1A928403374"
          ????});
          }

          public?static?transient?void?print(String?strs[])
          {
          ????for(int?i?=?0;?i?????????System.out.println(strs[i]);
          }

          從反編譯后代碼可以看出,可變參數(shù)在被使用的時(shí)候,他首先會(huì)創(chuàng)建一個(gè)數(shù)組,數(shù)組的長度就是調(diào)用該方法是傳遞的實(shí)參的個(gè)數(shù),然后再把參數(shù)值全部放到這個(gè)數(shù)組當(dāng)中,然后再把這個(gè)數(shù)組作為參數(shù)傳遞到被調(diào)用的方法中。


          糖塊五 、 枚舉

          Java SE5提供了一種新的類型-Java的枚舉類型,關(guān)鍵字enum可以將一組具名的值的有限集合創(chuàng)建為一種新的類型,而這些具名的值可以作為常規(guī)的程序組件使用,這是一種非常有用的功能。

          要想看源碼,首先得有一個(gè)類吧,那么枚舉類型到底是什么類呢?是enum嗎?

          答案很明顯不是,enum就和class一樣,只是一個(gè)關(guān)鍵字,他并不是一個(gè)類。

          那么枚舉是由什么類維護(hù)的呢,我們簡單的寫一個(gè)枚舉:

          public?enum?t?{
          ????SPRING,SUMMER;
          }

          然后我們使用反編譯,看看這段代碼到底是怎么實(shí)現(xiàn)的,反編譯后代碼內(nèi)容如下:

          public?final?class?T?extends?Enum
          {
          ????private?T(String?s,?int?i)
          ????
          {
          ????????super(s,?i);
          ????}
          ????public?static?T[]?values()
          ????{
          ????????T?at[];
          ????????int?i;
          ????????T?at1[];
          ????????System.arraycopy(at?=?ENUM$VALUES,?0,?at1?=?new?T[i?=?at.length],?0,?i);
          ????????return?at1;
          ????}

          ????public?static?T?valueOf(String?s)
          ????
          {
          ????????return?(T)Enum.valueOf(demo/T,?s);
          ????}

          ????public?static?final?T?SPRING;
          ????public?static?final?T?SUMMER;
          ????private?static?final?T?ENUM$VALUES[];
          ????static
          ????{
          ????????SPRING?=?new?T("SPRING",?0);
          ????????SUMMER?=?new?T("SUMMER",?1);
          ????????ENUM$VALUES?=?(new?T[]?{
          ????????????SPRING,?SUMMER
          ????????});
          ????}
          }

          通過反編譯后代碼我們可以看到,public final class T extends Enum,說明,該類是繼承了Enum類的,同時(shí)final關(guān)鍵字告訴我們,這個(gè)類也是不能被繼承的。

          當(dāng)我們使用enmu來定義一個(gè)枚舉類型的時(shí)候,編譯器會(huì)自動(dòng)幫我們創(chuàng)建一個(gè)final類型的類繼承Enum類,所以枚舉類型不能被繼承。


          糖塊六 、 內(nèi)部類

          內(nèi)部類又稱為嵌套類,可以把內(nèi)部類理解為外部類的一個(gè)普通成員。

          內(nèi)部類之所以也是語法糖,是因?yàn)樗鼉H僅是一個(gè)編譯時(shí)的概念。

          outer.java里面定義了一個(gè)內(nèi)部類inner,一旦編譯成功,就會(huì)生成兩個(gè)完全不同的.class文件了,分別是outer.class和outer$inner.class。所以內(nèi)部類的名字完全可以和它的外部類名字相同。

          public?class?OutterClass?{
          ????private?String?userName;

          ????public?String?getUserName()?{
          ????????return?userName;
          ????}

          ????public?void?setUserName(String?userName)?{
          ????????this.userName?=?userName;
          ????}

          ????public?static?void?main(String[]?args)?{

          ????}

          ????class?InnerClass{
          ????????private?String?name;

          ????????public?String?getName()?{
          ????????????return?name;
          ????????}

          ????????public?void?setName(String?name)?{
          ????????????this.name?=?name;
          ????????}
          ????}
          }

          以上代碼編譯后會(huì)生成兩個(gè)class文件:OutterClass$InnerClass.class 、OutterClass.class 。

          當(dāng)我們嘗試使用jad對OutterClass.class文件進(jìn)行反編譯的時(shí)候,命令行會(huì)打印以下內(nèi)容:

          Parsing OutterClass.class...
          Parsing inner?class?OutterClass$InnerClass.class...
          Generating OutterClass.jad

          他會(huì)把兩個(gè)文件全部進(jìn)行反編譯,然后一起生成一個(gè)OutterClass.jad文件。文件內(nèi)容如下:

          public?class?OutterClass
          {
          ????class?InnerClass
          ????
          {
          ????????public?String?getName()
          ????????
          {
          ????????????return?name;
          ????????}
          ????????public?void?setName(String?name)
          ????????
          {
          ????????????this.name?=?name;
          ????????}
          ????????private?String?name;
          ????????final?OutterClass?this$0;

          ????????InnerClass()
          ????????{
          ????????????this.this$0?=?OutterClass.this;
          ????????????super();
          ????????}
          ????}

          ????public?OutterClass()
          ????
          {
          ????}
          ????public?String?getUserName()
          ????
          {
          ????????return?userName;
          ????}
          ????public?void?setUserName(String?userName){
          ????????this.userName?=?userName;
          ????}
          ????public?static?void?main(String?args1[])
          ????
          {
          ????}
          ????private?String?userName;
          }



          糖塊七 、條件編譯

          —般情況下,程序中的每一行代碼都要參加編譯。但有時(shí)候出于對程序代碼優(yōu)化的考慮,希望只對其中一部分內(nèi)容進(jìn)行編譯,此時(shí)就需要在程序中加上條件,讓編譯器只對滿足條件的代碼進(jìn)行編譯,將不滿足條件的代碼舍棄,這就是條件編譯。

          如在C或CPP中,可以通過預(yù)處理語句來實(shí)現(xiàn)條件編譯。其實(shí)在Java中也可實(shí)現(xiàn)條件編譯。我們先來看一段代碼:

          public?class?ConditionalCompilation?{
          ????public?static?void?main(String[]?args)?{
          ????????final?boolean?DEBUG?=?true;
          ????????if(DEBUG)?{
          ????????????System.out.println("Hello,?DEBUG!");
          ????????}

          ????????final?boolean?ONLINE?=?false;

          ????????if(ONLINE){
          ????????????System.out.println("Hello,?ONLINE!");
          ????????}
          ????}
          }

          反編譯后代碼如下:

          public?class?ConditionalCompilation
          {

          ????public?ConditionalCompilation()
          ????
          {
          ????}

          ????public?static?void?main(String?args[])
          ????
          {
          ????????boolean?DEBUG?=?true;
          ????????System.out.println("Hello,?DEBUG!");
          ????????boolean?ONLINE?=?false;
          ????}
          }

          首先,我們發(fā)現(xiàn),在反編譯后的代碼中沒有System.out.println("Hello, ONLINE!");,這其實(shí)就是條件編譯。

          當(dāng)if(ONLINE)為false的時(shí)候,編譯器就沒有對其內(nèi)的代碼進(jìn)行編譯。

          所以,Java語法的條件編譯,是通過判斷條件為常量的if語句實(shí)現(xiàn)的。根據(jù)if判斷條件的真假,編譯器直接把分支為false的代碼塊消除。通過該方式實(shí)現(xiàn)的條件編譯,必須在方法體內(nèi)實(shí)現(xiàn),而無法在正整個(gè)Java類的結(jié)構(gòu)或者類的屬性上進(jìn)行條件編譯。

          這與C/C++的條件編譯相比,確實(shí)更有局限性。在Java語言設(shè)計(jì)之初并沒有引入條件編譯的功能,雖有局限,但是總比沒有更強(qiáng)。


          糖塊八 、 斷言

          在Java中,assert關(guān)鍵字是從JAVA SE 1.4 引入的,為了避免和老版本的Java代碼中使用了assert關(guān)鍵字導(dǎo)致錯(cuò)誤,Java在執(zhí)行的時(shí)候默認(rèn)是不啟動(dòng)斷言檢查的(這個(gè)時(shí)候,所有的斷言語句都將忽略!)。

          如果要開啟斷言檢查,則需要用開關(guān)-enableassertions或-ea來開啟。

          看一段包含斷言的代碼:

          public?class?AssertTest?{
          ????public?static?void?main(String?args[])?{
          ????????int?a?=?1;
          ????????int?b?=?1;
          ????????assert?a?==?b;
          ????????System.out.println("公眾號:wupx");
          ????????assert?a?!=?b?:?"wupx";
          ????????System.out.println("博客:www.tianheyu.top");
          ????}
          }

          反編譯后代碼如下:

          public?class?AssertTest?{
          ???public?AssertTest()
          ????
          {
          ????}
          ????public?static?void?main(String?args[])
          {
          ????int?a?=?1;
          ????int?b?=?1;
          ????if(!$assertionsDisabled?&&?a?!=?b)
          ????????throw?new?AssertionError();
          ????System.out.println("\u516C\u4F17\u53F7\uFF1AHollis");
          ????if(!$assertionsDisabled?&&?a?==?b)
          ????{
          ????????throw?new?AssertionError("wupx");
          ????}?else
          ????{
          ????????System.out.println("\u535A\u5BA2\uFF1Awww.tianheyu.top");
          ????????return;
          ????}
          }

          static?final?boolean?$assertionsDisabled?=?!com/wupx/suguar/AssertTest.desiredAssertionStatus();


          }

          很明顯,反編譯之后的代碼要比我們自己的代碼復(fù)雜的多。所以,使用了assert這個(gè)語法糖我們節(jié)省了很多代碼。

          其實(shí)斷言的底層實(shí)現(xiàn)就是if語言,如果斷言結(jié)果為true,則什么都不做,程序繼續(xù)執(zhí)行,如果斷言結(jié)果為false,則程序拋出AssertError來打斷程序的執(zhí)行。

          -enableassertions會(huì)設(shè)置$assertionsDisabled字段的值。


          糖塊九 、 數(shù)值字面量

          在java 7中,數(shù)值字面量,不管是整數(shù)還是浮點(diǎn)數(shù),都允許在數(shù)字之間插入任意多個(gè)下劃線。這些下劃線不會(huì)對字面量的數(shù)值產(chǎn)生影響,目的就是方便閱讀。

          比如:

          public?class?Test?{
          ????public?static?void?main(String...?args)?{
          ????????int?i?=?10_000;
          ????????System.out.println(i);
          ????}
          }

          反編譯后:

          public?class?Test
          {
          ??public?static?void?main(String[]?args)
          ??
          {
          ????int?i?=?10000;
          ????System.out.println(i);
          ??}
          }

          反編譯后就是把_刪除了。也就是說編譯器并不認(rèn)識在數(shù)字字面量中的_,需要在編譯階段把他去掉。


          糖塊十 、 for-each

          增強(qiáng)for循環(huán)(for-each)相信大家都不陌生,日常開發(fā)經(jīng)常會(huì)用到的,他會(huì)比for循環(huán)要少寫很多代碼,那么這個(gè)語法糖背后是如何實(shí)現(xiàn)的呢?

          public?static?void?main(String...?args)?{
          ????String[]?strs?=?{"wupx",?"公眾號:wupx",?"博客:www.tianheyu.top"};
          ????for?(String?s?:?strs)?{
          ????????System.out.println(s);
          ????}
          ????List<String>?strList?=?ImmutableList.of("wupx",?"公眾號:wupx",?"博客:www.tianheyu.top");
          ????for?(String?s?:?strList)?{
          ????????System.out.println(s);
          ????}
          }

          反編譯后代碼如下:

          public?static?transient?void?main(String?args[])
          {
          ????String?strs[]?=?{
          ????????"wupx",?"\u516C\u4F17\u53F7\uFF1Awupx",?"\u535A\u5BA2\uFF1Awww.tianheyu.top"
          ????};
          ????String?args1[]?=?strs;
          ????int?i?=?args1.length;
          ????for(int?j?=?0;?j?????{
          ????????String?s?=?args1[j];
          ????????System.out.println(s);
          ????}

          ????List?strList?=?ImmutableList.of("wupx",?"\u516C\u4F17\u53F7\uFF1Awupx",?"\u535A\u5BA2\uFF1Awww.tianheyu.top");
          ????String?s;
          ????for(Iterator?iterator?=?strList.iterator();?iterator.hasNext();?System.out.println(s))
          ????????s?=?(String)iterator.next();

          }

          代碼很簡單,for-each的實(shí)現(xiàn)原理其實(shí)就是使用了普通的for循環(huán)和迭代器。


          糖塊十一 、 try-with-resource


          Java里,對于文件操作IO流、數(shù)據(jù)庫連接等開銷非常昂貴的資源,用完之后必須及時(shí)通過close方法將其關(guān)閉,否則資源會(huì)一直處于打開狀態(tài),可能會(huì)導(dǎo)致內(nèi)存泄露等問題。

          關(guān)閉資源的常用方式就是在finally塊里是釋放,即調(diào)用close方法。比如,我們經(jīng)常會(huì)寫這樣的代碼:

          public?static?void?main(String[]?args)?{
          ????BufferedReader?br?=?null;
          ????try?{
          ????????String?line;
          ????????br?=?new?BufferedReader(new?FileReader("d:\\wupx.xml"));
          ????????while?((line?=?br.readLine())?!=?null)?{
          ????????????System.out.println(line);
          ????????}
          ????}?catch?(IOException?e)?{
          ????????//?handle?exception
          ????}?finally?{
          ????????try?{
          ????????????if?(br?!=?null)?{
          ????????????????br.close();
          ????????????}
          ????????}?catch?(IOException?ex)?{
          ????????????//?handle?exception
          ????????}
          ????}
          }

          從Java 7開始,jdk提供了一種更好的方式關(guān)閉資源,使用try-with-resources語句,改寫一下上面的代碼,效果如下:

          public?static?void?main(String...?args)?{
          ????try?(BufferedReader?br?=?new?BufferedReader(new?FileReader("d:\\?wupx.xml")))?{
          ????????String?line;
          ????????while?((line?=?br.readLine())?!=?null)?{
          ????????????System.out.println(line);
          ????????}
          ????}?catch?(IOException?e)?{
          ????????//?handle?exception
          ????}
          }

          看,這簡直是一大福音啊,雖然我之前一般使用IOUtils去關(guān)閉流,并不會(huì)使用在finally中寫很多代碼的方式,但是這種新的語法糖看上去好像優(yōu)雅很多呢。

          反編譯以上代碼,看下他的背后原理:

          public?static?transient?void?main(String?args[])
          ????{
          ????????BufferedReader?br;
          ????????Throwable?throwable;
          ????????br?=?new?BufferedReader(new?FileReader("d:\\?wupx.xml"));
          ????????throwable?=?null;
          ????????String?line;
          ????????try
          ????????{
          ????????????while((line?=?br.readLine())?!=?null)
          ????????????????System.out.println(line);
          ????????}
          ????????catch(Throwable?throwable2)
          ????????{
          ????????????throwable?=?throwable2;
          ????????????throw?throwable2;
          ????????}
          ????????if(br?!=?null)
          ????????????if(throwable?!=?null)
          ????????????????try
          ????????????????{
          ????????????????????br.close();
          ????????????????}
          ????????????????catch(Throwable?throwable1)
          ????????????????{
          ????????????????????throwable.addSuppressed(throwable1);
          ????????????????}
          ????????????else
          ????????????????br.close();
          ????????????break?MISSING_BLOCK_LABEL_113;
          ????????????Exception?exception;
          ????????????exception;
          ????????????if(br?!=?null)
          ????????????????if(throwable?!=?null)
          ????????????????????try
          ????????????????????{
          ????????????????????????br.close();
          ????????????????????}
          ????????????????????catch(Throwable?throwable3)
          ??????????????????????{
          ????????????????????????throwable.addSuppressed(throwable3);
          ????????????????????}
          ????????????????else
          ????????????????????br.close();
          ????????throw?exception;
          ????????IOException?ioexception;
          ????????ioexception;
          ????}
          }

          其實(shí)背后的原理也很簡單,那些我們沒有做的關(guān)閉資源的操作,編譯器都幫我們做了。

          所以,再次印證了,語法糖的作用就是方便程序員的使用,但最終還是要轉(zhuǎn)成編譯器認(rèn)識的語言。


          糖塊十二、Lambda表達(dá)式

          關(guān)于lambda表達(dá)式,有人可能會(huì)有質(zhì)疑,因?yàn)榫W(wǎng)上有人說他并不是語法糖。其實(shí)我想糾正下這個(gè)說法。

          Labmda表達(dá)式不是匿名內(nèi)部類的語法糖,但是他也是一個(gè)語法糖。實(shí)現(xiàn)方式其實(shí)是依賴了幾個(gè)JVM底層提供的lambda相關(guān)api。

          先來看一個(gè)簡單的lambda表達(dá)式。遍歷一個(gè)list:

          public?static?void?main(String...?args)?{
          ????List?strList?=?ImmutableList.of("wupx",?"公眾號:wupx",?"博客:www.tianheyu.top");

          ????strList.forEach(?s?->?{?System.out.println(s);?}?);
          }

          為啥說他并不是內(nèi)部類的語法糖呢,前面講內(nèi)部類我們說過,內(nèi)部類在編譯之后會(huì)有兩個(gè)class文件,但是,包含lambda表達(dá)式的類編譯后只有一個(gè)文件。

          反編譯后代碼如下:

          public?static?/*?varargs?*/?void?main(String?...?args)?{
          ????ImmutableList?strList?=?ImmutableList.of((Object)"wupx",?(Object)"\u516c\u4f17\u53f7\uff1awupx",?(Object)"\u535a\u5ba2\uff1awww.tianheyu.top");
          ????strList.forEach((Consumer<String>)LambdaMetafactory.metafactory(null,?null,?null,?(Ljava/lang/Object;)V,?lambda$main$0(java.lang.String?),?(Ljava/lang/String;)V)());
          }

          private?static?/*?synthetic?*/?void?lambda$main$0(String?s)?{
          ????System.out.println(s);
          }

          可以看到,在forEach方法中,其實(shí)是調(diào)用了java.lang.invoke.LambdaMetafactory#metafactory方法,該方法的第四個(gè)參數(shù)implMethod指定了方法實(shí)現(xiàn)。可以看到這里其實(shí)是調(diào)用了一個(gè)lambda$main$0方法進(jìn)行了輸出。

          再來看一個(gè)稍微復(fù)雜一點(diǎn)的,先對List進(jìn)行過濾,然后再輸出:

          public?static?void?main(String...?args)?{
          ????List?strList?=?ImmutableList.of("wupx",?"公眾號:wupx",?"博客:www.tianheyu.top");

          ????List?infoList?=?strList.stream().filter(string?->?string.contains("Hollis")).collect(Collectors.toList());

          ????infoList.forEach(?s?->?{?System.out.println(s);?}?);
          }

          反編譯后代碼如下:

          public?static?/*?varargs?*/?void?main(String?...?args)?{
          ????ImmutableList?strList?=?ImmutableList.of((Object)"wupx",?(Object)"\u516c\u4f17\u53f7\uff1awupx",?(Object)"\u535a\u5ba2\uff1awww.tianheyu.top");
          ????List<Object>?infoList=?strList.stream().filter((Predicate<String>)LambdaMetafactory.metafactory(null,?null,?null,?(Ljava/lang/Object;)Z,?lambda$main$0(java.lang.String?),?(Ljava/lang/String;)Z)()).collect(Collectors.toList());
          ????infoList.forEach((Consumer<Object>)LambdaMetafactory.metafactory(null,?null,?null,?(Ljava/lang/Object;)V,?lambda$main$1(java.lang.Object?),?(Ljava/lang/Object;)V)());
          }

          private?static?/*?synthetic?*/?void?lambda$main$1(Object?s)?{
          ????System.out.println(s);
          }

          private?static?/*?synthetic?*/?boolean?lambda$main$0(String?string)?{
          ????return?string.contains("wupx");
          }

          兩個(gè)lambda表達(dá)式分別調(diào)用了lambda$main$1和lambda$main$0兩個(gè)方法。

          所以,lambda表達(dá)式的實(shí)現(xiàn)其實(shí)是依賴了一些底層的api,在編譯階段,編譯器會(huì)把lambda表達(dá)式進(jìn)行解糖,轉(zhuǎn)換成調(diào)用內(nèi)部api的方式。


          可能遇到的坑

          泛型——當(dāng)泛型遇到重載?

          public class GenericTypes {

          public?static?void?method(List?list)?{??
          ????????System.out.println("invoke?method(List?list)");??
          ????}??

          ????public?static?void?method(List?list)?{??
          ????????System.out.println("invoke?method(List?list)");??
          ????}??
          }??

          上面這段代碼,有兩個(gè)重載的函數(shù),因?yàn)樗麄兊膮?shù)類型不同,一個(gè)是List另一個(gè)是List,但是,這段代碼是編譯通不過的。因?yàn)槲覀兦懊嬷v過,參數(shù)List和List編譯之后都被擦除了,變成了一樣的原生類型List,擦除動(dòng)作導(dǎo)致這兩個(gè)方法的特征簽名變得一模一樣。

          泛型——當(dāng)泛型遇到catch?

          泛型的類型參數(shù)不能用在Java異常處理的catch語句中。因?yàn)楫惓L幚硎怯蒍VM在運(yùn)行時(shí)刻來進(jìn)行的。由于類型信息被擦除,JVM是無法區(qū)分兩個(gè)異常類型MyException>和MyException

          泛型—當(dāng)泛型內(nèi)包含靜態(tài)變量

          public?class?StaticTest{
          ????public?static?void?main(String[]?args){
          ????????GT?gti?=?new?GT();
          ????????gti.var=1;
          ????????GT?gts?=?new?GT();
          ????????gts.var=2;
          ????????System.out.println(gti.var);
          ????}
          }
          class?GT{
          ????public?static?int?var=0;
          ????public?void?nothing(T?x){}
          }

          以上代碼輸出結(jié)果為:2!由于經(jīng)過類型擦除,所有的泛型類實(shí)例都關(guān)聯(lián)到同一份字節(jié)碼上,泛型類的所有靜態(tài)變量是共享的。

          自動(dòng)裝箱與拆箱——對象相等比較

          public?static?void?main(String[]?args)?{
          ????Integer?a?=?1000;
          ????Integer?b?=?1000;
          ????Integer?c?=?100;
          ????Integer?d?=?100;
          ????System.out.println("a?==?b?is?"?+?(a?==?b));
          ????System.out.println(("c?==?d?is?"?+?(c?==?d)));
          }

          輸出結(jié)果:

          a?==?b?is?false
          c?==?d?is?true

          在Java 5中,在Integer的操作上引入了一個(gè)新功能來節(jié)省內(nèi)存和提高性能。整型對象通過使用相同的對象引用實(shí)現(xiàn)了緩存和重用。

          適用于整數(shù)值區(qū)間-128 至 +127。

          只適用于自動(dòng)裝箱。使用構(gòu)造函數(shù)創(chuàng)建對象不適用。

          增強(qiáng)for循環(huán)

          for?(Student?stu?:?students)?{????
          ????if?(stu.getId()?==?2)?????
          ????????students.remove(stu);????
          }

          會(huì)拋出ConcurrentModificationException異常。

          Iterator是工作在一個(gè)獨(dú)立的線程中,并且擁有一個(gè) mutex 鎖。Iterator被創(chuàng)建之后會(huì)建立一個(gè)指向原來對象的單鏈索引表,當(dāng)原來的對象數(shù)量發(fā)生變化時(shí),這個(gè)索引表的內(nèi)容不會(huì)同步改變,所以當(dāng)索引指針往后移動(dòng)的時(shí)候就找不到要迭代的對象,所以按照 fail-fast 原則 Iterator 會(huì)馬上拋出java.util.ConcurrentModificationException異常。

          所以 Iterator 在工作的時(shí)候是不允許被迭代的對象被改變的。但你可以使用 Iterator 本身的方法remove()來刪除對象,Iterator.remove()?方法會(huì)在刪除當(dāng)前迭代對象的同時(shí)維護(hù)索引的一致性。


          總結(jié)

          前面介紹了12種Java中常用的語法糖。所謂語法糖就是提供給開發(fā)人員便于開發(fā)的一種語法而已。

          但是這種語法只有開發(fā)人員認(rèn)識。要想被執(zhí)行,需要進(jìn)行解糖,即轉(zhuǎn)成JVM認(rèn)識的語法。

          當(dāng)我們把語法糖解糖之后,你就會(huì)發(fā)現(xiàn)其實(shí)我們?nèi)粘J褂玫倪@些方便的語法,其實(shí)都是一些其他更簡單的語法構(gòu)成的。

          有了這些語法糖,我們在日常開發(fā)的時(shí)候可以大大提升效率,但是同時(shí)也要避免過渡使用。使用之前最好了解下原理,避免掉坑。


          ? ? ? ?
          ???
          內(nèi)存泄露的原因找到了,罪魁禍?zhǔn)拙尤皇荍ava TheadLocal
          Java 線程池中的線程復(fù)用是如何實(shí)現(xiàn)的?
          你編寫的Java代碼是咋跑起來的?

          覺得不錯(cuò),點(diǎn)個(gè)在看~

          瀏覽 55
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本黄在线播放 | 99色精品 | 豆花无码视频一区二区三区四区 | 婷婷六月综合 | 久久精品七区 |