首页 文章

什么是NullPointerException,我该如何解决?

提问于
浏览 364
210

什么是空指针异常( java.lang.NullPointerException )以及它们的原因是什么?

可以使用哪些方法/工具来确定原因,以便停止异常导致程序过早终止?

12 回答

  • 3349

    当应用程序在需要对象的情况下尝试使用null时,将引发空指针异常 . 这些包括:

    • 调用 null 对象的实例方法 .

    • 访问或修改 null 对象的字段 .

    • null 的长度视为数组 .

    • 访问或修改 null 的插槽,就像它是一个数组一样 .

    • 抛出 null ,好像它是一个Throwable值 .

    应用程序应抛出此类的实例以指示 null 对象的其他非法用法 .

    参考:http://docs.oracle.com/javase/8/docs/api/java/lang/NullPointerException.html

  • 793

    什么是NullPointerException?

    一个好的起点是JavaDocs . 他们有这个涵盖:

    在需要对象的情况下,应用程序尝试使用null时抛出 . 其中包括:调用null对象的实例方法 . 访问或修改空对象的字段 . 将null的长度视为数组 . 访问或修改null的插槽,就像它是一个数组一样 . 抛出null,好像它是一个Throwable值 . 应用程序应抛出此类的实例以指示null对象的其他非法使用 .

    如果您尝试使用带有 synchronized 的空引用,也会抛出此异常,per the JLS

    SynchronizedStatement:
    同步(表达式)块
    否则,如果Expression的值为null,则抛出NullPointerException .

    如何解决?

    所以你有 NullPointerException . 你是如何解决的?让我们举一个抛出 NullPointerException 的简单例子:

    public class Printer {
        private String name;
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void print() {
            printString(name);
        }
    
        private void printString(String s) {
            System.out.println(s + " (" + s.length() + ")");
        }
    
        public static void main(String[] args) {
            Printer printer = new Printer();
            printer.print();
        }
    }
    

    Identify the null values

    第一步是确切地确定导致异常的值 . 为此,我们需要做一些调试 . 学习阅读堆栈跟踪很重要 . 这将显示抛出异常的位置:

    Exception in thread "main" java.lang.NullPointerException
        at Printer.printString(Printer.java:13)
        at Printer.print(Printer.java:9)
        at Printer.main(Printer.java:19)
    

    在这里,我们看到了例外在第13行抛出(在 printString 方法中) . 查看该行并通过添加日志记录语句或使用调试器来检查哪些值为空 . 我们发现 s 为null,并且在其上调用 length 方法会抛出异常 . 我们可以看到,当从方法中删除 s.length() 时,程序停止抛出异常 .

    Trace where these values come from

    接下来检查此值的来源 . 通过跟随方法的调用者,我们看到 print()print() 方法中传入 printString(name)this.name 为空 .

    Trace where these values should be set

    this.name 在哪里设置?在 setName(String) 方法中 . 通过一些更多的调试,我们可以看到根本没有调用此方法 . 如果调用该方法,请确保检查调用这些方法的顺序,并且在print方法之后不调用set方法 .

    这足以为我们提供解决方案:在调用 printer.print() 之前添加对 printer.setName() 的调用 .

    其他修复

    变量可以有一个默认值( setName 可以防止它被设置为null):

    private String name = "";
    

    printprintString 方法可以检查null,例如:

    printString((name == null) ? "" : name);
    

    或者您可以设计类,以便 name 始终具有非空值:

    public class Printer {
        private final String name;
    
        public Printer(String name) {
            this.name = Objects.requireNonNull(name);
        }
    
        public void print() {
            printString(name);
        }
    
        private void printString(String s) {
            System.out.println(s + " (" + s.length() + ")");
        }
    
        public static void main(String[] args) {
            Printer printer = new Printer("123");
            printer.print();
        }
    }
    

    See also:

    我仍然找不到问题

    如果你试图调试问题,但仍然没有尝试到目前为止 . 问题中至少为 include the stacktrace ,代码中为 mark the important line numbers . 另外,请先尝试简化代码(请参阅SSCCE) .

  • 638

    空指针异常是指示您正在使用对象而不初始化它 .

    例如,下面是一个将在我们的代码中使用它的学生类 .

    public class Student {
    
        private int id;
    
        public int getId() {
            return this.id;
        }
    
        public setId(int newId) {
            this.id = newId;
        }
    }
    

    下面的代码为您提供了空指针异常 .

    public class School {
    
        Student obj_Student;
    
        public School() {
            try {
                obj_Student.getId();
            }
            catch(Exception e) {
                System.out.println("Null Pointer ");
            }
        }
    }
    

    因为您使用的是 Obj_Student ,但是您忘记按照下面显示的正确代码初始化它:

    public class School {
    
        Student obj_Student;
    
        public School() {
            try {
                obj_Student = new Student();
                obj_Student.setId(12);
                obj_Student.getId();
            }
            catch(Exception e) {
                System.out.println("Null Pointer ");
            }
        }
    }
    
  • 458

    在Java中,一切都是以类的形式出现的 .

    如果您想使用任何对象,那么您有两个阶段:

    • 申报

    • 初始化

    例:

    • 宣言: Object a;

    • 初始化: a=new Object();

    对于阵列概念也是如此

    • 宣言: Item i[]=new Item[5];

    • 初始化: i[0]=new Item();

    如果您没有给出初始化部分,则会出现 NullpointerException .

  • 383

    问题:导致NullPointerException(NPE)的原因是什么?

    您应该知道,Java类型分为基本类型( booleanint 等)和引用类型 . Java中的引用类型允许您使用特殊值 null ,这是Java方式"no object" .

    每当程序试图使用 null 时,就会在运行时抛出 NullPointerException ,就像它是真正的引用一样 . 例如,如果你这样写:

    public class Test {
        public static void main(String[] args) {
            String foo = null;
            int length = foo.length();   // HERE
        }
    }
    

    标记为"HERE"的语句将尝试在 null 引用上运行 length() 方法,这将抛出 NullPointerException .

    有很多方法可以使用 null 值来产生 NullPointerException . 事实上,在没有造成NPE的情况下,你可以用_767616做的唯一事情是:

    • 将其分配给引用变量或从引用变量中读取它,

    • 将其分配给数组元素或从数组元素中读取它(假设数组引用本身为非null!),

    • 将其作为参数传递或作为结果返回,或者

    • 使用 ==!= 运算符或 instanceof 对其进行测试 .

    问题:如何阅读NPE堆栈跟踪?

    假设我编译并运行上面的程序:

    $ javac Test.java 
    $ java Test
    Exception in thread "main" java.lang.NullPointerException
        at Test.main(Test.java:4)
    $
    

    首先观察:编译成功!程序中的问题不是编译错误 . 这是一个运行时错误 . (某些IDE可能会警告您的程序将始终抛出异常......但标准 javac 编译器不会 . )

    第二个观察:当我运行程序时,它会输出两行"gobbledy-gook" . WRONG!! 那不是狼吞虎咽的 . 它是一个堆栈跟踪...它提供了重要的信息,可以帮助您跟踪代码中的错误,如果您花时间仔细阅读它 .

    那么让我们来看看它的内容:

    Exception in thread "main" java.lang.NullPointerException
    

    堆栈跟踪的第一行告诉您许多事情:

    • 它告诉您抛出异常的Java线程的名称 . 对于一个带有一个线程的简单程序(比如这个),它将是"main" . 让我们继续 ...

    • 它告诉你抛出的异常的全名;即 java.lang.NullPointerException .

    • 如果异常有关联的错误消息,则将在异常名称后输出 . NullPointerException 在这方面很不寻常,因为它很少有错误信息 .

    第二行是诊断NPE中最重要的一行 .

    at Test.main(Test.java:4)
    

    这告诉了我们许多事情:

    • "at Test.main"说我们在 Test 类的 main 方法中 .

    • "Test.java:4"给出了类的源文件名,它告诉我们发生这种情况的语句在文件的第4行 .

    如果你计算上面文件中的行,第4行是我用“HERE”注释标记的行 .

    请注意,在一个更复杂的示例中,NPE堆栈跟踪中会有很多行 . 但是你可以确定第二行(第一行"at"行)会告诉你NPE被抛出的位置1 .

    简而言之,堆栈跟踪将毫不含糊地告诉我们程序的哪个语句抛出了NPE .

    1 - 不完全正确 . 有些东西叫做嵌套异常......

    问题:如何在代码中追踪NPE异常的原因?

    这是困难的部分 . 简短的回答是对堆栈跟踪,源代码和相关API文档提供的证据应用逻辑推理 .

    让我们首先用简单的例子(上面)来说明 . 我们首先看一下堆栈跟踪告诉我们的是NPE发生的位置:

    int length = foo.length(); // HERE
    

    怎么样能扔掉NPE吗?

    实际上只有一种方法:只有当 foo 的值为 null 时才会发生 . 然后我们尝试在 null 上运行 length() 方法并且......砰!

    但是(我听你说)如果NPE被抛入 length() 方法调用中会怎么样?

    好吧,如果发生这种情况,堆栈跟踪看起来会有所不同 . 第一个"at"行会在 java.lang.String 类的某一行中抛出异常, Test.java 的第4行将是第二个"at"行 .

    那么 null 来自哪里?在这种情况下,很明显,我们需要做的是修复它 . (为 foo 指定非空值 . )

    好的,让我们尝试一个稍微棘手的例子 . 这将需要一些逻辑演绎 .

    public class Test {
    
        private static String[] foo = new String[2];
    
        private static int test(String[] bar, int pos) {
            return bar[pos].length();
        }
    
        public static void main(String[] args) {
            int length = test(foo, 1);
        }
    }
    
    $ javac Test.java 
    $ java Test
    Exception in thread "main" java.lang.NullPointerException
        at Test.test(Test.java:6)
        at Test.main(Test.java:10)
    $
    

    所以现在我们有两条“at”线 . 第一个是这一行:

    return args[pos].length();
    

    第二个就是这一行:

    int length = test(foo, 1);
    

    看第一行,怎么会抛出一个NPE?有两种方法:

    • 如果 bar 的值为 null ,则 bar[pos] 将抛出NPE .

    • 如果 bar[pos] 的值是 null ,则在其上调用 length() 将抛出NPE .

    接下来,我们需要弄清楚哪些场景解释了实际发生的情况 . 我们将从探索第一个开始:

    bar 来自哪里?它是 test 方法调用的参数,如果我们看一下如何调用 test ,我们可以看到它来自 foo 静态变量 . 此外,我们可以清楚地看到我们将 foo 初始化为非空值 . 这足以暂时驳回这一解释 . (理论上,其他东西可能会改变 foo ......但这不会发生在这里 . )

    那么我们的第二个场景呢?好吧,我们可以看到 pos1 ,这意味着 foo[1] 必须是 null . 那可能吗?

    的确是!这就是问题所在 . 当我们这样初始化时:

    private static String[] foo = new String[2];
    

    我们用一个初始化为 null 的两个元素分配 String[] . 之后,我们没有更改 foo 的内容......所以 foo[1] 仍然是 null .

  • 330

    声明引用变量(即对象)时,实际上是在创建指向对象的指针 . 请考虑以下代码,您可以在其中声明基本类型 int 的变量:

    int x;
    x = 10;
    

    在此示例中,变量x是 int ,Java会将其初始化为0 . 当您在第二行中将其分配给10时,您的值10将被写入x指向的内存位置 .

    但是,当您尝试声明引用类型时会发生不同的事情 . 请使用以下代码:

    Integer num;
    num = new Integer(10);
    

    第一行声明一个名为 num 的变量,但它不包含原始值 . 相反,它包含一个指针(因为类型是 Integer ,它是一个引用类型) . 既然你还没有说什么指向Java将其设置为null,意味着“ I am pointing at nothing ” .

    在第二行中, new 关键字用于实例化(或创建)Integer类型的对象,并为指针变量 num 分配此对象 . 您现在可以使用解除引用运算符 . (一个点)来引用该对象 .

    您询问的 Exception 在声明变量但未创建对象时发生 . 如果您尝试取消引用 num 在创建对象之前,您将获得 NullPointerException . 在最琐碎的情况下,编译器将捕获问题并让您知道"num may not have been initialized"但有时您编写的代码不会直接创建对象 .

    例如,您可能有如下方法:

    public void doSomething(SomeObject obj) {
       //do something to obj
    }
    

    在这种情况下,您不是在创建对象 obj ,而是假设它是在调用 doSomething 方法之前创建的 . 不幸的是,可以像这样调用方法:

    doSomething(null);
    

    在这种情况下 obj 为空 . 如果该方法旨在对传入的对象执行某些操作,则抛出 NullPointerException 是合适的,因为它是程序员错误,程序员将需要该信息用于调试目的 .

    或者,可能存在这样的情况:该方法的目的不仅仅是对传入的对象进行操作,因此可以接受空参数 . 在这种情况下,您需要检查 null parameter 并采取不同的行为 . 您还应该在文档中解释这一点 . 例如, doSomething 可以写成:

    /**
      * @param obj An optional foo for ____. May be null, in which case 
      *  the result will be ____.
      */
    public void doSomething(SomeObject obj) {
        if(obj != null) {
           //do something
        } else {
           //do something else
        }
    }
    

    最后,How to pinpoint the exception & cause using Stack Trace

  • 305

    这就像你试图访问一个 null 的对象 . 考虑下面的例子:

    TypeA objA;
    

    此时你只有 declared 这个对象而不是 initialized or instantiated . 每当你尝试访问其中的任何属性或方法时,它都会抛出 NullPointerException ,这是有道理的 .

    请参阅以下示例:

    String a = null;
    System.out.println(a.toString()); // NullPointerException will be thrown
    
  • 291

    Java中,您声明的所有变量实际上都是"references"对象(或基元)而不是对象本身 .

    当您尝试执行一个对象方法时,该引用会要求生命对象执行该方法 . 但是如果引用引用NULL(nothing,zero,void,nada),则该方法无法执行 . 然后运行时通过抛出NullPointerException让您知道这一点 .

    你的引用是“指向”null,因此“Null - > Pointer” .

    该对象位于VM内存空间中,访问它的唯一方法是使用 this 引用 . 举个例子:

    public class Some {
        private int id;
        public int getId(){
            return this.id;
        }
        public setId( int newId ) {
            this.id = newId;
        }
    }
    

    在代码中的另一个位置:

    Some reference = new Some();    // Point to a new object of type Some()
    Some otherReference = null;     // Initiallly this points to NULL
    
    reference.setId( 1 );           // Execute setId method, now private var id is 1
    
    System.out.println( reference.getId() ); // Prints 1 to the console
    
    otherReference = reference      // Now they both point to the only object.
    
    reference = null;               // "reference" now point to null.
    
    // But "otherReference" still point to the "real" object so this print 1 too...
    System.out.println( otherReference.getId() );
    
    // Guess what will happen
    System.out.println( reference.getId() ); // :S Throws NullPointerException because "reference" is pointing to NULL remember...
    

    这是一个重要的事情要知道 - 当没有更多对象的引用时(在上面的例子中 referenceotherReference 都指向null),那么对象是"unreachable" . 我们无法使用它,因此该对象已准备好进行垃圾收集,并且在某些时候,VM将释放此对象使用的内存并将分配另一个 .

  • 288

    NullPointerException 是当您尝试使用指向内存中没有位置的引用(null)时发生的异常,就好像它引用了一个对象一样 . 在空引用上调用方法或尝试访问空引用的字段将触发 NullPointerException . 这些是最常见的,但NullPointerException javadoc页面上列出了其他方式 .

    可能我提出的最快的示例代码来说明 NullPointerException 将是:

    public class Example {
    
        public static void main(String[] args) {
            Object obj = null;
            obj.hashCode();
        }
    
    }
    

    main 内的第一行,我明确地将 Object 引用 obj 设置为等于 null . 这意味着我有一个引用,但它没有指向任何对象 . 之后,我尝试将引用视为通过调用其上的方法指向对象 . 这导致 NullPointerException 因为没有代码在引用指向的位置执行 .

    (这是技术性的,但我认为值得一提的是:指向null的引用与指向无效内存位置的C指针不同 . 空指针实际上并不指向任何位置,这与指向恰好无效的位置 . )

  • 283

    当一个声明一个对象数组,然后立即尝试取消引用它内部的元素时,会发生另一次出现 NullPointerException .

    String[] phrases = new String[10];
    String keyPhrase = "Bird";
    for(String phrase : phrases) {
        System.out.println(phrase.equals(keyPhrase));
    }
    

    如果比较顺序颠倒,则可以避免这种特殊的NPE;即,在保证的非null对象上使用.equals .

    数组内的所有元素are initialized to their common initial value;对于任何类型的对象数组,这意味着所有元素都是 null .

    在访问或取消引用它们之前,必须初始化数组中的元素 .

    String[] phrases = new String[] {"The bird", "A bird", "My bird", "Bird"};
    String keyPhrase = "Bird";
    for(String phrase : phrases) {
        System.out.println(phrase.equals(keyPhrase));
    }
    
  • 274

    NULL 指针是指向无处的指针 . 当你取消引用一个指针 p 时,你会说"give me the data at the location stored in " p“ . 当 p 是一个空指针时,存储在 p 中的位置是 nowhere ,你这样做,所以它会抛出一个 NULL pointer exception .

    一般来说,这是因为某些东西尚未正确初始化 .

  • 256

    已经有很多解释来解释它是如何发生的以及如何解决它,但你也应该遵循 best practices 以避免 NullPointerException .

    另见:A good list of best practices

    我想补充一点,非常重要的是,要好好利用 final 修饰符 . Using the "final" modifier whenever applicable in Java

    Summary:

    • 使用 final 修饰符强制执行良好的初始化 .

    • 避免在方法中返回null,例如在适用时返回空集合 .

    • 使用注释 @NotNull@Nullable

    • 快速失败并使用断言以避免在不应为null的情况下将null对象传播到整个应用程序 .

    • 首先使用等于已知对象: if("knownObject".equals(unknownObject)

    • 首选 valueOf() over toString() .

    • 使用null safe StringUtils methods StringUtils.isEmpty(null) .

相关问题