詳解12個Java語法糖禽翼,再搞不懂屠橄,別說自己會Java

語法糖

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

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

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

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

解語法糖

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

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

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

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

一铛纬、switch 支持 String 與枚舉

前面提到過厌均,從Java 7 開始,Java語言中的語法糖在逐漸豐富告唆,其中一個比較重要的就是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;
        }
    }
}

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

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

二漫谷、 泛型

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

通常情況下惕稻,一個編譯器處理泛型有兩種方式:Code specialization和Code sharing蝙叛。

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

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

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

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

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

  • 移除所有的類型參數(shù)豫柬。

以下代碼:

Map<String, String> map = new HashMap<String, String>();  
map.put("name", "suxiansheng");  
map.put("wechat", "java");  
map.put("blog", "http://www.reibang.com/u/94111742c97c");  

解語法糖之后會變成:

Map map = new HashMap();  
map.put("name", "suxiansheng");  
map.put("wechat", "Java");  
map.put("blog", "http://www.reibang.com/u/94111742c97c");  

以下代碼:

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

類型擦除后會變成:

 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ù)在編譯時都會被擦除指么,泛型類并沒有自己獨(dú)有的Class類對象。比如并不存在List<String>.class或是List<Integer>.class晚唇,而只有List.class盗似。

三赫舒、 自動裝箱與拆箱

自動裝箱就是Java自動將原始類型值轉(zhuǎn)換成對應(yīng)的對象,比如將int的變量轉(zhuǎn)換成Integer對象并鸵,這個過程叫做裝箱鸳粉,反之將Integer對象轉(zhuǎn)換成int類型值扔涧,這個過程叫做拆箱枯夜。參考:一文讀懂什么是Java中的自動拆裝箱

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

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

先來看個自動裝箱的代碼:

 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);
}

再來看個自動拆箱的代碼:

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)容可以看出征讲,在裝箱的時候自動調(diào)用的是Integer的valueOf(int)方法诗箍。而在拆箱的時候自動調(diào)用的是Integer的intValue方法挽唉。

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

四 首昔、 方法變長參數(shù)

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

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

public static void main(String[] args)
    {
        print("suxiansheng", "Java", "博客:http://www.reibang.com/u/94111742c97c", "QQ:1210793511");
    }

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

反編譯后代碼:

 public static void main(String args[])
{
    print(new String[] {
        "java", "\u516C\u4F17\u53F7:suxiansheng", "\u535A\u5BA2\uFF1Ahttp://www.reibang.com/u/94111742c97c", "QQ\uFF1A1210793511"
    });
}

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

}

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

五 庇麦、 枚舉

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

要想看源碼奄薇,首先得有一個類吧馁蒂,那么枚舉類型到底是什么類呢蜘腌?是enum嗎撮珠?

答案很明顯不是,enum就和class一樣勺届,只是一個關(guān)鍵字免姿,他并不是一個類榕酒。

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

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類的波材,同時final關(guān)鍵字告訴我們,這個類也是不能被繼承的唯灵。

當(dāng)我們使用enmu來定義一個枚舉類型的時候埠帕,編譯器會自動幫我們創(chuàng)建一個final類型的類繼承Enum類玖绿,所以枚舉類型不能被繼承斑匪。

六 、 內(nèi)部類

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

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

outer.java里面定義了一個內(nèi)部類inner丝格,一旦編譯成功显蝌,就會生成兩個完全不同的.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;
        }
    }
}

以上代碼編譯后會生成兩個class文件:OutterClass$InnerClass.class 神郊、OutterClass.class 涌乳。

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

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

他會把兩個文件全部進(jìn)行反編譯,然后一起生成一個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;
}

七 柒室、條件編譯

—般情況下雄右,程序中的每一行代碼都要參加編譯纺讲。但有時候出于對程序代碼優(yōu)化的考慮熬甚,希望只對其中一部分內(nèi)容進(jìn)行編譯乡括,此時就需要在程序中加上條件,讓編譯器只對滿足條件的代碼進(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("Java, DEBUG!");
        }

        final boolean ONLINE = false;

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

反編譯后代碼如下:

public class ConditionalCompilation
{

    public ConditionalCompilation()
    {
    }

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

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

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

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

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

八 晓猛、 斷言

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

如果要開啟斷言檢查针炉,則需要用開關(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("Java");
        assert a != b : "suxiansheng";
        System.out.println("博客:http://www.reibang.com/u/94111742c97c");
    }
}

