1. Foreach和泛型語法糖
Map<String,String> map = newHashMap<String,String>();
for(Entry<String, String> iter:test.map.entrySet()){
Stringstr = iter.getValue();
}
--看看字節碼是什麼內容:
8:aload_1
9: getfield #27 // Field map:Ljava/util/Map;
12: invokeinterface #44, 1 // InterfaceMethod java/util/Map.e
ntrySet:()Ljava/util/Set;
17: invokeinterface #48, 1 // InterfaceMethodjava/util/Set.i
terator:()Ljava/util/Iterator;
22: astore_3
23: goto 47
26: aload_3
27: invokeinterface #54, 1 // InterfaceMethodjava/util/Itera
tor.next:()Ljava/lang/Object;
32: checkcast #60 // class java/util/Map$Entry
35: astore_2
36: aload_2
37: invokeinterface #62, 1 // InterfaceMethod java/util/Map$E
ntry.getValue:()Ljava/lang/Object;
42: checkcast #65 // class java/lang/String
45: astore 4
47: aload_3
48: invokeinterface #67, 1 //InterfaceMethod java/util/Itera
tor.hasNext:()Z
53: ifne 26
--可以看到紅色那些內容,說明實際上編譯器真正使用的還是正常的for循環加迭代器遍歷的套路,只不過爲了讓使用者感覺方便創造了foreach這種看似簡單易用的語法糖。還有綠色的checkcast,說明Map<String,String> 這個泛型其實也是顆語法糖,跟C++不一樣,JVM並不會創建一個Map<String,String>類型,只是編譯器在每個使用到Map<String,String>的代碼中,進行了類型轉化,可以根據InterfaceMethodjava/util/Map$Entry.getValue:()Ljava/lang/Object;可知道iter.getValue()實際返回值只是Object對象,編譯器添加了向下轉化代碼checkcast,所以整個代碼經過編譯器轉化後應該是這樣的:String str = (String)iter.getValue();
2. 接口文件的字節碼內容
接口文件字節碼的內容是什麼呢?這是個有意思的問題,首先看到接口文件其實也是生成一份.class文件,再看看字節碼經過javap轉譯後是什麼內容:
Classfile/D:/javadevelop/eclipse/user/workspace/RedisDao/bin/RedisDao.class
Lastmodified 2017-7-19; size 3918 bytes
MD5checksum 1389d391e77bd4dba7e05d39f38182b9
Compiled from "RedisDao.java"
public interface RedisDao
minorversion: 0
major version: 52
flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
Constant pool:
。。。。。。省略
{
public abstract java.lang.StringbatchSetString(redis.clients.jedis.Jedis, jav
a.lang.String[], boolean);
descriptor:(Lredis/clients/jedis/Jedis;[Ljava/lang/String;Z)Ljava/lang/Stri
ng;
flags: ACC_PUBLIC, ACC_ABSTRACT
public abstract java.util.List<java.lang.String>batchGetString(redis.clients.
jedis.Jedis, java.lang.String[]);
descriptor: (Lredis/clients/jedis/Jedis;[Ljava/lang/String;)Ljava/util/List;
flags: ACC_PUBLIC, ACC_ABSTRACT
Signature: #10 // (Lredis/clients/jedis/Jedis;[Ljav
a/lang/String;)Ljava/util/List<Ljava/lang/String;>;
}
--是的,其實跟正常類的字節碼佈局基本一樣,只是方法都被編譯器加上了abstract關鍵字(在源碼中並沒寫該關鍵字),而且沒有CODE段。所以說所謂的“接口”其實本質上就是被編譯器閹割(限制)的類而且,就像一個普通人皈依了某宗教,被教律限制了不能做這不能做那,但是對於JVM而言,跟正常類區別不大。另外有特點,如果在定義接口時沒有寫訪問控制權限和抽象關鍵字,編譯器會在生成字節碼時在接口前面添加public abstract,所以出現了一個這麼特點:你可以不寫public abstract,編譯器會爲你加上,但是你不能寫其他限定詞,否則編譯器就報錯不通過。
2. 抽象類的字節碼內容:
Classfile /D:/javadevelop/eclipse/user/workspace/RedisDao/bin/ChildTest.class
Last modified 2017-7-20; size 671 bytes
MD5 checksum 64cf89af9ee1f820eac71c6f59759a78
Compiled from "ChildTest.java"
public abstractclass ChildTest extends test
minor version: 0
majorversion: 52
flags: ACC_PUBLIC, ACC_SUPER, ACC_ABSTRACT
Constant pool:
。。。。省略
protected abstractvoid fun();
descriptor: ()V
flags: ACC_PROTECTED, ACC_ABSTRACT
。。。。省略
--可見抽象類和正常類的佈局 也基本相同,並且抽象類也是獨立生成一個.class文件的,所以說“接口”和“抽象類”其實更多是java上的概念,而這個概念其實是對正常類的閹割,這不能做,那不能做,而這個閹割由編譯器來實現(如果違反閹割規則編譯器不允許通過編譯)。但是從字節碼角度看,其實相差不大,都是正常的.class佈局。
3. 從字節碼角度看try、catch、finally機制
public void fun(){
try{
Filefile = new File("");
}catch(Exceptione){
e.printStackTrace();
return;
}finally{
((Throwable) e).getMessage();
}
}
--這段代碼編譯成字節碼:
public void fun();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=3, args_size=1
0: new #34 // class java/io/File
3: dup
4: ldc #36 // String
6: invokespecial #38 // Methodjava/io/File."<init>":(L
java/lang/String;)V
9: astore_1
10: goto 44
13: astore_1
14: aload_1
15: invokevirtual #41 // Methodjava/lang/Exception.prin
tStackTrace:()V
18: aload_0
19: getfield #46 // Field e:Ljava/lang/Object;
22: checkcast #50 // class java/lang/Throwable
25: invokevirtual #52 // Methodjava/lang/Throwable.getM
essage:()Ljava/lang/String;
28: pop
29: return
30: astore_2
31: aload_0
32: getfield #46 // Field e:Ljava/lang/Object;
35: checkcast #50 // class java/lang/Throwable
38: invokevirtual #52 // Methodjava/lang/Throwable.getM
essage:()Ljava/lang/String;
41: pop
42: aload_2
43: athrow
44: aload_0
45: getfield #46 // Field e:Ljava/lang/Object;
48: checkcast #50 // class java/lang/Throwable
51: invokevirtual #52 // Methodjava/lang/Throwable.getM
essage:()Ljava/lang/String;
54: pop
55: return
Exception table:
from to target type
0 10 13 Class java/lang/Exception
0 18 30 any
LineNumberTable:
line 28: 0
line 29: 10
line 30: 14
line 33: 18
line 31: 29
line 32: 30
line 33: 31
line 34: 42
line 33: 44
line 35: 55
LocalVariableTable:
Start Length Slot Name Signature
0 56 0 this LChildTest;
14 16 1 e Ljava/lang/Exception;
StackMapTable: number_of_entries = 3
frame_type = 77 /* same_locals_1_stack_item */
stack = [ class java/lang/Exception ]
frame_type = 80 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]
frame_type = 13 /* same */
如果運行時不拋出異常則按綠色標識的CODE段的正常流程走,看看正常流程是怎麼走的:1.執行代碼File file = new File("");,2. Goto 第44字節開始的命令,3.第44字節開始的命令就是((Throwable) e).getMessage();
如果運行時拋出異常呢?注意紅色那段,這是Exception table,當運行時try模塊拋出異常後,第一步就是去查詢Exception table,紅色那兩行是什麼意思呢?第一行意思就是當從0到10字節中拋出類型爲Exception或它的子類異常時,則指令應該跳轉到第13字節指令,第13字節開始的指令就是catch模塊的e.printStackTrace();,順着執行,然後執行finally模塊中的代碼。最後才執行catch模塊的return。第二行意思就是當從0到18字節指令也就是try和catch兩個模塊中拋出任何其他類型的異常時,第一步跳轉到第30字節指令,也就是finally模塊的代碼。
這樣一來就保證了無論如何finally模塊的代碼都必須執行,並且在return之前執行。
如果把finally模塊修改下:
finally{
thrownew Exception("zrf");
}
則字節碼如下:
public void fun() throws java.lang.Exception;
descriptor: ()V
flags: ACC_PUBLIC
Exceptions:
throws java.lang.Exception
22: new #35 // class java/lang/Exception
25: dup
26: ldc #47 // String zrf
28: invokespecial #49 // Method java/lang/Exception."<in
it>":(Ljava/lang/String;)V
31: athrow
--可見在fun方法的字節碼中增加了一個Exceptions: throws java.lang.Exception段,而且在finally模塊指令中執行athrow指令