Java 静态变量 NullPointerException !空指针异常

本文分析了Java中静态变量在遇到空对象时为何不会抛出NullPointerException,揭示了字节码层面的机制:静态变量实际上是通过类的Class对象而非实例对象访问,即使实例变量为null,编译后的字节码会直接使用静态常量池的值。
摘要由CSDN通过智能技术生成

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值