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指令