反編譯后代碼如下:

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\uFF1AJava");
    if(!$assertionsDisabled && a == b)
    {
        throw new AssertionError("Java");
    } else
    {
        System.out.println("\u535A\u5BA2\uFF1Awww.reibang.com/u/94111742c97c");
        return;
    }
}

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

}

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

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

九 衡招、 數(shù)值字面量

在java 7中始腾,數(shù)值字面量浪箭,不管是整數(shù)還是浮點(diǎn)數(shù)山林,都允許在數(shù)字之間插入任意多個下劃線。這些下劃線不會對字面量的數(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)常會用到的拂苹,他會比for循環(huán)要少寫很多代碼瓢棒,那么這個語法糖背后是如何實(shí)現(xiàn)的呢音羞?

public static void main(String... args) {
    String[] strs = {"suxiansehng", "Java", "博客:www.reibang.com/u/94111742c97c"};
    for (String s : strs) {
        System.out.println(s);
    }
    List<String> strList = ImmutableList.of("suxiansheng", "java", "博客:www.reibang.com/u/94111742c97c");
    for (String s : strList) {
        System.out.println(s);
    }
}

反編譯后代碼如下:

public static transient void main(String args[])
{
    String strs[] = {
        "suxiansheng", "\u516C\u4F17\u53F7\uFF1AJava", "\u535A\u5BA2\uFF1Awww.reibang.com/u/94111742c97c"
    };
    String args1[] = strs;
    int i = args1.length;
    for(int j = 0; j < i; j++)
    {
        String s = args1[j];
        System.out.println(s);
    }

    List strList = ImmutableList.of("suxiansheng", "\u516C\u4F17\u53F7\uFF1AJava", "\u535A\u5BA2\uFF1Awww.reibang.com/u/94111742c97c");
    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ù)庫連接等開銷非常昂貴的資源菜谣,用完之后必須及時通過close方法將其關(guān)閉,否則資源會一直處于打開狀態(tài)媳危,可能會導(dǎo)致內(nèi)存泄露等問題待笑。

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

public static void main(String[] args) {
    BufferedReader br = null;
    try {
        String line;
        br = new BufferedReader(new FileReader("d:\\hollischuang.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:\\ hollischuang.xml"))) {
        String line;
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }
    } catch (IOException e) {
        // handle exception
    }
}

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

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

