NPE:三目运算符导致空指针问题

三目运算符导致空指针问题

  • 1、一段代码引发的血案
  • [2、什么是 Java 空指针异常?](#2、什么是 Java 空指针异常?)
  • 3、引发空指针异常的常见情况
    • [3.1 访问空引用的实例变量或方法](#3.1 访问空引用的实例变量或方法)
    • [3.2 集合中元素为 null 时访问](#3.2 集合中元素为 null 时访问)
    • [3.3 字符串比较出现空指针异常](#3.3 字符串比较出现空指针异常)
    • [3.4 自动拆箱导致空指针](#3.4 自动拆箱导致空指针)
      • [3.4.1 Integer 类型自动拆箱导致空指针](#3.4.1 Integer 类型自动拆箱导致空指针)
      • [3.4.2 Java 三元运算符的空指针问题](#3.4.2 Java 三元运算符的空指针问题)
      • [3.4.3 装箱和拆箱](#3.4.3 装箱和拆箱)
      • [3.4.4 小结](#3.4.4 小结)
  • 4、如何避免空指针异常?

1、一段代码引发的血案

java 复制代码
public class NPEExample {
    public static void main(String[] args) {
        Integer i = null;
        Integer ii = 1;
        int Y = 1;
        i = ii == null ? Y : i;
        System.out.println(i);
    }
}

打印结果:

Exception in thread "main" java.lang.NullPointerException
	at com.atu.NPEExample.main(NPEExample.java:10)

在Java开发中,空指针异常是最常遇到的问题之一。接下来,我们将探讨空指针异常的常见原因以及如何避免。

2、什么是 Java 空指针异常?

我们都知道,Java中并没有指针,所谓的【Java指针】实际上指的是 Java 中的引用。空指针就是指向空的引用。

当引用为空时,如果尝试调用它所指向对象的成员变量或方法,就会抛出空指针异常。

成员变量和方法是属于对象的(排除静态成员),它们只有在对象存在时才能被访问。空指针并不指向任何对象,因此它没有对应的成员变量和方法。当通过空指针尝试访问这些属性或方法时,就会导致空指针异常。

3、引发空指针异常的常见情况

  1. 对象未初始化直接使用;
  2. 方法返回值为 null 后直接调用方法或访问属性;
  3. 从集合中获取元素为 null,并且尝试访问该元素的属性或方法:
  4. 方法参数为 null;
  5. 方法参数为 null,方法内部尝试使用该参数的成员或方法;

3.1 访问空引用的实例变量或方法

当引用变量为 null 时,直接访问其成员变量或调用方法会引发空指针异常。例如:

java 复制代码
MyObject obj = null;
System.out.println(obj.someMethod());  // 会抛出 NullPointerException

3.2 集合中元素为 null 时访问

如果集合中的元素是 null,并且你尝试访问该元素的属性或方法,也会抛出空指针异常:

java 复制代码
List<MyObject> list = new ArrayList<>();
list.add(null);
list.get(0).someMethod();  // 会抛出 NullPointerException

3.3 字符串比较出现空指针异常

java 复制代码
String str = null;
boolean isEqual = str.equals("hello");  // 会抛出 NullPointerException

3.4 自动拆箱导致空指针

3.4.1 Integer 类型自动拆箱导致空指针

对于 Integer 等包装类,自动拆箱时,如果包装类对象为 null,将导致空指针异常,因为 null 无法被自动转换为对应的原始类型。

java 复制代码
public class Test {
    public static void main(String[] args) {
        Integer a = null;
        Integer b = 1;
        System.out.println(a+b); // 会抛出 NullPointerException
    }
}

jd 反编译:

java 复制代码
public class NPEExample {
  public static void main(String[] paramArrayOfString) {
    Integer integer1 = null;
    Integer integer2 = Integer.valueOf(1);
    System.out.println(integer1.intValue() + integer2.intValue());
  }
}

原因分析:拆箱调用 intValue方法,null.intValue 报空指针。

3.4.2 Java 三元运算符的空指针问题

回到文章开头的那段代码:

java 复制代码
public class NPEExample {
    public static void main(String[] args) {
        Integer i = null;
        Integer ii = 1;
        int Y = 1;
        i = ii == null ? Y : i;
        System.out.println(i);
    }
}

jd 反编译:

java 复制代码
public class NPEExample {
  public static void main(String[] paramArrayOfString) {
    Integer integer1 = null;
    Integer integer2 = Integer.valueOf(1);
    boolean bool = true;
    integer1 = Integer.valueOf((integer2 == null) ? bool : integer1.intValue());
    System.out.println(integer1);
  }
}

原因分析:

  • 如果 表达式1表达式2 都是包装类型(比如 Integer、Double 等),那么结果的类型就是那个包装类型。
  • 如果其中有一个是基本数据类型(比如 int、float 等),那么结果的类型就会是这个基本类型。
  • 如果两者中有一个是包装类型,编译器会自动进行拆箱,把包装类转换为基本类型,以保证类型一致。

新版的阿里开发规约关于三目运算符使用过程中,自动拆箱可能导致的 NullPointerException 的说明。

3.4.3 装箱和拆箱

  • 装箱(Boxing):将基本数据类型转换为对应的包装类对象的过程。例如,将 int 类型转换为 Integer 对象。装箱可以是显式的,也可以是隐式的(自动装箱)。

显式装箱:使用包装类的构造函数或静态方法来手动将基本数据类型转换为对象。

例如:

java 复制代码
int x = 10;
Integer integerValue = new Integer(x);  // 显式装箱

隐式装箱(自动装箱):在需要包装类对象的地方,Java 会自动将基本数据类型转换为对应的包装类对象。

java 复制代码
int x = 10;
Integer integerValue = x;  // 自动装箱,编译器会自动把 int 转为 Integer

jd 反编译后的隐式装箱:

java 复制代码
byte var1 = 10;
Integer var2 = Integer.valueOf(var1);
  • 拆箱(Unboxing):拆箱是指将包装类对象转换为对应的基本数据类型的过程。类似于装箱,拆箱也可以是显式的,也可以是隐式的(自动拆箱)。

显式拆箱:通过调用包装类的 intValue() 方法(对于 Integer)来手动将 Integer 对象转换为 int 基本数据类型。

java 复制代码
Integer integerValue = new Integer(10);
int x = integerValue.intValue();  // 显式拆箱

隐式拆箱(自动拆箱):

java 复制代码
Integer integerValue = 10;  // 自动装箱
int x = integerValue;  // 自动拆箱,编译器会自动将 Integer 转为 int

jd 反编译隐式拆箱代码:

java 复制代码
Integer integer = Integer.valueOf(10);
int i = integer.intValue();

3.4.4 小结

Java 会自动进行装箱和拆箱。在实际编程中,尤其要小心在拆箱时避免空指针异常。

  • if 判断: == 两边如果一边是基本类型,一边是包装类型,需要判空,否则会空指针。
  • 使用包装类进行数值计算:如果包装类对象为 null,同样会引发 NPE。
  • 对 null 的包装对象进行自动拆箱:当包装对象为 null 时,尝试对其进行拆箱操作,会直接抛出 NullPointerException。

4、如何避免空指针异常?

  1. 空值检查:在使用对象之前,显式检查它是否为 null 是最直接的一种方法。
java 复制代码
if (myObject != null) {
    myObject.someMethod();
}
  1. 使用 Optional
java 复制代码
public class OptionalDemo {
    public static void main(String[] args) {
        User user = null;
        Optional<User> optUser = Optional.ofNullable(user);
        String name = optUser.map(User::getName).orElse(null);
    }
}
  1. 使用 @NonNull @Nullable 注解:
java 复制代码
public class User {
    @NotNull
    private Integer id;
    @NotBlank
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
  1. 通过 try-catch 捕获异常
java 复制代码
try {
    myObject.someMethod();
} catch (NullPointerException e) {
    // 处理异常
}

总结:免空指针异常的核心思想是:尽量避免 null,显式地处理 null,或者通过工具类和方法安全地访问可能为 null 的对象。