57

最近我的一个同事写了一些代码来捕获整个方法周围的空指针异常,并返回一个结果。我指出空指针可能有多种原因,因此我们将其更改为针对一个结果的防御性检查。

然而,捕捉 NullPointerException 对我来说似乎是错误的。在我看来,空指针异常是错误代码的结果,而不是系统中的预期异常。

是否存在捕获空指针异常有意义的情况?

4

18 回答 18

37

是的,捕捉到任何RuntimeException几乎总是一种代码气味。C2 Wiki似乎同意这一点。

一个例外可能是一些特别防御性的代码,它们运行来自其他模块的几乎随机代码。此类防御结构的示例是EDT、ThreadPools/Executors 和插件系统。

于 2010-04-06T16:04:30.233 回答
25

我可以想出一个永远捕获 a 的用途NullPointerException

catch (NullPointerException) {
    ApplyPainfulElectricShockToProgrammer();
}
于 2010-04-06T17:46:41.097 回答
15

由于第三方库中的错误,我有时不得不捕获空指针异常。我们使用的库抛出了这个异常,我们对此无能为力。

在这种情况下,抓住它是可以的,否则就不行。

于 2010-04-06T16:40:31.430 回答
7

这取决于。

这位同事的经验如何?他这样做是出于无知/懒惰,还是有真正的充分理由?(就像这是高于一切的主线程,永远不应该死?)

90% 的时间捕获运行时异常是错误的,99% 的时间捕获 NullPointerException 是错误的(如果原因是“我得到了很多......” 那么整个程序员都是错误的,你应该小心他正在做的其余代码)

但在某些情况下,捕获 NullPointerException 可能是可以接受的。

于 2010-04-06T16:12:06.910 回答
3

总的来说,我认为这是一种代码味道;在我看来,防御性检查更好。我会扩展它以涵盖大多数未经检查的异常,除了事件循环等想要捕获所有错误以进行报告/记录的情况。

我能想到的例外是对无法修改的库的调用,它可能会生成空指针异常以响应某些难以主动检查的断言失败。

于 2010-04-06T16:03:36.987 回答
3

有趣的

我刚刚发现了一些不应该在工作中做的事情:

public static boolean isValidDate(final String stringDateValue) {
    String exp = "^[0-9]{2}/[0-9]{2}/[0-9]{4}$";
    boolean isValid = false;
    try {
        if (Pattern.matches(exp, stringDateValue)) {
            String[] dateArray = stringDateValue.split("/");
            if (dateArray.length == 3) {
                GregorianCalendar gregorianCalendar = new GregorianCalendar();
                int annee = new Integer(dateArray[2]).intValue();
                int mois = new Integer(dateArray[1]).intValue();
                int jour = new Integer(dateArray[0]).intValue();

                gregorianCalendar = new GregorianCalendar(annee, mois - 1,
                        jour);
                gregorianCalendar.setLenient(false);
                gregorianCalendar.get(GregorianCalendar.YEAR);
                gregorianCalendar.get(GregorianCalendar.MONTH);
                gregorianCalendar.get(GregorianCalendar.DAY_OF_MONTH);
                isValid = true;
            }
        }
    } catch (Exception e) {
        isValid = false;
    }
    return isValid;
}

巴阿德 :)

开发人员希望日历引发此类异常:

java.lang.IllegalArgumentException: DAY_OF_MONTH
    at java.util.GregorianCalendar.computeTime(GregorianCalendar.java:2316)
    at java.util.Calendar.updateTime(Calendar.java:2260)
    at java.util.Calendar.complete(Calendar.java:1305)
    at java.util.Calendar.get(Calendar.java:1088)

使值无效...

是的,它有效,但这不是一个很好的做法......

引发异常(特别是填充堆栈跟踪)的成本远远超过手动检查数据而没有异常......

于 2010-04-07T14:40:52.180 回答
3

这很糟糕,但它可以产生优化的字节码。

