04、对象的创建和访问指令
Java 是一门面向对象的编程语言,那么 Java 虚拟机是如何从字节码层面进行支持的呢?
1)创建指令
数组也是一种对象,但它创建的字节码指令和普通的对象不同。创建数组的指令有三种:
newarray:创建基本数据类型的数组
anewarray:创建引用类型的数组
multianewarray:创建多维数组
普通对象的创建指令只有一个,就是 new,它会接收一个操作数,指向常量池中的一个索引,表示要创建的类型。
举例来说。
public void newObject() { String name = new String("沉默王二"); File file = new File("无愁河的浪荡汉子.book"); int [] ages = {}; }
通过 jclasslib 看一下 newObject() 方法的字节码指令。
new #13 <java/lang/String>,创建一个 String 对象。
new #15 <java/io/File>,创建一个 File 对象。
newarray 10 (int),创建一个 int 类型的数组。
2)字段访问指令
字段可以分为两类,一类是成员变量,一类是静态变量(static 关键字修饰的),所以字段访问指令可以分为两类:
访问静态变量:getstatic、putstatic。
访问成员变量:getfield、putfield,需要创建对象后才能访问。
举例来说。
public class Writer { private String name; static String mark = "作者"; public static void main(String[] args) { print(mark); Writer w = new Writer(); print(w.name); } public static void print(String arg) { System.out.println(arg); } }
通过 jclasslib 看一下 main() 方法的字节码指令。
getstatic #2 <com/itwanger/jvm/Writer.mark>,访问静态变量 mark
getfield #6 <com/itwanger/jvm/Writer.name>,访问成员变量 name
05、方法调用和返回指令
方法调用指令有 5 个,分别用于不同的场景:
invokevirtual:用于调用对象的成员方法,根据对象的实际类型进行分派,支持多态。
invokeinterface:用于调用接口方法,会在运行时搜索由特定对象实现的接口方法进行调用。
invokespecial:用于调用一些需要特殊处理的方法,包括构造方法、私有方法和父类方法。
invokestatic:用于调用静态方法。
invokedynamic:用于在运行时动态解析出调用点限定符所引用的方法,并执行。
举例来说。
public class InvokeExamples { private void run() { List ls = new ArrayList(); ls.add("难顶"); ArrayList als = new ArrayList(); als.add("学不动了"); } public static void print() { System.out.println("invokestatic"); } public static void main(String[] args) { print(); InvokeExamples invoke = new InvokeExamples(); invoke.run(); } }
我们用 javap -c InvokeExamples.class 来反编译一下。
Compiled from "InvokeExamples.java" public class com.itwanger.jvm.InvokeExamples { public com.itwanger.jvm.InvokeExamples(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return private void run(); Code: 0: new #2 // class java/util/ArrayList 3: dup 4: invokespecial #3 // Method java/util/ArrayList."<init>":()V 7: astore_1 8: aload_1 9: ldc #4 // String 难顶 11: invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z 16: pop 17: new #2 // class java/util/ArrayList 20: dup 21: invokespecial #3 // Method java/util/ArrayList."<init>":()V 24: astore_2 25: aload_2 26: ldc #6 // String 学不动了 28: invokevirtual #7 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z 31: pop 32: return public static void print(); Code: 0: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #9 // String invokestatic 5: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return public static void main(java.lang.String[]); Code: 0: invokestatic #11 // Method print:()V 3: new #12 // class com/itwanger/jvm/InvokeExamples 6: dup 7: invokespecial #13 // Method "<init>":()V 10: astore_1 11: aload_1 12: invokevirtual #14 // Method run:()V 15: return }
InvokeExamples 类有 4 个方法,包括缺省的构造方法在内。
1)InvokeExamples() 构造方法中
缺省的构造方法内部会调用超类 Object 的初始化构造方法:
`invokespecial #1 // Method java/lang/Object."<init>":()V`
2)成员方法 run() 中
invokeinterface #5, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
由于 ls 变量的引用类型为接口 List,所以 ls.add() 调用的是 invokeinterface 指令,等运行时再确定是不是接口 List 的实现对象 ArrayList 的 add() 方法。
invokevirtual #7 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
由于 als 变量的引用类型已经确定为 ArrayList,所以 als.add() 方法调用的是 invokevirtual 指令。
3)main() 方法中
invokestatic #11 // Method print:()V
print() 方法是静态的,所以调用的是 invokestatic 指令。
方法返回指令根据方法的返回值类型进行区分,常见的返回指令见下图。