public static transient void main(String args[])
    {
        BufferedReader br;
        Throwable throwable;
        br = new BufferedReader(new FileReader("d:\\ hollischuang.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á)式霉涨,有人可能會有質(zhì)疑,因?yàn)榫W(wǎng)上有人說他并不是語法糖惭适。其實(shí)我想糾正下這個說法笙瑟。

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

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

public static void main(String... args) {
    List<String> strList = ImmutableList.of("suxiansheng", "Java", "博客:www.reibang.com/u/94111742c97c");

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

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

反編譯后代碼如下:

public static /* varargs */ void main(String ... args) {
    ImmutableList strList = ImmutableList.of((Object)"Java", (Object)"\u516c\u4f17\u53f7\uff1aJava", (Object)"\u535a\u5ba2\uff1awww.reibang.com/u/94111742c97c");
    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方法导而,該方法的第四個參數(shù)implMethod指定了方法實(shí)現(xiàn)∠ゾ耍可以看到這里其實(shí)是調(diào)用了一個lambdamain0方法進(jìn)行了輸出嗡载。

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

public static void main(String... args) {
    List<String> strList = ImmutableList.of("suxiansheng", "Java, "博客:www.reibang.com/u/94111742c97c");

    List HollisList = strList.stream().filter(string -> string.contains("Java")).collect(Collectors.toList());

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

反編譯后代碼如下:

public static /* varargs */ void main(String ... args) {
    ImmutableList strList = ImmutableList.of((Object)"Java", (Object)"\u516c\u4f17\u53f7\uff1aJava", (Object)"\u535a\u5ba2\uff1awww.reibang.com/u/94111742c97c");
    List<Object> HollisList = 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());
    HollisList.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("Hollis");
}

兩個lambda表達(dá)式分別調(diào)用了lambdamain1和lambdamain0兩個方法洼滚。

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

可能遇到的坑

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

public class GenericTypes {

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

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

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

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

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

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

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

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

自動裝箱與拆箱——對象相等比較

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的操作上引入了一個新功能來節(jié)省內(nèi)存和提高性能帝美。整型對象通過使用相同的對象引用實(shí)現(xiàn)了緩存和重用碍彭。

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

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

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

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

會拋出ConcurrentModificationException異常舞箍。

Iterator是工作在一個獨(dú)立的線程中舰褪,并且擁有一個 mutex 鎖。 Iterator被創(chuàng)建之后會建立一個指向原來對象的單鏈索引表疏橄,當(dāng)原來的對象數(shù)量發(fā)生變化時占拍,這個索引表的內(nèi)容不會同步改變,所以當(dāng)索引指針往后移動的時候就找不到要迭代的對象捎迫,所以按照 fail-fast 原則 Iterator 會馬上拋出java.util.ConcurrentModificationException異常晃酒。參考:一不小心就讓Java開發(fā)者踩坑的fail-fast是個什么鬼?

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

總結(jié)

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

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

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

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

寫在最后

最后何什,歡迎做Java的工程師朋友們加入Java高級架構(gòu)進(jìn)階Qqun:963944895

群內(nèi)有技術(shù)大咖指點(diǎn)難題,還提供免費(fèi)的Java架構(gòu)學(xué)習(xí)資料(里面有高可用等龙、高并發(fā)处渣、高性能及分布式、Jvm性能調(diào)優(yōu)蛛砰、Spring源碼罐栈,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點(diǎn)的架構(gòu)資料)

比你優(yōu)秀的對手在學(xué)習(xí)泥畅,你的仇人在磨刀荠诬,你的閨蜜在減肥,隔壁老王在練腰位仁, 我們必須不斷學(xué)習(xí)柑贞,否則我們將被學(xué)習(xí)者超越!

趁年輕聂抢,使勁拼钧嘶,給未來的自己一個交代!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末琳疏,一起剝皮案震驚了整個濱河市有决,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌空盼,老刑警劉巖书幕,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異揽趾,居然都是意外死亡台汇,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進(jìn)店門但骨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來励七,“玉大人,你說我怎么就攤上這事奔缠÷犹В” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵校哎,是天一觀的道長两波。 經(jīng)常有香客問我瞳步,道長,這世上最難降的妖魔是什么腰奋? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任单起,我火速辦了婚禮,結(jié)果婚禮上劣坊,老公的妹妹穿的比我還像新娘嘀倒。我一直安慰自己,他們只是感情好局冰,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布测蘑。 她就那樣靜靜地躺著,像睡著了一般康二。 火紅的嫁衣襯著肌膚如雪碳胳。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天沫勿,我揣著相機(jī)與錄音挨约,去河邊找鬼。 笑死产雹,一個胖子當(dāng)著我的面吹牛诫惭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播洽故,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼贝攒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了时甚?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤哈踱,失蹤者是張志新(化名)和其女友劉穎荒适,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體开镣,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡刀诬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了邪财。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片陕壹。...
    茶點(diǎn)故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖树埠,靈堂內(nèi)的尸體忽然破棺而出糠馆,到底是詐尸還是另有隱情,我是刑警寧澤怎憋,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布又碌,位于F島的核電站九昧,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏毕匀。R本人自食惡果不足惜铸鹰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望皂岔。 院中可真熱鬧蹋笼,春花似錦、人聲如沸躁垛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缤苫。三九已至速兔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間活玲,已是汗流浹背涣狗。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留舒憾,地道東北人镀钓。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像镀迂,于是被迫代替她去往敵國和親丁溅。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 《深入理解Java虛擬機(jī)》筆記_第一遍 先取看完這本書(JVM)后必須掌握的部分探遵。 第一部分 走近 Java 從傳...
    xiaogmail閱讀 5,067評論 1 34
  • 對象的創(chuàng)建與銷毀 Item 1: 使用static工廠方法窟赏,而不是構(gòu)造函數(shù)創(chuàng)建對象:僅僅是創(chuàng)建對象的方法,并非Fa...
    孫小磊閱讀 1,967評論 0 3
  • “泛型”這個術(shù)語的意思是:"適用于許多許多的類型”箱季。如何做到這一點(diǎn)呢涯穷,正是通過解耦類或方法與所使用的類型之間的約束...
    王偵閱讀 1,123評論 0 0
  • 深入理解Class對象 RRTI的概念以及Class對象作用 認(rèn)識Class對象之前,先來了解一個概念藏雏,RTTI(...
    架構(gòu)師springboot閱讀 1,557評論 0 3
  • 一次拷况,我在二叔公面前說著想自己曬些白菜干,煲湯熬粥時用得上掘殴。 當(dāng)時我是隨口的一說赚瘦,然二叔公卻是嚴(yán)實(shí)的記在心里。 幾...
    添一抹嵐閱讀 1,058評論 16 9