国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Java語法糖的編譯結(jié)果分析(一)

zhangxiangliang / 2671人閱讀

摘要:操作對應(yīng)字節(jié)碼中的個字節(jié)我們可以看到最關(guān)鍵的操作其實就是調(diào)用的其實是類的方法,此方法的入?yún)㈩愋褪牵祷刂殿愋褪牵g過來就是類的方法,執(zhí)行完后將獲得的結(jié)果做了,檢查返回的對象類型是否是。

語法糖(Syntactic Sugar)的出現(xiàn)是為了降低我們編寫某些代碼時陷入的重復(fù)或繁瑣,這使得我們使用語法糖后可以寫出簡明而優(yōu)雅的代碼。在Java中不加工的語法糖代碼運行時可不會被虛擬機接受,因此編譯器為了讓這些含有語法糖的代碼正常工作其實需要對這些代碼進行加工,經(jīng)過編譯器在生成class字節(jié)碼的階段完成解語法糖(desugar)的過程,那么這些語法糖最終究竟被編譯成了什么呢,在這里列舉了如下的一些Java典型的語法糖,結(jié)合實例和它們的編譯結(jié)果分析一下。本文為該系列的第一篇。

泛型和類型擦除

java的泛型實際上是偽泛型,在編譯后編譯器會擦除泛型對象的參數(shù)化類型,也就是說源代碼中的類型其實都會擦除,最終成為class字節(jié)碼中的Object類型,賦值等操作也就會直接轉(zhuǎn)換為強制的類型轉(zhuǎn)換,這樣做無風(fēng)險的原因是在編譯的標注檢查階段其實已經(jīng)進行了泛型的檢查,如果當時無法通過檢查的話編譯無法通過。

另外,這個泛型信息不是真的就此丟掉了,class字節(jié)碼中還是會保留Signature屬性來記錄泛型對象在源碼中的參數(shù)化類型。

代碼:

public class Main {
    public static void main(String[] args) {
        List strList = new ArrayList<>();
        strList.add("aaa");
        String strEle = strList.get(0);
    }
}

main方法在javap編譯后的字節(jié)碼

public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: new           #2                  // class java/util/ArrayList
         3: dup
         4: invokespecial #3                  // Method java/util/ArrayList."":()V
         7: astore_1
         8: aload_1
         9: ldc           #4                  // String aaa
        11: invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
        16: pop
        17: aload_1
        18: iconst_0
        19: invokeinterface #6,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
        24: checkcast     #7                  // class java/lang/String
        27: astore_2
        28: return

上面我們演示了一個參數(shù)化類型為StringList的泛型對象strListaddget操作:

add操作:對應(yīng)字節(jié)碼中的8~16個字節(jié):我們可以看到最關(guān)鍵的add操作其實就是

invokeinterface #5,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z

調(diào)用的其實是java/util/List類的add方法,此方法的入?yún)㈩愋褪?b>Ljava/lang/Object;,返回值類型是Z,翻譯過來就是List類的boolean add(Object o)方法,這里并沒有參數(shù)化類型String的什么事情。

get操作:對應(yīng)字節(jié)碼中的17~27個字節(jié):我們可以看到最關(guān)鍵的get操作其實就是

invokeinterface #6,  2            // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
checkcast     #7                  // class java/lang/String

調(diào)用的其實是java/util/List類的get方法,此方法的入?yún)㈩愋褪?b>I,返回值類型是Ljava/lang/Object;,翻譯過來就是List類的Object get(int i)方法,執(zhí)行完后將獲得的結(jié)果做了checkcast,檢查返回的對象類型是否是String

從上面的分析我們不難看出,Java泛型到了編譯出結(jié)果的時候參數(shù)化類型已經(jīng)沒有什么作用了,就是簡單做了強制的類型轉(zhuǎn)換。這段去掉了語法糖的代碼如下:

public class Main {
    public static void main(String[] args) {
        List strList = new ArrayList();
        strList.add((Object)"aaa");
        String strEle = (String) strList.get(0);
    }
}

Java的泛型是偽泛型的原因如上,在運行時這個代碼完全體會不到不同參數(shù)化類型的List有什么不同。而泛型參數(shù)化類型的用武之地更多的是在編譯時用來做檢驗類型使用的,正常情況下如果編譯時通過檢驗當然就不會在運行期類型強制轉(zhuǎn)換的時候出現(xiàn)異常,更何況其實字節(jié)碼中還有checkcast的顯式類型檢查。

如果使用javac-g:vars參數(shù)來保留class字節(jié)碼中方法的局部變量信息,那么我們可以看到額外的信息:

