在介绍新语言都会对比 java 来讲,以贬低 java 抬高自己方式来介绍语言的一些方便之处。Java 的确存在许多让人头痛的问题。而且由于不断吸收变得庞大、臃肿。通过添加新特性来让自己变得强大并不是坏事,但是变得越来越复杂也是其副作用。从而失去自己的特点。这也是 rob pike 坚持己见,对 go 不做应适修改的原因。rob bike 所倡导的简单也是值得我们对于一些当下流行语言反思。要做到复杂可能并不难,不过要做的简单可能需要多花一些精力。
现在许多新兴的语言都可以运行在 JVM 上,这是因为他们都可以编译为字节码。只要你的语言最终编译成符合 JVM 规范的字节码就可以运行在 JVM 上。这样我们就也可以写出自己语言来运行在 JVM 虚拟机上。
JVM java 的虚拟机是基于栈结构。都会在栈中产生一个帧。
package com.zidea.test;
public class Demo {
private int a = 1;
public Demo() {
}
public int getA() {
return this.a;
}
public void setA(int a) {
this.a = a;
}
}
Demo.class
Last modified 2019-4-3; size 466 bytes
MD5 checksum 49bf9f0b6522624b609713b4f94a3cb5
Compiled from "Demo.java"
public class com.zidea.test.Demo
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#20 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#21 // com/zidea/test/Demo.a:I
#3 = Class #22 // com/zidea/test/Demo
#4 = Class #23 // java/lang/Object
#5 = Utf8 a
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcom/zidea/test/Demo;
#14 = Utf8 getA
#15 = Utf8 ()I
#16 = Utf8 setA
#17 = Utf8 (I)V
#18 = Utf8 SourceFile
#19 = Utf8 Demo.java
#20 = NameAndType #7:#8 // "<init>":()V
#21 = NameAndType #5:#6 // a:I
#22 = Utf8 com/zidea/test/Demo
#23 = Utf8 java/lang/Object
{
private int a;
descriptor: I
flags: ACC_PRIVATE
public com.zidea.test.Demo();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_1
6: putfield #2 // Field a:I
9: return
LineNumberTable:
line 3: 0
line 4: 4
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lcom/zidea/test/Demo;
public int getA();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field a:I
4: ireturn
LineNumberTable:
line 7: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/zidea/test/Demo;
public void setA(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: iload_1
2: putfield #2 // Field a:I
5: return
LineNumberTable:
line 11: 0
line 12: 5
LocalVariableTable:
Start Length Slot Name Signature
0 6 0 this Lcom/zidea/test/Demo;
0 6 1 a I
}
SourceFile: "Demo.java"
我们先简单浏览一遍
private int a = 1;
中 descriptor 为描述符,flags 标识符,其中包含修饰变量的修饰符信息。
- descriptor: I
- flags: ACC_PRIVATE
private static int b;
如果是这样的定义一个静态变量,标识符就多了一个表示静态变量 ACC_STATIC
descriptor: I
flags: ACC_PRIVATE, ACC_STATIC
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_1
6: putfield #2 // Field a:I
9: return
这是一个构造函数的 code
- aload_0 加载 this
可以用一些工具来查看字节码文件
aload_0: 从局部变量的相应位置装载一个对象引用到操作数栈的栈顶。尽管示例代码中并不包含构造函数,我们知道 JVM 会类添加一个默认的构造函数。但是类的变量的初始化代码实际会由编译器构建构建默认构造函数中执行。
invokespecial: 用于调用实例的初始化方法,包括私有方法以及当前类的父类方法。同样属于一组以不同方式来调用方法的操作码,这些操作码包括invokedynamic、invokeinterface、invokespecial、invokestatic和invokevirtual。invokespecial是用于调用父类构造方法的指令,例如java.lang.Object 的构造方法。
// Method java/lang/Object."<init>":()V
- putfield: 从运行时的常量池中取一个指向成员变量的引用,这个成员变量的值以及其对应的对象都会从操作数栈中弹出,本例中的成员变量即为 a。例子中,aload_0 首先向操作数栈中添加了对象,然后 iconst_1 向操作数栈中添加了 1 这个值,最后putfield从栈中弹出了这两个值,最终,这个对象的a的值被设置为1。
如果我们将 a 赋值为 100 即 private int a = 100;
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: bipush 100
7: putfield #2 // Field a:I
10: return
iconst_1 就变为 bipush 100