我可以看到 @Nullable
和 @Nonnull
注释可能有助于防止 NullPointerException
,但它们不会传播很远。
这些注释的有效性在一级间接后完全下降,所以如果你只添加一些,它们不会传播很远。
由于这些注释没有得到很好的执行,因此存在假设用 @Nonnull 标记的值不为 null 并因此不执行 null 检查的危险。
下面的代码导致标有 @Nonnull
的参数为 null
,而不会引起任何投诉。它在运行时抛出 NullPointerException
。
public class Clazz {
public static void main(String[] args){
Clazz clazz = new Clazz();
// this line raises a complaint with the IDE (IntelliJ 11)
clazz.directPathToA(null);
// this line does not
clazz.indirectPathToA(null);
}
public void indirectPathToA(Integer y){
directPathToA(y);
}
public void directPathToA(@Nonnull Integer x){
x.toString(); // do stuff to x
}
}
有没有办法让这些注释更严格地执行和/或进一步传播?
@Nullable
或 @Nonnull
的想法,但如果它们值得,它非常“可能会引起辩论”
@Nonnull
方法时要求强制转换为 @Nonnull
。当然,在 Java 7 中无法使用注解进行强制转换,但 Java 8 将添加将注解应用于变量使用的能力,包括强制转换。所以这有可能在 Java 8 中实现。
(@NonNull Integer) y
在语法上是可能的,但不允许编译器根据注释发出任何特定的字节码。对于运行时断言,微小的辅助方法就足够了,如 bugs.eclipse.org/442103 中所讨论的(例如,directPathToA(assertNonNull(y))
) - 但请注意,这只有助于快速失败。唯一安全的方法是执行实际的空值检查(希望在 else 分支中有一个替代实现)。
@Nonnull
和 @Nullable
会很有帮助,因为有多个类似的注解(请参阅 this question)。您是在谈论包 javax.annotation
中的注释吗?
简短的回答:我猜这些注释仅对您的 IDE 有用,以警告您潜在的空指针错误。
正如“清洁代码”一书中所说,您应该检查公共方法的参数,并避免检查不变量。
另一个好技巧是永远不要返回空值,而是使用 Null Object Pattern。
除了当您将 null
传递给期望参数不为 null 的方法时,您的 IDE 会给您提示之外,还有其他优点:
静态代码分析工具可以像你的 IDE 一样测试(例如 FindBugs)
您可以使用面向方面的编程 (AOP) 来检查这些断言
这可以帮助您的代码更易于维护(因为您不需要 null
检查)并且不易出错。
assert
。我发现 @Nullable
和 @Nonnull
是有用的想法,但我希望它们背后有更多的力量,而不是我们假设人们可以用它们做什么,这仍然留下了可能性对他们什么都不做。
我认为这个原始问题间接指向了一个一般建议,即仍然需要运行时空指针检查,即使使用了@NonNull。请参考以下链接:
在上面的博客中,建议:
可选类型注释不能替代运行时验证 在类型注释之前,描述可空性或范围等内容的主要位置是在 javadoc 中。使用类型注释,这种通信以编译时验证的方式进入字节码。您的代码仍应执行运行时验证。
在 Eclipse 中以合规性 1.8 编译原始示例并启用基于注释的 null 分析,我们收到以下警告:
directPathToA(y);
^
Null type safety (type annotations): The expression of type 'Integer' needs unchecked conversion to conform to '@NonNull Integer'
此警告的措辞类似于您在使用原始类型(“未经检查的转换”)将通用代码与遗留代码混合时收到的警告。我们在这里遇到完全相同的情况:方法 indirectPathToA()
有一个“遗留”签名,因为它没有指定任何空合约。工具可以轻松地报告这一点,因此它们会在需要传播但尚未传播空注释的所有小巷中追逐你。
当使用一个聪明的 @NonNullByDefault
时,我们甚至不必每次都这么说。
换句话说:空注释是否“传播得很远”可能取决于您使用的工具,以及您对工具发出的所有警告的关注程度。使用 TYPE_USE null annotations,您最终可以选择让该工具警告您程序中每一个可能的 NPE,因为 nullness 已成为类型系统的固有属性。
https://i.stack.imgur.com/bfG9h.png
激活后,所有未注释的参数都将被视为非空,因此您还会在间接调用中看到警告:
clazz.indirectPathToA(null);
对于更强大的检查,Checker Framework 可能是一个不错的选择(请参阅这个不错的 tutorial。
注意:我还没有使用它,并且 Jack 编译器可能存在问题:请参阅 { 2}
我同意注释“不会传播得很远”。但是,我看到了程序员方面的错误。
我将 Nonnull
注释理解为文档。以下方法表示需要(作为前提条件)非空参数 x
。
public void directPathToA(@Nonnull Integer x){
x.toString(); // do stuff to x
}
然后以下代码片段包含一个错误。该方法调用 directPathToA()
而不强制 y
为非空(即,它不保证被调用方法的前提条件)。一种可能性是将 Nonnull
注释也添加到 indirectPathToA()
(传播前提条件)。可能性二是检查 indirectPathToA()
中的 y
是否为 null,并在 y
为 null 时避免调用 directPathToA()
。
public void indirectPathToA(Integer y){
directPathToA(y);
}
@Nonnull
传播到 indirectPathToA(@Nonnull Integer y)
恕我直言,这是一种不好的做法:您需要在整个调用堆栈上维护传播(因此,如果您在 directPathToA()
中添加 null
签入,则需要替换 {1 } 由 @Nullable
在完整的调用堆栈中)。对于大型应用程序,这将是一项巨大的维护工作。
如果您使用 Kotlin,它会在其编译器中支持这些可空性注释,并将阻止您将空值传递给需要非空参数的 java 方法。事件虽然这个问题最初是针对 Java 的,但我提到了这个 Kotlin 功能,因为它专门针对这些 Java 注释,问题是“有没有办法让这些注释更严格地执行和/或进一步传播?”而这个特性确实让这些注解得到了更严格的执行。
使用 @NotNull
注释的 Java 类
public class MyJavaClazz {
public void foo(@NotNull String myString) {
// will result in an NPE if myString is null
myString.hashCode();
}
}
Kotlin 类调用 Java 类并为使用 @NotNull 注释的参数传递 null
class MyKotlinClazz {
fun foo() {
MyJavaClazz().foo(null)
}
}
强制执行 @NotNull
注释的 Kotlin 编译器错误。
Error:(5, 27) Kotlin: Null can not be a value of a non-null type String
见:http://kotlinlang.org/docs/reference/java-interop.html#nullability-annotations
@NotNull
,访问 myString.hashCode()
仍然会抛出 NPE。那么添加它的更具体的内容是什么?
在 Java 中,我会使用 Guava's Optional type。作为一种实际类型,您可以获得编译器对其使用的保证。绕过它并获得 NullPointerException
很容易,但至少该方法的签名清楚地传达了它期望作为参数的内容或可能返回的内容。
java.util.Optional
而不是 Guava 的类。 See Guava's notes/comparison 了解有关差异的详细信息。
由于 Java 8 的新特性Optional,您不应再在自己的代码中使用 @Nullable 或 @Notnull。举个例子:
public void printValue(@Nullable myValue) {
if (myValue != null) {
System.out.print(myValue);
} else {
System.out.print("I dont have a value");
}
它可以重写为:
public void printValue(Optional<String> myValue) {
if (myValue.ifPresent) {
System.out.print(myValue.get());
} else {
System.out.print("I dont have a value");
}
使用可选的强制您检查空值。在上面的代码中,您只能通过调用 get
方法来访问该值。
另一个优点是代码更具可读性。随着 Java 9 ifPresentOrElse 的加入,该函数甚至可以写成:
public void printValue(Optional<String> myValue) {
myValue.ifPresentOrElse(
v -> System.out.print(v),
() -> System.out.print("I dont have a value"),
)
}
Optional
,仍有许多库和框架使用这些注释,因此使用更新版本来更新/替换所有依赖项以使用 Optionals 是不可行的。但是,Optional
在您在自己的代码中使用 null 的情况下会有所帮助。
Optional
类型而不是普通的null
Optional
和可为空的区别在于,Optional
可以更好地传达此值可以有意为空。当然,它不是一根魔杖,在运行时它可能会以与可空变量完全相同的方式失败。但是,在我看来,使用Optional
程序员的 API 接收效果更好。