LocalVariableTable:
Start  Length  Slot  Name   Signature
    0      29     0  args   [Ljava/lang/String;
    8      21     1 strList   Ljava/util/List;
   28       1     2 strEle   Ljava/lang/String;
LocalVariableTypeTable:
Start  Length  Slot  Name   Signature
    8      21     1 strList   Ljava/util/List;

其中的LocalVariableTypeTable屬性記錄了strList的擦除泛型前的類型:Ljava/util/List;,翻譯過來其實就是List,如果在反射中獲取泛型變量的類型元信息,其來源其實就是這個Signature。這也算是Java為了彌補因類型擦除而導(dǎo)致的class字節(jié)碼中的類型數(shù)據(jù)缺失而做出的額外努力吧。

變長參數(shù):編譯后變成數(shù)組類型的參數(shù)

變長參數(shù)會被編譯成為數(shù)組類型的參數(shù),變長參數(shù)只能出現(xiàn)在參數(shù)列表的結(jié)尾以消除歧義。

代碼:

public class Main {
    public static void method(String... args) {

    }
}

method方法在編譯后:

public static void method(java.lang.String...);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_VARARGS
    Code:
      stack=0, locals=1, args_size=1
         0: return

我們可以清楚地看到方法的特征符是([Ljava/lang/String;)V,即參數(shù)是[Ljava/lang/String;,翻譯過來就是String[],即數(shù)組類型。

這段去掉了語法糖的代碼如下:

public class Main {
    public static void method(String[] args) {

    }
}
自動裝箱拆箱

編譯后裝箱通過valueOf()變成了對象,拆箱通過xxxValue()變成了原始類型值。

代碼:

public class Main {
    public static void main(String[] args) {
        Integer x = 1;
        int y = x;
    }
}

main方法編譯后:

    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=3, args_size=1
         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: return

這里我們可以明顯看到Integer x = 1;編譯時x轉(zhuǎn)換成了java/lang/Integer.valueOf生成的引用類型Integer變量,而int y = x;編譯時y轉(zhuǎn)換成了java/lang/Integer.intValue生成的原始類型int變量。

去掉了語法糖的代碼如下:

public class Main {
    public static void main(String[] args) {
        Integer x = Integer.valueOf(1);
        int y = x.intValue();
    }
}
遍歷循環(huán)

編譯后變成了迭代器遍歷。

代碼:

public class Main {
    public static void main(String[] args) {
        List strList = new ArrayList<>();
        for (String str : strList) {
            System.out.println(str);
        }
    }
}

main方法編譯后:

public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=1
         0: new           #2                  // class java/util/ArrayList
         3: dup
         4: invokespecial #3                  // Method java/util/ArrayList."":()V
         7: astore_1
         8: aload_1
         9: invokeinterface #4,  1            // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
        14: astore_2
        15: aload_2
        16: invokeinterface #5,  1            // InterfaceMethod java/util/Iterator.hasNext:()Z
        21: ifeq          44
        24: aload_2
        25: invokeinterface #6,  1            // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
        30: checkcast     #7                  // class java/lang/String
        33: astore_3
        34: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
        37: aload_3
        38: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        41: goto          15
        44: return
      StackMapTable: number_of_entries = 2
        frame_type = 253 /* append */
          offset_delta = 15
          locals = [ class java/util/List, class java/util/Iterator ]
        frame_type = 250 /* chop */
          offset_delta = 28

從上面我們可以看到遍歷循環(huán)的語法糖被替換成了List.iterator的循環(huán)操作,用下面的代碼即可表達這段編譯后的去掉語法糖的代碼:

public class Main {
    public static void main(String[] args) {
        List strList = new ArrayList<>();
        Iterator strIterator = strList.iterator();
        while(strIterator.hasNext()){
            System.out.println((String) strIterator.next());
        }
    }
}
條件編譯

編譯后將常量不可達條件分支直接在編譯結(jié)果中消除掉。

代碼:

public class Main {
    public static void main(String[] args) {
        if (true) {
            System.out.println("Yes");
        } else {
            System.out.println("No");
        }
    }
}

main方法編譯后:

public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String Yes
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
}

從上面我們可以看到常量不可達條件直接就在編譯結(jié)果中略去了,仿佛就沒有這個分支一樣,用下面的代碼即可表達這段編譯后的去掉語法糖的代碼:

public class Main {
    public static void main(String[] args) {
        System.out.println("Yes");
    }
}

需要注意的是這里強調(diào)的是常量不可達條件才會略去,比如直接就是true的分支或者1==1這樣的分支是會保留的,如果是變量經(jīng)過運算后才被確定為不可達是不會發(fā)生這種條件編譯的,比如:

public class Main {
    public static void main(String[] args) {
        int i = 1;
        if (i==1) {
            System.out.println("Yes");
        } else {
            System.out.println("No");
        }
    }
}

編譯后還是會走ifelse判斷:

public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: iconst_1
         1: istore_1
         2: iload_1
         3: iconst_1
         4: if_icmpne     18
         7: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        10: ldc           #3                  // String Yes
        12: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        15: goto          26
        18: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        21: ldc           #5                  // String No
        23: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        26: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      27     0  args   [Ljava/lang/String;
            2      25     1     i   I
      StackMapTable: number_of_entries = 2
        frame_type = 252 /* append */
          offset_delta = 18
          locals = [ int ]
        frame_type = 7 /* same */
}
內(nèi)部類

內(nèi)部類即是類中類,我們來看這個簡單的例子:

代碼:

public class Main {

    class Person{
        String name;
        Integer age;

        public Person(String name, Integer age) {
            this.name = name;
            this.age = age;
        }
    }

    public void demo(String[] args) {
        Person person = new Person("ccc", 20);
    }
}

來看看編譯后的結(jié)果,編譯后會將內(nèi)部類Person多帶帶拿出來做編譯,不過語法糖褪去后編譯器做了一些處理,比如為Person類加了與外部的Main類相聯(lián)系的字段this$0

...
class top.jinhaoplus.Main$Person
...
{
  java.lang.String name;
    descriptor: Ljava/lang/String;
    flags:

  java.lang.Integer age;
    descriptor: Ljava/lang/Integer;
    flags:

  final top.jinhaoplus.Main this$0;
    descriptor: Ltop/jinhaoplus/Main;
    flags: ACC_FINAL, ACC_SYNTHETIC

  public top.jinhaoplus.Main$Person(top.jinhaoplus.Main, java.lang.String, java.lang.Integer);
    descriptor: (Ltop/jinhaoplus/Main;Ljava/lang/String;Ljava/lang/Integer;)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=4, args_size=4
         0: aload_0
         1: aload_1
         2: putfield      #1                  // Field this$0:Ltop/jinhaoplus/Main;
         5: aload_0
         6: invokespecial #2                  // Method java/lang/Object."":()V
         9: aload_0
        10: aload_2
        11: putfield      #3                  // Field name:Ljava/lang/String;
        14: aload_0
        15: aload_3
        16: putfield      #4                  // Field age:Ljava/lang/Integer;
        19: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      20     0  this   Ltop/jinhaoplus/Main$Person;
            0      20     1 this$0   Ltop/jinhaoplus/Main;
            0      20     2  name   Ljava/lang/String;
            0      20     3   age   Ljava/lang/Integer;
}

這里翻譯過來類似這樣的:

class Person {
    String name;
    Integer age;
    final Main this$0;

    public Person(final Main this$0, String name, Integer age) {
        this.this$0 = this$0;
        this.name = name;
        this.age = age;
    }
}

public class Main {
    public void demo(String[] args) {
        Person person = new Person(this, "ccc", 20);
    }
}

至于為什么需要這個多余的外部類的字段呢,其實是為了通過它來獲取外部類中的信息,我們對例子加以改造,添加兩個外部類的字段secret1secret2

public class Main {

    private String secret1;
    private String secret2;

    class Person{
        String name;
        Integer age;

        public Person(String name, Integer age) {
            this.name = name;
            this.age = age;
        }

        public void getSecrets(){
            System.out.println(secret1);
            System.out.println(secret2);
        }
    }

    public void demo(String[] args) {
        Person person = new Person("ccc", 20);
        person.getSecrets();
    }
}

這個時候編譯的結(jié)果是Main為了對外提供自己屬性的值自動添加了靜態(tài)方法access$000(Main)access$100(Main)

static java.lang.String access$000(top.jinhaoplus.Main);
    descriptor: (Ltop/jinhaoplus/Main;)Ljava/lang/String;
    flags: ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field secret1:Ljava/lang/String;
         4: areturn
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0    x0   Ltop/jinhaoplus/Main;

static java.lang.String access$100(top.jinhaoplus.Main);
    descriptor: (Ltop/jinhaoplus/Main;)Ljava/lang/String;
    flags: ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #1                  // Field secret2:Ljava/lang/String;
         4: areturn
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0    x0   Ltop/jinhaoplus/Main;
}

而內(nèi)部類編譯后的結(jié)果在獲取外部類的屬性的時候其實就是調(diào)用暴露出的這些方法:

public void getSecret();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: aload_0
         4: getfield      #1                  // Field this$0:Ltop/jinhaoplus/Main;
         7: invokestatic  #6                  // Method top/jinhaoplus/Main.access$000:(Ltop/jinhaoplus/Main;)Ljava/lang/String;
        10: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        13: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
        16: aload_0
        17: getfield      #1                  // Field this$0:Ltop/jinhaoplus/Main;
        20: invokestatic  #8                  // Method top/jinhaoplus/Main.access$100:(Ltop/jinhaoplus/Main;)Ljava/lang/String;
        23: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        26: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      27     0  this   Ltop/jinhaoplus/Main$Person;
}