如果大多数时间i不是整数,则检查会降低整体性能。null检查本身需要 3 条指令 (0-4)。然后整个案例需要 7 条指令(0-14)。

public class IfNotNull {

    public Integer i;

    public String getIAsString() {
        if (i != null) {
            return i.toString();
        } else {
            return "";
        }
    }
}

  public java.lang.String getIAsString();
    Code:
       0: aload_0       
       1: getfield      #2                  // Field i:Ljava/lang/Integer;
       4: ifnull        15
       7: aload_0       
       8: getfield      #2                  // Field i:Ljava/lang/Integer;
      11: invokevirtual #3                  // Method java/lang/Integer.toString:()Ljava/lang/String;
      14: areturn       // <- here we go
      15: ldc           #4                  // String 
      17: areturn 

遵循Python 世界中常见的EAFP方法。案例会null很昂贵,但我们只需要 4 条指令 (0-7) 即可not null

public class TryCatch {

    public Integer i;

    public String getIAsString() {
        try {
            return i.toString();
        } catch (NullPointerException npe) {
            return "";
        }
    }
}

  public java.lang.String getIAsString();
    Code:
       0: aload_0       
       1: getfield      #2                  // Field i:Ljava/lang/Integer;
       4: invokevirtual #3                  // Method java/lang/Integer.toString:()Ljava/lang/String;
       7: areturn       // <- here we go
       8: astore_1      
       9: ldc           #5                  // String a
      11: areturn       
    Exception table:
       from    to  target type
           0     7     8   Class java/lang/NullPointerException

谁知道,如果 JIT 编译器可以优化这个?

于 2014-02-27T16:54:46.867 回答
2

那当然是。

大多数情况下,您的变量一开始就不应该为空。许多新语言都内置了对不可为空的引用类型的支持——即保证永远不会为空的类型。

对于允许传入值为空的时间,您需要进行检查。但是例外绝对是一种不好的方法。

if 语句可能需要执行三个指令,并且是本地检查(意思是,您在需要保证的地方进行检查)。

另一方面,使用异常可能需要更多的指令——系统尝试查找方法,失败,在异常表中查找适当的异常处理程序,跳转到那里,执行处理程序,然后再次跳转。此外,检查可能是非本地的。如果您的代码是这样的:

try
  return contacts.find("Mom").getEmail()
catch (NullPointerException e)
  return null

您不知道 NPE 是在“getEmail”还是“find”中抛出的。

以更模糊的方式编写的非常非常常见的模式的技术更糟糕的解决方案?它不是等级,但它肯定闻起来很糟糕:/

于 2010-04-06T16:16:44.710 回答
2

您应该捕获 NullPointerException(或者具体来说,只是任何 Throwable)的唯一位置是在某个顶级或系统边界,这样您的程序就不会完全崩溃并且可以恢复。例如,在您的 web.xml 中设置错误页面提供了一个包罗万象的功能,以便 Web 应用程序可以从异常中恢复并通知用户。

于 2010-04-06T16:48:48.787 回答
1

很久以前,我有一个用途。当通过键请求集合中的对象并且找不到对象时,一个特别愚蠢的库会抛出 NullPointerException。除了按键之外没有其他方法可以查找,也无法检查对象是否存在。

一段时间后,我们启动了供应商并开始修改库。现在库抛出一个更好的异常(我的更改)并具有检查功能(其他人的更改)。

当然,我总是会在 try 块中得到恰好一行。再多的话,我自己就会犯下糟糕的代码。

于 2010-04-06T18:25:48.600 回答
1

捕获 NULL 指针异常实际上取决于上下文...应该努力避免严格的绝对规则...规则应该在上下文中应用 - 想要捕获此异常并将整个软件置于某种 STABLE 状态 - 什么都不做或几乎不做几乎没有。所有这些编码规则都应该很好理解

在这一点上,您然后查看您的软件 AUDIT TRACE ...您应该执行的操作并发现此异常的 SOURCE。

