NullPointerException底层源码分析

NullPointerException 是 Java 编程中一个常见的运行时异常,通常在尝试访问或操作一个为 null 的对象引用时抛出。下面是一些常见原因及解决方法:

常见原因

  1. 未初始化的对象:如果对象在使用前没有被实例化,就会引发 NullPointerException。

    String str = null;
    System.out.println(str.length()); // 这里会抛出 NullPointerException
    
  2. 返回值可能为 null 的方法调用:某些方法可能返回 null 值,没有对这些返回值进行检查就直接使用了。

    String str = getString(); // getString() 返回 null
    System.out.println(str.length()); // 这里会抛出 NullPointerException
    
  3. 集合中的元素为 null:对集合中的元素进行操作时,如果元素为 null,也会抛出 NullPointerException。

    List<String> list = new ArrayList<>();
    list.add(null);
    System.out.println(list.get(0).length()); // 这里会抛出 NullPointerException
    

解决方法

  1. 初始化对象

    String str = "";
    System.out.println(str.length()); // 输出 0
    
  2. 在使用前进行 null 检查

    String str = getString();
    if (str != null) {
        System.out.println(str.length());
    } else {
        System.out.println("String is null");
    }
    
  3. 使用 Optional 类(Java 8 及以上)

    Optional<String> optionalStr = Optional.ofNullable(getString());
    optionalStr.ifPresent(s -> System.out.println(s.length()));
    
  4. 防御性编程:在设计 API 时,尽量避免返回 null,可以返回空集合、空字符串等。

    public List<String> getList() {
        return Collections.emptyList(); // 而不是返回 null
    }
    

NullPointerException底层源码分析

NullPointerException 是 Java 中一个常见的运行时异常,属于 java.lang 包。

NullPointerException 类源码

首先,让我们看看 NullPointerException 类的源代码:

package java.lang;

public class NullPointerException extends RuntimeException {
    private static final long serialVersionUID = 5162710183389028792L;

    /**
     * Constructs a NullPointerException with no detail message.
     */
    public NullPointerException() {
        super();
    }

    /**
     * Constructs a NullPointerException with the specified detail message.
     *
     * @param s the detail message.
     */
    public NullPointerException(String s) {
        super(s);
    }
}

从上面的源码可以看出,NullPointerException 类继承自 RuntimeException,并提供了两个构造方法:

  • 无参构造方法:创建没有详细消息的异常对象。
  • 有参构造方法:创建带有详细消息的异常对象。

JVM 如何抛出 NullPointerException

NullPointerException 的抛出过程是由 JVM 在运行时自动完成的,而不是通过显式调用 new NullPointerException() 来实现的。

字节码分析

对于下面这段代码:

String str = null;
str.length();

编译后生成的字节码如下(使用 javap -c):

0: aconst_null
1: astore_1
2: aload_1
3: invokevirtual #1   // Method java/lang/String.length:()I
6: pop
7: return

解释每条指令:

  • 0: aconst_null 将 null 压入栈顶。
  • 1: astore_1 将栈顶的 null 存储到局部变量表的索引为1的位置(即 str)。
  • 2: aload_1 将局部变量表中索引为1的值(即 str)压入栈顶。
  • 3: invokevirtual #1 尝试调用栈顶对象的 length() 方法。

在执行 invokevirtual 指令时,如果栈顶的对象引用是 null,JVM 就会抛出 NullPointerException

JVM 源码分析

在 OpenJDK 中,invokevirtual 指令的实现位于 bytecodeInterpreter.cpp 文件中。以下是简化后的伪代码描述:

case Bytecodes::_invokevirtual: {
    oop receiver = STACK_OBJECT(-number_of_arguments); 
    if (receiver == NULL) {
        THROW(vmSymbols::java_lang_NullPointerException());
    }
    // 继续方法调用流程
}
  • oop receiver = STACK_OBJECT(-number_of_arguments); 获取调用对象的引用。
  • if (receiver == NULL) 检查引用是否为 null
  • THROW(vmSymbols::java_lang_NullPointerException()); 抛出 NullPointerException

