之前费了好大劲,一直想搞清楚 .class文件是个什么东东,因为我知道 .java 文件编译后是字节码的二进制文件,所以。。。有点蒙,因为在我的理解中,二进制文件就是文件中只有0和1的文件。。。。其实class文件用十六进制编辑器打开后就是16进制的字符了,那么,其实这是将4个数字合并的结果。.class文件就是字节码文件,用javap指令可以查看到对应的解析。
字节码文件是怎么执行的呢?这个和虚拟机的不同有关,有些是先解释,形成另一种虚拟机的指令,再执行,有些是JIT编译器编译成虚拟机所在主机cpu的本地指令集执行(可是cpu指令集是个什么东东),这样就和物理机打交道了。
粘贴一段文字:
汇编是离机器码最近的一个人类可阅读可编写的语言形式。
机器(CPU)为了运算速度,被设计成只能阅读010101这样的文字的东西。
而人类不可能用0 和 1 ,来写程序,阅读和理解和修改起来太费劲了,效率太低。
所以最开始就有了汇编语言,人类用汇编语言来写人类看得懂的程序,例如:MOV AH,15
之后“编译器”这个程序会把人类编写的程序,翻译成CPU能理解的010101.........
其实编译执行,就是产生本地代码(Native Code)的过程,其实就是生成宿主机处理器的指令集的过程,本地代码并不是0101...,它是指令集。。
机器(CPU)为了运算速度,被设计成只能阅读010101这样的文字的东西。
而人类不可能用0 和 1 ,来写程序,阅读和理解和修改起来太费劲了,效率太低。
所以最开始就有了汇编语言,人类用汇编语言来写人类看得懂的程序,例如:MOV AH,15
之后“编译器”这个程序会把人类编写的程序,翻译成CPU能理解的010101.........
Class文件包括j
ava虚拟机指令集和符号表已经若干其他辅助信息(摘自深入理解JVM虚拟机),从这里可以看出javap命查看的字节码指令确实就是Class文件。
扯远了。。。以上只是前提,下面进入讨论的主题。
JVM分派:
方法调用是如何确定调用版本(调用哪一个方法)的呢?
分为静态和动态,静态就是在编译后解析过程就能确定调用版本,动态就是在运行解析时才能确定哪一个版本。
这里其实也是对java的多态有了更深一步的认识,编译期间只知道父类型(或接口类型),直到运行期间才能确定实际类型。
先来看一个例子:
public class Dispatch1
{
static abstract class Human
{
}
static class Man extends Human
{
}
static class Woman extends Human{}
public void sayHello(Human guy)
{
System.out.println("hello,guy!");
}
public void sayHello(Man guy)
{
System.out.println("hello,gentleman!");
}
public void sayHello(Woman guy)
{
System.out.println("hello,lady!");
}
public static void main(String[] args)
{
Human man=new Man();
Human woman=new Woman();
Dispatch1 dp=new Dispatch1();
dp.sayHello(man);
dp.sayHello(woman);
}
}
输出:
hello,guy!
hello,guy!
这个其实是重载,Human其实是静态类型,编译期间就可以直到,Man Woman 是实际类型,运行期间才可以确定。重载的时候,是根据参数的静态类型确定的,而不是实际类型。
hello,guy!
这个其实是重载,Human其实是静态类型,编译期间就可以直到,Man Woman 是实际类型,运行期间才可以确定。重载的时候,是根据参数的静态类型确定的,而不是实际类型。
接下来我们继续来看:
public class SuperTest {
public static void main(String[] args) {
new Sub().exampleMethod();
}
}
class Super {
void interestingMethod() {
System.out.println("Super's interestingMethod");
}
void exampleMethod() {
interestingMethod();
}
}
class Sub extends Super {
void interestingMethod() {
System.out.println("Sub's interestingMethod");
}
}
输出:
Sub's interestingMethod
public class SuperTest {
public static void main(String[] args) {
new Sub().exampleMethod();
}
}
class Super {
private void interestingMethod() {
System.out.println("Super's interestingMethod");
}
void exampleMethod() {
interestingMethod();
}
}
class Sub extends Super {
void interestingMethod() {
System.out.println("Sub's interestingMethod");
}
}
输出:
Super‘s interestingMethod
代码二的Super类javap输出
Compiled from "SuperTest.java"
class Super {
Super();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":
()V
4: return
void exampleMethod();
Code:
0: aload_0
1: ldc #5 // String aa
3: invokespecial #6 // Method interestingMethod:(Ljava/l
ang/String;)I
6: pop
7: return
}
class Super {
Super();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":
()V
4: return
void exampleMethod();
Code:
0: aload_0
1: ldc #5 // String aa
3: invokespecial #6 // Method interestingMethod:(Ljava/l
ang/String;)I
6: pop
7: return
}
代码一的Super类javap输出 写道
Compiled from "SuperTest.java"
class Super {
Super();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":
()V
4: return
int interestingMethod(java.lang.String);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/
io/PrintStream;
3: ldc #3 // String Super's interestingMethod
5: invokevirtual #4 // Method java/io/PrintStream.printl
n:(Ljava/lang/String;)V
8: iconst_1
9: ireturn
void exampleMethod();
Code:
0: aload_0
1: ldc #5 // String aa
3: invokevirtual #6 // Method interestingMethod:(Ljava/l
ang/String;)I
6: pop
7: return
}
class Super {
Super();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":
()V
4: return
int interestingMethod(java.lang.String);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/
io/PrintStream;
3: ldc #3 // String Super's interestingMethod
5: invokevirtual #4 // Method java/io/PrintStream.printl
n:(Ljava/lang/String;)V
8: iconst_1
9: ireturn
void exampleMethod();
Code:
0: aload_0
1: ldc #5 // String aa
3: invokevirtual #6 // Method interestingMethod:(Ljava/l
ang/String;)I
6: pop
7: return
}
两边的不同我通过加粗来说明了.,在Super类的exampleMethod方法中调用interestingMethod方法的指令是不同的,代码二采用的是invokespecial 而代码一采用的是invokevirtual .
写道
· invokespecial - super方法调用、private方法调用与构造器调用
· invokevirtual - 用于调用一般实例方法(包括声明为final但不为private的实例方法)
· invokevirtual - 用于调用一般实例方法(包括声明为final但不为private的实例方法)
其中
写道
invokespecial调用的目标必然是可以静态绑定的,因为它们都无法参与子类型多态;invokevirtual的则一般需要做运行时绑定
invokespecial调用的目标必然是可以静态绑定的,因为它们都无法参与子类型多态;invokevirtual的则一般需要做运行时绑定
所以说,有private 修饰的方法,是可以在编译器就找到调用版本的。invokerspecial调用的目标为interestingMethod,所以在编译器就能确定,一定是静态绑定。
其他的例子,
public class Dispatch
{
static class QQ{};
static class Q1 extends QQ{};
static class Q2 extends QQ{};
public static class Father
{
public void hardChoice(QQ arg)
{
System.out.println("father choose qq");
}
public void hardChoice(Q1 arg)
{
System.out.println("father choose q1");
}
public void hardChoice(Q2 arg)
{
System.out.println("father choose q2");
}
}
public static class Son extends Father
{
public void hardChoice(QQ arg)
{
System.out.println("son choose qq");
}
public void hardChoice(Q1 arg)
{
System.out.println("son choose q1");
}
public void hardChoice(Q2 arg)
{
System.out.println("son choose q2");
}
}
public static void main(String[] args)
{
/*
* 两个宗量:方法的接收者和参数,我的理解是,参数是静态的,接收者是动态的,即直等到运行时才能确定接收者的实际类型
*/
Father father=new Father();
QQ q =new Q1();
QQ q1=new Q2();
father.hardChoice(q);//相当于father的重载,静态绑定,输出father choose qq
Father son=new Son();
son.hardChoice(q);//动态分配是单分派,参数已经在静态期间确定了,实际类型运行时确定,那么为什么不在运行时确定q的实际类型呢?所以,我认为,
//动态单分派,指的是接收者的单分派。
}
}
补充---------------------------------------》
关于静态分派和动态分派的理解,深入理解JVM书中所说:只要能被invokerstatic和invokerspecial指令调用的方法,都可以在解析阶段确定唯一的调用版本,就是说,只要在解析阶段能确定到底谁是要执行的目标函数时,就进行静态绑定了,那么我们在看上面的例子:
new sub()在编译期还不知道实际类型,只知道为Super类型,所以去找Super的exampleMethod方法
父类的exampleMethod方法 有 invokerspecial 指令,去找 interestingMethod,找到了,进行静态绑定
如果private变为public ,从Super类找的时候,exampleMethod 并不能找到调用目标,所以不能绑定,只有在运行时进行动态绑定。