翻譯過來其實就是這樣子的:

class Person {
    String name;
    Integer age;
    final Main this$0;

    public Person(final Main this$0, String name, Integer age) {
        this.this$0 = this$0;
        this.name = name;
        this.age = age;
    }

    public void getSecrets(){
        System.out.println(Main.access$000(this$0));
        System.out.println(Main.access$100(this$0));
    }
}

public class Main {
    private String secret1;
    private String secret2;

    public void demo(String[] args) {
        Person person = new Person(this, "ccc", 20);
    }

    public static String access$000(Main main) {
        return main.secret1;
    }

    public static String access$100(Main main) {
        return main.secret2;
    }
}

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/77050.html

相關(guān)文章

  • Java語法糖的編譯結(jié)果分析(二)

    摘要:因此,對應(yīng)地我們可以翻譯這段二進制字節(jié)碼為這樣的代碼注意,這段代碼并不能通過編譯,因為源碼這一層是不允許直接繼承的,這個繼承過程只允許在編譯器內(nèi)部解語法糖的過程中被編譯器添加,添加之后的類才會有的訪問標識符。 語法糖(Syntactic Sugar)的出現(xiàn)是為了降低我們編寫某些代碼時陷入的重復(fù)或繁瑣,這使得我們使用語法糖后可以寫出簡明而優(yōu)雅的代碼。在Java中不加工的語法糖代碼運行時可...

    LeviDing 評論0 收藏0
  • Hollis原創(chuàng)|不了解這12個語法糖,別說你會Java

    摘要:但其實,虛擬機并不支持這些語法糖。方式為每個泛型類型創(chuàng)建唯一的字節(jié)碼表示,并且將該泛型類型的實例都映射到這個唯一的字節(jié)碼表示上。GitHub 2.5k Star 的Java工程師成神之路 ,不來了解一下嗎); GitHub 2.5k Star 的Java工程師成神之路 ,真的不來了解一下嗎); GitHub 2.5k Star 的Java工程師成神之路 ,真的確定不來了解一下嗎); 本文從 ...

    番茄西紅柿 評論0 收藏0
  • Hollis原創(chuàng)|不了解這12個語法糖,別說你會Java

    摘要:但其實,虛擬機并不支持這些語法糖。方式為每個泛型類型創(chuàng)建唯一的字節(jié)碼表示,并且將該泛型類型的實例都映射到這個唯一的字節(jié)碼表示上。GitHub 2.5k Star 的Java工程師成神之路 ,不來了解一下嗎); GitHub 2.5k Star 的Java工程師成神之路 ,真的不來了解一下嗎); GitHub 2.5k Star 的Java工程師成神之路 ,真的確定不來了解一下嗎); 本文從 ...

    番茄西紅柿 評論0 收藏0
  • Hollis原創(chuàng)|不了解這12個語法糖,別說你會Java

    摘要:但其實,虛擬機并不支持這些語法糖。方式為每個泛型類型創(chuàng)建唯一的字節(jié)碼表示,并且將該泛型類型的實例都映射到這個唯一的字節(jié)碼表示上。GitHub 2.5k Star 的Java工程師成神之路 ,不來了解一下嗎); GitHub 2.5k Star 的Java工程師成神之路 ,真的不來了解一下嗎); GitHub 2.5k Star 的Java工程師成神之路 ,真的確定不來了解一下嗎); 本文從 ...

    yy13818512006 評論0 收藏0
  • JAVA語法糖和語法編譯

    摘要:提供給了用戶大量的語法糖,比如泛型自動裝箱拆箱循環(huán)變長參數(shù)內(nèi)部類枚舉類斷言新特性方法引用等解語法糖語法糖的存在主要是方便開發(fā)人員使用。 首先,部分總結(jié)文字引用 簡書作者:Eric新之助 。鏈接:https://www.jianshu.com/p/4de08deb6ba4 已獲得授權(quán) showImg(https://segmentfault.com/img/bVbfuX9?w=646&...

    weakish 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<