总结

NullPointerException 是由 JVM 在检测到空引用操作时自动抛出的,它继承自 RuntimeException。通过查看底层字节码和 JVM 源码,我们可以更清楚地理解 NPE 的抛出机制。

ObservableZipIterable 是 RxJava 中的一个类,用于将多个 Observable 转换成单个 Observable ,并将它们的元素打包成一个元素。下面是该类的源码分析: ``` final class ObservableZipIterable<T, U, V> extends Observable<V> { final Iterable<? extends ObservableSource<? extends T>> sources; final Function<? super Object[], ? extends V> zipper; ObservableZipIterable(Iterable<? extends ObservableSource<? extends T>> sources, Function<? super Object[], ? extends V> zipper) { this.sources = sources; this.zipper = zipper; } @Override public void subscribeActual(Observer<? super V> s) { @SuppressWarnings("unchecked") Iterator<? extends ObservableSource<? extends T>> it = sources.iterator(); // 检查源 Observable 是否为空 if (!it.hasNext()) { EmptyDisposable.error(new NoSuchElementException(), s); return; } // 创建一个动态数组,用于缓存每个源 Observable 的元素 // 这里使用了可变长数组 ArrayList,因为不知道每个源 Observable 会产生多少个元素 // 使用 ArrayList 可以动态地添加元素 int n = 0; ObservableSource<? extends T>[] sources = new ObservableSource[8]; try { while (it.hasNext()) { ObservableSource<? extends T> p = it.next(); if (p == null) { EmptyDisposable.error(new NullPointerException("One of the sources is null"), s); return; } if (n == sources.length) { ObservableSource<? extends T>[] b = new ObservableSource[n + (n >> 2)]; System.arraycopy(sources, 0, b, 0, n); sources = b; } sources[n++] = p; } } catch (Throwable e) { Exceptions.throwIfFatal(e); EmptyDisposable.error(e, s); return; } // 创建一个数组,用于缓存每个源 Observable 的 Observer // 与上面的 sources 数组一样,这里也使用了 ArrayList,因为不知道每个源 Observable 会产生多少个元素 // 使用 ArrayList 可以动态地添加元素 ObservableZip<T>[] zip = new ObservableZip[n]; for (int i = 0; i < n; i++) { zip[i] = new ObservableZip<T>(sources[i]); } // 创建一个 ZipCoordinator 对象,用于协调多个 Observable 的订阅和元素的打包 ZipCoordinator<T, V> coordinator = new ZipCoordinator<T, V>(s, zipper, n, zip); // 订阅每个源 Observable coordinator.subscribe(zip); // 执行协调器的 run 方法,开始打包元素 coordinator.run(); } } ``` 从上面的源码中可以看出,ObservableZipIterable 类实际上是一个 Observable 的子类,它的 subscribeActual 方法用于订阅源 Observable,并将它们的元素打包成一个元素。 在 subscribeActual 方法中,首先使用 sources.iterator() 获取源 Observable 的迭代器,然后依次遍历每个源 Observable,并把它们的元素缓存在一个动态数组中。如果在遍历过程中遇到了 null 值或者源 Observable 为空,则会发送 onError 事件,并终止订阅。 接着,根据每个源 Observable 的个数创建一个 ObservableZip 数组,用于缓存每个源 Observable 的 Observer。然后创建一个 ZipCoordinator 对象,用于协调多个 Observable 的订阅和元素的打包。 最后,订阅每个源 Observable,并执行协调器的 run 方法,开始打包元素。在 run 方法中,协调器会等待每个源 Observable 发送元素,并将它们打包成一个元素,然后发送给订阅者。如果有任意一个源 Observable 发出了 onError 事件,则会直接发送 onError 事件,并终止订阅。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值