前言
无论从事什么职业,曾经在哪里工作,总要留下自己的一些痕迹。
也许是多年以后的某一天,你蓦然回首,你可以淡淡的说一句,来过!
一、null是什么?
我相信你肯定觉得这个问题很白痴,别着急,看代码:
/**
* @author top
*/
public class NullClassTest {
public static void sayHello() {
System.out.println("hello");
}
public static void main(String[] args) {
((NullClassTest) null).sayHello();
NullClassTest nullClassTest = null;
nullClassTest.sayHello();
}
}
请问以上代码编译能通过吗?如果通过了运行会报空指针异常吗?
别急我们接着往下看,打开NullClasTest的字节码:
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=2, args_size=1
0: aconst_null
1: checkcast #5 // class com/myhexin/test/NullClasTest
4: pop
5: invokestatic #6 // Method sayHello:()V
8: aconst_null
9: astore_1
10: aload_1
11: pop
12: invokestatic #6 // Method sayHello:()V
15: return
LineNumberTable:
line 13: 0
line 15: 8
line 16: 10
line 17: 15
我们看到第一个指令:aconst_null
通过查看虚拟机字节码指令表知道这条指令的含义是将null推送到栈顶,但是更多的信息就没有了,线索似乎到这里中断了。
别急,我们再看,我们知道Java是一门解释性语言(别杠,杠就是你对),虚拟机字节码指令需要解释器解释为机器指令才能执行。hotspot虚拟机中的zero解释器有这样一段代码:
#define NULL 0
...
#define SET_STACK_OBJECT(value, offset) (*((oop *)&topOfStack[-(offset)]) = (value))
...
CASE(_aconst_null):
SET_STACK_OBJECT(NULL, 0);
UPDATE_PC_AND_TOS_AND_CONTINUE(1, 1);
SET_STACK_OBJECT(NULL, 0)代码的含义是将栈中偏移量为0的位置的值设为0.
所有在内存中null的值其实是零。这似乎跟我们猜想的不太一样他怎么会是0呢?
0的含义是什么?我们知道java的宿主语言是C++,那么C++中会不会有我们想要得答案呢?
恰巧博主在学习C++的过程中看到过一段话:
原来0代表不指向任何一个可访问的地址。
那么我们开局的代码运行结果是什么呢?根据上边我们一步一步的推导你肯定在想爱,是报空指针啦!
错,错,错!
通过程序运行:
不仅没报错反而正确运行了。null不是指向不指向任何可访问的地址吗?这怎么还调用成功了呢?
其实呢这一切的一切都是编译器在作怪,我们用ide工具打开编译后的class文件,当然牛逼的可以直接看字节码指令,但是通过ide查看会更直观:
public class NullClassTest {
public NullClassTest() {
}
public static void sayHello() {
System.out.println("hello");
}
public static void main(String[] args) {
NullClassTest var10000 = (NullClassTest)null;
sayHello();
NullClassTest nullClassTest = null;
sayHello();
}
开具的代码经过编译器的处理变成了以上的形式,其实是编译器偷偷改了我们的代码,他替我们写了正确的调用方法。
以上就是就是Java中的null,觉得有帮助的可以点个赞。
ps:zero解释器的代码路径:hotspot源码下的src\share\vm\interpreter\bytecodeInterpreter.cpp