NULL 指针异常永远不会发生的想法必须是可验证的。首先进行静态分析......(如果有 3rd 方代码/组件进来,这会更难),然后使用相关工具进行详尽的状态空间搜索。

X

于 2010-04-06T18:45:40.137 回答
1

捕获 NPE(实际上是任何 RTE)对于干净地终止基于 Swing-GUI 的应用程序是必要的。

编辑:在这种情况下,它通常是通过 UncaughtExceptionHandler 完成的。

于 2010-04-06T20:40:29.950 回答
1

那这个呢:

try
{
    foo.x = bar;
}
catch (NullPointerException e)
{
    // do whatever needs to be done
}

当 foo 可能为空但几乎从不为空时作为微优化?

这个想法是这样的:

  • 显式 NULL 检查需要一条机器指令

  • 另一方面,第二个版本中的 NULL 检查可以通过让 NULL 访问发生、捕获 SIGSEGV 并抛出 NullPointerException 来完成。如果对象不为 NULL,则这是免费的。

于 2013-12-21T11:47:15.670 回答
0

我试图保证我的接口的结果,但如果某些库或某人的代码可以产生 null 作为结果,我希望保证捕获它可能是可行的。当然,一旦你抓住它,你会做什么取决于你。有时检查空值是没有意义的,如果你发现它,你有一些其他的方法来解决问题,可能不是那么好,但可以完成工作。

我说的是尽你所能使用例外,这是一个非常好的语言功能。

于 2010-04-06T16:29:08.370 回答
0

如果您的方法调用外部接口(或 SOAP API)并且返回的值可能为 Null,则捕获 NullPointerException 可能很有用。除此之外,捕获这些异常并没有太大的好处。

于 2010-04-06T17:03:14.620 回答
0

这实际上取决于接口定义。非结构化 NPE 处理与捕获 Exception 或 Throwable 一样糟糕。

Null 可用于识别未初始化的状态,而不是使用空字符串或 max_int 或其他任何东西。一旦我经常使用 null 的地方是回调对象不相关的地方。

我真的很喜欢 Guice 提供的 @Nullable 注释。

http://code.google.com/docreader/#p=google-guice&s=google-guice&t=UseNullable

要消除代码库中的 NullPointerExceptions,您必须注意空引用。通过遵循并执行一个简单的规则,我们在这方面取得了成功:

除非明确指定,否则每个参数都是非空的。Google Collections 库和 JSR-305 具有简单的 API 来控制空值。Preconditions.checkNotNull 可用于在找到空引用时快速失败,@Nullable 可用于注释允许空值的参数。

Guice 默认禁止 null。它将拒绝注入 null,而是以 ProvisionException 失败。如果您的类允许 null,您可以使用 @Nullable 注释字段或参数。Guice 可以识别任何 @Nullable 注解,例如 edu.umd.cs.findbugs.annotations.Nullable 或 javax.annotation.Nullable。

于 2010-04-06T17:36:46.980 回答
0

是的,在 Java 中需要检查 NullPointerException。

当应用程序在需要对象的情况下尝试使用 null 时引发。这些包括:

调用空对象的实例方法。访问或修改空对象的字段。将 null 的长度视为一个数组。访问或修改 null 的槽,就像它是一个数组一样。把 null 当作一个 Throwable 值来抛出。

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

读取文本文件(即 XML)时其他语言中的 NullPointerException,其记录尚未验证为正确的 ASCII 字符和记录格式。

于 2010-04-06T18:43:34.520 回答
0

如果程序员是初学者,他可能习惯于捕捉每一个阻止他获得最终输出的异常。代码审查者不应该对此感到满意。

捕获任何 RuntimeException 都是不好的。但是,如果确实有必要,那么代码中的注释将对将来处理该代码的程序员非常有帮助。如果你不能写一个合理的评论来捕捉它们,那么你必须避免它们。时期。

于 2016-09-25T14:55:52.050 回答