Java 静态变量 NullPointerException !
前段时间,小编无意中发现一个很有意思的东西,今天就同大家一起讨论讨论,分析分析。
话不多说,直接上代码:
// 定义一个 People 类, 就一个静态变量 name
public class People {
public final static String name = "xiao ming";
}
// 定义一个测试类
public class Test {
public static void main(String[] args) {
People people = new People();
// 先创建一个People对象实例,又赋值为空
people = null;
// 通过 变量名称 people.name 访问静态变量,你说它会不会空指针异常?
System.out.println(people.name); // 它会不会报空指针异常 → NullPointerException
}
}
以上是一段很简单的代码,但是其中有一段很风骚的操作,哈哈哈,如果你看过上面这段代码,你认为它会不会报空指针异常呢???→ NullPointerException
小编建议大家可以手动敲敲看看,按照常理来说,上面这段是必报空指针异常的,但是没有,你会发现控制台输出了:xiao ming
Connected to the target VM, address: '127.0.0.1:59522', transport: 'socket'
xiao ming
Disconnected from the target VM, address: '127.0.0.1:59522', transport: 'socket'
WTF ???是不是很惊讶,小编一开始也和你们一样。
【ps: 有些高版本的IEDA,如果通过变量名称访问静态变量的话,会提示不允许这样操作,但是却可以通过编译,并且可以运行的】
哈哈哈,说明这种操作静态变量的方式,应该是以前有坑过很多人呀
我们回到问题,怎么没有报 NullPointerException呢 ???
我们都知道 Java 中最终在 JVM 中跑的是字节码,我们的代码竟然通过了编译,说明是生成了字节码的。那我们就看看字节码中是什么样的。
通过 javap -v Test.class 得到上面的字节码文件内容【小编只截取了 main 方法中的内容】
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #2 // class com/wizardframework/core/util/thread/People
3: dup
4: invokespecial #3 // Method com/wizardframework/core/util/thread/People."<init>":()V
7: astore_1
8: aconst_null
9: astore_1
10: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
13: aload_1
14: pop
15: ldc #5 // String xiao ming
17: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
20: return
分析你会发现,源码的 people.name 对应编译后的指令是 ldc #5 【Code 中 15 行】
ldc 字节码指令的意思是:直接获取运行时常量池的数据 → xiao ming
原来编译后直接就变成了直接使用字符串常量了
那如果是我们一开始不用字符串,改用对象引用还成立?
我们改造一下代码:
public class XiaoMing {
private String name = "xiao ming";
}
// 将原来 People 对象的静态变量从字符串改成对象
public class People {
public final static XiaoMing XIAO_MING = new XiaoMing();
}
public class Test {
public static void main(String[] args) {
People people = new People();
people = null;
System.out.println(people.XIAO_MING); // 它会不会报空指针异常 → NullPointerException
}
}
执行后发现,一样,还是没有报空指针。我们在看一下字节码:
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #2 // class com/wizardframework/core/util/thread/People
3: dup
4: invokespecial #3 // Method com/wizardframework/core/util/thread/People."<init>":()V
7: astore_1
8: aconst_null
9: astore_1
10: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
13: aload_1
14: pop
15: getstatic #5 // Field com/wizardframework/core/util/thread/People.XIAO_MING:Lcom/wizardframework/core/util/thread/XiaoMing;
18: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
21: return
请大家仔细对比上面两段字节码,小编当时就震惊了,除了第 15 行的字节码指令不一样,其他的完全一样。
字节码指令从 ldc 变成了 getstatic ,其他的不变。
getstatic 字节码指令的意思是:通过类来获取静态字段
原来是这样呀,虽然我们用的是实例对象的变量名访问静态变量,但是编译后,却是通过变量名对应的类型,访问类的静态变量。
我们总结一下:
类的静态成员是属于类的,但是不属于类的所有实例对象。
实例变量是 new 出来的,静态变量所属于这个实例变量的 Class 对象,我们虽然可以通过类的实例对象. 属性的方式访问到,但是实际编译成字节码的时候是直接通过定义的类的去访问的。【可以理解为:类被类加载器加载后,在内存中是通过 类的 Class 对象访问到的】
更多内容欢迎点击阅读原文,喜欢文章的话,也希望能给小编点个赞或者转发,你们的喜欢与支持是小编最大的鼓励,小巫编程室感谢您的关注与支持。good good study day day up