避免!= null语句
我用object != null
了很多东西来避免NullPointerException
.
有没有一个很好的替代品呢?
例如:
if (someobject != null) {
someobject.doCalc();
}
NullPointerException
当不知道对象是否存在时,这避免了a null
.
请注意,接受的答案可能已过期,请参阅/sf/answers/167020941/以获取更新的方法.
回答
这对我来说听起来像是一个相当普遍的问题,初级到中级开发人员往往会在某些时候面临这样的问题:他们要么不知道,要么不信任他们参与的合同,并且防御性地过度检查空值.此外,在编写自己的代码时,它们倾向于依赖返回空值来指示某些内容,从而要求调用者检查空值.
换句话说,有两个实例进行空检查:
(2)很容易.使用assert
语句(断言)或允许失败(例如, NullPointerException).断言是1.4中添加的高度未充分利用的Java功能.语法是:
assert <condition>
要么
assert <condition> : <object>
where <condition>
是一个布尔表达式,<object>
是一个对象,其toString()
方法的输出将包含在错误中.
一个assert
语句抛出一个Error
(AssertionError
如果条件是不正确的).默认情况下,Java会忽略断言.您可以通过将选项传递-ea
给JVM 来启用断言.您可以为各个类和包启用和禁用断言.这意味着您可以在开发和测试时使用断言验证代码,并在生产环境中禁用它们,尽管我的测试表明,断言没有性能影响.
在这种情况下不使用断言是正常的,因为代码将失败,如果使用断言将会发生这种情况.唯一的区别是,断言可能会更快地发生,以更有意义的方式发生,并且可能带有额外的信息,这可能会帮助您弄清楚如果您没有预料到它会发生的原因.
(1)有点难.如果您无法控制您正在调用的代码,那么您就会陷入困境.如果null是有效响应,则必须检查它.
如果它是你控制的代码(然而通常就是这种情况),那么这是一个不同的故事.避免使用空值作为响应.使用返回集合的方法,很容易:几乎一直返回空集合(或数组)而不是null.
对于非收藏品,它可能会更难.以此为例:如果您有这些接口:
public interface Action {
void doSomething();
}
public interface Parser {
Action findAction(String userInput);
}
其中Parser接受原始用户输入并找到要做的事情,也许是在为某些事情实现命令行界面时.现在,如果没有适当的操作,您可以使合同返回null.这导致你正在谈论的空检查.
另一种解决方案是永远不会返回null,而是使用Null Object模式:
public class MyParser implements Parser {
private static Action DO_NOTHING = new Action() {
public void doSomething() { /* do nothing */ }
};
public Action findAction(String userInput) {
// ...
if ( /* we can't find any actions */ ) {
return DO_NOTHING;
}
}
}
相比:
Parser parser = ParserFactory.getParser();
if (parser == null) {
// now what?
// this would be an example of where null isn't (or shouldn't be) a valid response
}
Action action = parser.findAction(someInput);
if (action == null) {
// do nothing
} else {
action.doSomething();
}
至
ParserFactory.getParser().findAction(someInput).doSomething();
这是一个更好的设计,因为它导致更简洁的代码.
也就是说,也许findAction()方法完全适合抛出带有意义错误消息的异常 - 特别是在你依赖用户输入的情况下.对于findAction方法抛出一个Exception比调用方法爆炸一个没有解释的简单NullPointerException要好得多.
try {
ParserFactory.getParser().findAction(someInput).doSomething();
} catch(ActionNotFoundException anfe) {
userConsole.err(anfe.getMessage());
}
或者,如果您认为try/catch机制太难看,而不是Do Nothing,则默认操作应该向用户提供反馈.
public Action findAction(final String userInput) {
/* Code to return requested Action if found */
return new Action() {
public void doSomething() {
userConsole.err("Action not found: " + userInput);
}
}
}
- 我不同意你对DO_NOTHING行动的陈述.如果find动作方法找不到动作,那么返回null是正确的做法.您已经"找到"代码中的一个动作,该动作未真正找到,这违反了该方法的原则,以找到可用的动作.
- 我同意null在Java中被过度使用,特别是对于列表.如果返回空列表/数组/集合而不是null,那么很多api会更好.很多时候,在应该抛出异常的地方使用null.如果解析器无法解析,则应抛出异常.
- 这里的后一个例子是IIRC,Null对象设计模式.
- @Cshah(和MetroidFan2002).很简单,把它放到合同中然后很明显,一个未找到的返回动作将什么都不做.如果这是调用者的重要信息,那么提供一种方法来发现它是一个未找到的动作(即提供一种方法来检查结果是否是DO_NOTHING对象).或者,如果通常应该找到操作,那么您仍然不应该返回null,而是抛出一个特别指示该条件的异常 - 这仍然会产生更好的代码.如果需要,提供一个单独的方法返回布尔值以检查操作是否存在.
- 简洁不等于质量代码.对不起,你这么认为.您的代码隐藏了错误有利的情况.
- 检查空值没有任何问题,我看到很多高级/摇滚明星开发人员到处检查空值.此外,空检查是编程语言中最便宜的操作之一.
- 断言有利于测试.在PROD上使用是危险的.
- 同意DarthVader.我认为不够防守是初级的事情.有极少数的图书馆在那里,在我的经验很少有程序员有纪律,完整地描述他们的合同(这包括是否可能空值的参数返回/可接受值),所以往往没有太多的信任.
- 我不同意DO_NOTHING,因为虽然它消除了那个空检查,但结果行为是意外的.如果null是您的方法的意外输入,那么您的方法抛出NPE以便调用者知道并记录事物(快速失败行为)是恰当的.使用DO_NOTHING时,它就像所有传递一样,但结果动作没有发生,用户将重做动作(重新提交表单).我同意返回空列表,对象但不是空值作为返回值,但不同意上面说明的DO_NOTHING.但NPE不应该在UI上显示堆栈跟踪
- 我可能会创建一个hasAction(userInput)方法而不是DO_NOTHING操作,如果对该userInput有有效的操作,则返回true或false.然后findAction将返回该操作或抛出异常ActionNotFoundException
- @ThorbjørnRavnAndersen通过隐藏findAction方法可能*不*找到动作的可能性,你现在已经完成了逻辑,虽然它没有破坏程序,但可能会导致不需要的效果,直到很多后期(可能在生产中).取决于findAction是否真的可以找不到任何东西,这是非常不可取的 - 返回null或抛出异常,就像imaginaryboy建议的那样,是更好的方法,以便可以明确地检查它们并且路径被用来解释缺乏查找一种行为.
- 通常,无论您在何处收到null对象,如果对象为null,您要实现的行为都是类似的.正如其他人所指出的那样,答案的DO_NOTHING动作实际上是Null对象模式(http://en.wikipedia.org/wiki/Null_Object_pattern),它收集了"如果它为空,我们该怎么办?" 逻辑在一个地方,因此减少重复.但是,Null对象模式实际上只是Martin Fowler特殊案例模式的一个更有限的版本:(http://martinfowler.com/eaaCatalog/specialCase.html),它非常适合消除除null之外的值的检查.
- 我认为在源代码中应该可以指定从不从方法X返回null并且给定参数不应该为null并且传递一个参数是错误的.
- @MetroidFan,如果你返回一个没有做任何事情的有效Action你有一个没有分支的更简单的调用者.分支导致额外的复杂性,并尽可能少地保持它们使得更简单的测试.
- @SnOrfus +1提及Null对象设计模式.我在这里学到的东西.见http://en.wikipedia.org/wiki/Null_Object_pattern
- 我反对代码臃肿并同意这个解决方案.显式检查NULL是一种反模式恕我直言.Java方法是抛出*NotFound/InvalidAccess等异常,这避免了调用者明确处理不规则情况的必要性.我甚至更进一步:使用arg.equals("foo")而不是"foo".equals(arg),或使用本机类型(long而不是Long)隐式FORCE NPE.幸运的是,当返回一个null对象时,调用者仍然可以尝试在该null值上调用一个方法并强制执行NPE.:-)))))
- @Ziggy这不是我不同意null对象模式,我一直都在使用它.在这种特殊情况下,方法签名是*findAction*根据定义,DO_NOTHING操作未找到!这对你的用户说谎.并且让用户必须检查DO_NOTHING是否违反了多态性 - 现在不是*所有*您的实例可以互换使用.换句话说,假设该方法是BigDecimal findBigDecimal(String what) - Null Object模式在这里不起作用.如果返回零则找到一些东西.
- 现在在Java8中有一个更好的解决方案:使用Optional作为返回值来表示可以为空的值.这样调用者就知道它是一个可选值,并且可以使用Optional提供的所有方法来处理该值.
- 当他们只为你失败时,使用断言是什么意思?是的,你可以在你工作的时候捕捉东西,但是在生产中如果你在奇怪的情况下从未听说过它们会有什么帮助?
- 总的来说,问题不在于开发商不了解或信任合同; 问题是*没有合同.根据我对几个硬件供应商发布的Android SDK的经验,没有文档或没有有用的文档.香港和班加罗尔的代码工厂从未听说过Javadoc.
- 我不明白为什么每个人都讨厌DO_NOTHING.当然,在这个例子中它是简约的 - 但这是一种编程语言...在类中添加"isEmptyAction()"方法......重点是ENCODE对象的状态,而不是返回一些神秘的缺乏你不知何故必须知道检查的值..."isEmptyAction()"是一个实际的API,它编码一个动作可能为空/什么也不做的事实.非常清洁,imo(和许多其他人,因此它是它自己定义的设计模式......)
- 抛出NPE是一个非常明智的事情,当某些东西是*时不应该*.顺便说一下,如果你正在处理并发性,你应该小心,因为当你使用它时,检查它时不为null的东西可能是null.
- 我在编码时是防御性的,并进行空检查.特别是在J2EE环境中.我的想法是,任何可以为null的东西......在某些时候都可能是空的.在契约上,在大多数情况下,我不想抛出非法的异常或nullpointer,因为Java首先不应该有空引用.在大多数情况下,我尝试查找默认返回值,并使用默认值/预期返回值快速失败.
- assert对我来说是无法使用的,因为它必须被显式启用 - 我希望我的断言始终被启用.有人说:"但是在生产中,如果你在奇怪的情况下从未听说过这种情况,他们会如何帮助?" 简单 - 尽可能接近问题的根本原因,您可以获得精确且清晰的故障,并且可以轻松地对其进行修补,而不是对整个系统进行逆向工程,以确定故障的位置.这就是为什么按合同设计,快速失败,无空设计是唯一的方法......而且从不使用'断言',使用一种始终启用的机制....
- 我永远不会返回null,所以我从不检查null的返回值.我永远不需要考虑哪些变量是有效的.他们都是.我从不将null传递给方法,因此我的方法中从不需要任何输入验证.如果实际上有一个允许传递null参数的方法是合乎逻辑的,我会创建两个方法.通过打破公共资源来最小化代码重复是很重要的.
- 我会选择Optional/Option或例外.返回null对你的来电者来说只是毒药; 他们会忘记处理它并看到远离呼叫站点的问题.
- @MevenidFan2002:同意了:DO_NOTHING,但并不完全.如果find动作方法找不到动作,那么该方法没有按照它所说的那样做.在那种情况下,它不应该返回.它应该抛出.
- 我在生产中看到太多愚蠢的NullPointer异常,以为防御性检查null是“初级到中级开发人员的问题”,我认为,总是总是进行Null检查
- @ MetroidFan2002“如果find操作方法找不到操作,则返回null是正确的事情。”这完全取决于程序设计者来决定什么都不做的操作是否适合他的用例。如果操作符合您的设计要求,则不执行任何操作是完全有效的。您暗示真实/虚假动作差异对调用者很重要,但是根据您的用例,两种方法均有效。找不到动作对调用者很重要的情况也是合法的,但可以通过其他方法(而不是返回null)进行通信
如果您使用(或计划使用)Java IDE,如JetBrains IntelliJ IDEA,Eclipse或Netbeans或像findbugs这样的工具,那么您可以使用注释来解决此问题.
基本上,你已经@Nullable
和@NotNull
.
您可以在方法和参数中使用,如下所示:
@NotNull public static String helloWorld() {
return "Hello World";
}
要么
@Nullable public static String helloWorld() {
return "Hello World";
}
第二个示例将无法编译(在IntelliJ IDEA中).
当您helloWorld()
在另一段代码中使用第一个函数时:
public static void main(String[] args)
{
String result = helloWorld();
if(result != null) {
System.out.println(result);
}
}
现在,IntelliJ IDEA编译器会告诉您检查没用,因为该helloWorld()
函数永远不会返回null
.
使用参数
void someMethod(@NotNull someParameter) { }
如果你写的东西像:
someMethod(null);
这不会编译.
上一个例子使用 @Nullable
@Nullable iWantToDestroyEverything() { return null; }
这样做
iWantToDestroyEverything().something();
你可以肯定这不会发生.:)
这是一种很好的方式让编译器检查比通常更多的东西,并强制你的合同更强.不幸的是,并非所有编译器都支持它.
在IntelliJ IDEA 10.5及更高版本中,他们添加了对任何其他@Nullable
@NotNull
实现的支持.
请参阅博客文章更灵活和可配置的@Nullable/@NotNull注释.
- `@NotNull`,`@ Nullable`和其他nullness注释是[JSR 305]的一部分(http://jcp.org/en/jsr/detail?id=305).您还可以使用它们来检测[FindBugs](http://findbugs.sourceforge.net/)等工具的潜在问题.
- 关于注释的好处,`@NotNull`和`@Nullable`是因为当源代码由不理解它们的系统构建时它们会很好地降级.因此,实际上,代码不可移植的论点可能是无效的 - 如果您使用支持和理解这些注释的系统,您将获得更严格的错误检查的额外好处,否则您会得到更少但是您的代码仍然应该构建正常,运行程序的质量是相同的,因为这些注释无论如何都没有在运行时强制执行.此外,所有编译器都是定制的;-)
- 我发现奇怪的是,`@NotNull`和`@Nullable`接口存在于`com.sun.istack.internal`包中.(我想我将com.sun与关于使用专有API的警告联系起来.)
- 使用jetbrains进行代码可移植性为null.在关注ide级别之前,我会三思而后行(正方形).就像Jacek S所说的那样,他们是JSR的一部分,我认为顺便说一句,这就是JSR303.
- 我真的不认为使用自定义编译器是解决此问题的可行方案.
- 针对"@ Nullable"/`@NotNull`的"兼容性"参数缺失了.它们仅用于帮助工具(来自静态分析)确保满足这些基本合同 - 它们*不*替换适当的空值处理,当合适时(或根据输入源可能).虽然Java和C#(以及运行时)实际上支持类型系统中的非空引用类型,但是使用更好的静态分析工具(以及显式注释合同)可以大大减少100万亿美元错误即Algo,是Java,是C#..
如果不允许空值
如果您的方法是外部调用的,请从以下内容开始:
public void method(Object object) {
if (object == null) {
throw new IllegalArgumentException("...");
}
然后,在该方法的其余部分中,您将知道它object
不是null.
如果它是一个内部方法(不是API的一部分),只需记录它不能为空,就是这样.
例:
public String getFirst3Chars(String text) {
return text.subString(0, 3);
}
但是,如果您的方法只是传递了值,并且下一个方法将其传递给它,则可能会出现问题.在这种情况下,您可能需要检查上面的参数.
如果允许null
这真的取决于.如果发现我经常做这样的事情:
if (object == null) {
// something
} else {
// something else
}
所以我分支,做两件完全不同的事情.没有丑陋的代码片段,因为我真的需要根据数据做两件事.例如,我应该处理输入,还是应该计算一个好的默认值?
我真的很少使用这个成语" if (object != null && ...
".
如果您显示通常使用成语的示例,则可能更容易为您提供示例.
- 抛出IllegalArgumentException有什么意义?我认为NullPointerException会更清楚,如果你不自己进行空检查,也会抛出.我要么使用断言,要么根本不使用断言.
- 除null之外的所有其他值都不可能是可接受的.你可能有IllegalArgumentException,OutOfRageException等等.有时这是有道理的.其他时候你最终创建了许多不添加任何值的异常类,那么你只需使用IllegalArgumentException.对于null输入有一个例外是没有意义的,而对于其他一切有另一个例外.
- 一个安全漏洞?JDK充满了这样的代码.如果您不希望用户看到堆栈跟踪,则只需禁用它们.没有人暗示行为没有记录.MySQL是用C语言编写的,其中解除引用空指针是未定义的行为,就像抛出异常一样.
- `throw new IllegalArgumentException("object == null")`
- 是的,我同意失败 - 快速原则,但在上面给出的示例中,值不会传递,而是应该调用方法的对象.所以它失败同样快,并且添加一个空检查只是为了抛出一个异常,无论如何在同一时间和地点抛出它似乎不会使调试更容易.
- 1/2我必须同意,对于这些类型的情况,`IllegalArgumentException`比普通的'NPE`更好.我看到一个NPE作为代码中的某个地方的指示,一些不安全地取消引用恰好为null的表达式(假设满足所有已知和声明的前置条件和不变量).一个非法的争论异常OTH告诉我一个众所周知的先决条件或不变量未得到满足.
- 2/2 - 也就是说,NPE告诉我在方法中(或在它调用的方法中)还有一个未记录的空错误,或者我们没有编码的空前置条件.一个非法的参数异常告诉我这个方法的*之外的*存在于调用链的某个地方,导致当前的方法.坚持这种模式(明显地在理性范围内),它确实导致更快的根本原因错误分析(特别是令人讨厌,相互交织的错误).
- 我同意@Axel的意思是IllegalArgumentException意味着(因为我仍然使用它),参数的前提条件不成立,因为它与参数上的_constraint_有关.null参数通常只是"编程中的一般错误"而不是应用程序/ API特定约束.为了保持防御性的失败快速策略,没有什么可以阻止任何人检查null并抛出NullPointerException ......这恰好传达了所发生的事情,即"你没有正确编程":)
- "null参数通常只是"编程中的一般错误",而不是应用程序/ API特定约束." 幼稚.您可以将此作为DoS的安全漏洞或尝试获取堆栈跟踪信息(如果此类信息未被抑制).http://osvdb.org/show/osvdb/67379如果您的API是公开的,并且您没有明确定义您接受的边界 - 无论是通过API文档还是您可以用来确保合同有效性的任何工具 - 来电者可以自由地做他们想做的事.__你是那个没有正确编程的人,而不是来电者.
- @myplacedk我认为对于传入的不正确值传递的空值有一个不同的异常是有意义的.这两个通常是不同类型的编程错误的结果.空值通常来自某些未发生的必要计算,而其他问题通常来自所需的计算不正确.
- 抛出`NullPointerException`是Java约定。参见例如[`Objects.requireNonNull`](http://docs.oracle.com/javase/8/docs/api/java/util/Objects.html#requireNonNull-T-),它主要是*“用于进行参数验证在方法和构造函数中” *,并抛出“ NullPointerException”。[NullPointerException`](http://docs.oracle.com/javase/8/docs/api/java/lang/NullPointerException.html)的文档也很简单,该文档指出*“应用程序应抛出此错误的实例类,用于指示对“ null”对象的其他非法使用” *。
哇,当我们有57种不同的方式推荐时,我几乎NullObject pattern
不想添加另一个答案,但我认为有些人对这个问题感兴趣可能想知道桌面上有一个提议,Java 7添加"null-safe"处理" -a-if-not-equal-null逻辑的简化语法.
Alex Miller给出的例子如下:
public String getPostcode(Person person) {
return person?.getAddress()?.getPostcode();
}
该?.
装置仅去参考,如果它不为空左侧标识符,否则评估表达为其余部分null
.有些人,比如Java Posse成员Dick Wall和Devoxx的选民都非常喜欢这个提议,但也存在反对意见,理由是它实际上会鼓励更多地使用null
作为哨兵价值.
更新:已在Project Coin下提交了Java 7中零安全运算符的官方提议.语法与上面的示例略有不同,但它是相同的概念.
更新:零安全运营商提案未进入项目硬币.因此,您将不会在Java 7中看到此语法.
- 我认为这是错误的.应该有一种方法来指定给定变量始终为非null.
- 有趣的想法,但语法的选择是荒谬的; 我不希望代码库中充满了每个关节的问号.
- 此运算符存在于[Groovy](http://groovy.codehaus.org/Operators#Operators-SafeNavigationOperator)中,因此想要使用它的人仍然可以选择它.
- 这是我见过的最巧妙的想法.它应该添加到C语法的每个合理语言中.我宁愿在任何地方"钉上问号"而不是滚动屏幕线或整天躲避"警卫条款".
- 更新:提案不会制作Java7.请参阅http://blogs.sun.com/darcy/entry/project_coin_final_five.
- Tony Hoare为发明"null"而道歉.但更大的错误就是选择一种策略,坚持使用它超过20年,直到写入数十亿行代码,然后最终改变主意.这个`.?`就像是保持java"干净"的最后机会.这是一个非常简单的语法解决方案.但相反,他们更喜欢投入一些不同的`@Nullable`和`Option`类,空集合,空对象.目标是使可空性更容易.但相反,他们增加了许多层次的复杂性.最后,这将拆分java社区并将其推向破坏.
- Java8中添加了可选项
- Kotlin也有null安全呼叫运营商.https://kotlinlang.org/docs/reference/null-safety.html#safe-calls
- 但是在同一页面上,请参阅http://types.cs.washington.edu/jsr308/的链接,了解有关使用Checker框架在Java 7中添加@Nullable和@NonNull类型注释的信息.
如果不允许未定义的值:
您可以配置IDE以警告您可能的空取消引用.例如,在Eclipse中,请参阅首选项> Java>编译器>错误/警告/空分析.
如果允许未定义的值:
如果要定义未定义值有意义的新API,请使用选项模式(可能熟悉函数式语言).它具有以下优点:
- 在API中明确说明输入或输出是否存在.
- 编译器强制您处理"未定义"的情况.
- Option是monad,因此不需要详细的null检查,只需使用map/foreach/getOrElse或类似的组合器来安全地使用该值(示例).
Java 8有一个内置Optional
类(推荐); 对于早期版本,有库的替代品,例如番石榴的Optional
或FunctionalJava的Option
.但是,与许多功能样式模式一样,在Java中使用Option(甚至8)会产生相当多的样板,使用较简洁的JVM语言(例如Scala或Xtend)可以减少这种样板.
如果你必须处理可能返回null的API,那么你在Java中做不了多少.Xtend和Groovy有Elvis运算符 ?:
和null-safe dereference运算符 ?.
,但请注意,如果是null引用,则返回null,因此它只是"延迟"null的正确处理.
- 实际上,Option模式非常棒.存在一些Java等价物.Guava包含一个名为Optional的限制版本,它遗漏了大部分功能.在Haskell中,这种模式称为Maybe.
- Java 8中将提供Optional类
- Optional模式没有解决任何问题; 而不是一个潜在的空对象,现在你有两个.
仅限于这种情况 -
在调用equals方法之前不检查变量是否为null(下面的字符串比较示例):
if ( foo.equals("bar") ) {
// ...
}
将导致NullPointerException
if foo
不存在.
如果你比较你的String
s ,你可以避免这种情况:
if ( "bar".equals(foo) ) {
// ...
}
- 我同意 - 只是在那种情况下.我不能忍受程序员已经把它带到下一个不必要的级别并写if(null!= myVar)...只是看起来很丑我没有用!
- 这是一个特殊的例子,可能是最常用的一般做法:如果你知道它,总是做`<你知道不是null的对象> .equals(<可能是null的对象>);`.如果你知道契约并且那些方法可以处理`null`参数,它适用于除`equals`之外的其他方法.
- 这是我见过的[Yoda Conditions](http://www.codinghorror.com/blog/2012/07/new-programming-jargon.html)的第一个实际有用的例子
- 出于某种原因抛出NullPointerExceptions.它们被抛出,因为一个对象在它不应该的地方是null.程序员的工作是修复这个,而不是隐藏问题.
随着Java 8的出现,新的java.util.Optional
类可以解决一些问题.至少可以说它提高了代码的可读性,在公共API的情况下,使API的合同更清晰.
他们的工作方式如下:
Fruit
创建给定type()的可选对象作为方法的返回类型.它可以是空的或包含一个Fruit
对象:
public static Optional<Fruit> find(String name, List<Fruit> fruits) {
for (Fruit fruit : fruits) {
if (fruit.getName().equals(name)) {
return Optional.of(fruit);
}
}
return Optional.empty();
}
现在看一下我们搜索给定Fruit实例的Fruit
(fruits
)列表的代码:
Optional<Fruit> found = find("lemon", fruits);
if (found.isPresent()) {
Fruit fruit = found.get();
String name = fruit.getName();
}
您可以使用map()
运算符对可选对象执行计算或从中提取值. orElse()
允许您提供缺失值的后备.
String nameOrNull = find("lemon", fruits)
.map(f -> f.getName())
.orElse("empty-name");
当然,检查空值/空值仍然是必要的,但至少开发人员意识到值可能是空的并且忘记检查的风险是有限的.
在从头开始构建的API中,Optional
只要返回值可能为空,并且只有当它不能null
(约定)时才返回普通对象,客户端代码可能会放弃对简单对象返回值的空检查...
当然Optional
也可以用作方法参数,在某些情况下,可能是指示可选参数而不是5或10重载方法的更好方法.
Optional
提供其他方便的方法,例如orElse
允许使用默认值,并ifPresent
使用lambda表达式.
我邀请你阅读这篇文章(我写这个答案的主要来源),其中NullPointerException
(和一般的空指针)问题以及带来的(部分)解决方案Optional
都得到了很好的解释:Java可选对象.
- 重要的是要强调使用Optional和ifPresent()不会*在正常的空值检查之上添加很多值.它的核心价值在于它是一个可以在map/flapMap的函数链中使用的monad,它实现了与其他地方提到的Groovy中的Elvis运算符类似的结果.即使没有这种用法,我发现orElse/orElseThrow语法也非常有用.
- 谷歌的番石榴有一个可选的Java 6+版本.
- 人们为什么倾向于这样做?if(optional.isPresent()){optional.get(); }”而不是“ optional.ifPresent(o-> {...})”
根据您检查的对象类型,您可以使用apache公共中的一些类,例如:apache commons lang和apache commons collections
例:
String foo;
...
if( StringUtils.isBlank( foo ) ) {
///do something
}
或(取决于您需要检查的内容):
String foo;
...
if( StringUtils.isEmpty( foo ) ) {
///do something
}
StringUtils类只是其中之一; 在公共场所中有很多优秀的类可以安全操作.
下面是一个如何在包含apache库(commons-lang-2.4.jar)时在JAVA中使用null vallidation的示例
public DOCUMENT read(String xml, ValidationEventHandler validationEventHandler) {
Validate.notNull(validationEventHandler,"ValidationHandler not Injected");
return read(new StringReader(xml), true, validationEventHandler);
}
如果您使用的是Spring,Spring在其包中也具有相同的功能,请参阅library(spring-2.4.6.jar)
有关如何从spring使用此静态classf的示例(org.springframework.util.Assert)
Assert.notNull(validationEventHandler,"ValidationHandler not Injected");
- 您也可以使用Apache Commons中更通用的版本,在检查我发现的参数的方法开始时非常有用.Validate.notNull(object,"object must not null"); http://commons.apache.org/lang/apidocs/org/apache/commons/lang/Validate.html
- 如果你认为一个对象不应该为null(或者它是一个bug),请使用一个断言.
- 如果你的方法不接受null params在javadoc中说它并使用断言.
只有当你想要处理对象可能为null的情况时,你必须检查对象!= null ...
有人建议在Java7中添加新的注释来帮助null/notnull params:http://tech.puredanger.com/java7/#jsr308
- 不,[不要在生产代码中使用断言](http://stackoverflow.com/a/21245984/3779853).
我是"快速失败"代码的粉丝.问问自己 - 在参数为null的情况下,您是否正在做一些有用的事情?如果你没有一个明确的答案,你的代码在这种情况下应该做什么...即它首先应该永远不为null,然后忽略它并允许抛出NullPointerException.调用代码与IllegalArgumentException一样对NPE有同样的意义,但是如果抛出NPE而不是代码试图执行其他意外的意外事件,开发人员将更容易调试并理解出现了什么问题.逻辑 - 最终导致应用程序失败.
- 更好地使用断言,即Contract.notNull(abc,"abc必须为非null,是否在xyz期间无法加载?"); - 这比执行if(abc!= null){throw new RuntimeException ...}更紧凑.
Google集合框架提供了一种实现空检查的优雅方式.
像这样的库类中有一个方法:
static <T> T checkNotNull(T e) {
if (e == null) {
throw new NullPointerException();
}
return e;
}
用法是(带import static
):
...
void foo(int a, Person p) {
if (checkNotNull(p).getAge() > a) {
...
}
else {
...
}
}
...
或者在你的例子中:
checkNotNull(someobject).doCalc();
- 嗯,有什么区别?p.getAge()会以更少的开销和更清晰的堆栈跟踪抛出相同的NPE.我错过了什么?
- 闻起来像过度工程我.让JVM抛出一个NPE,不要用这个垃圾混乱你的代码.
- 最好在您的示例中抛出IllegalArgumentException("e == null"),因为它清楚地表明它是程序员预期的异常(以及实际上允许维护者识别问题的足够信息).应该为JVM保留NullPointerExceptions,因为它清楚地表明这是无意的(并且通常发生在难以识别的地方)
- 这是Google Guava的一部分.
- 我喜欢它并打开大多数方法和构造函数,并对参数进行显式检查; 如果有错误,方法总是在前几行失败,我知道有问题的引用而没有找到类似`getThing().getItsThing().getOtherThing().wowEncapsulationIsBroken().setLol("hi");`
- Java 1.7添加了`java.util.Objects.requireNonNull`方法(以及其他方法).这是一个如此常见的习语,我很惊讶这花了很长时间!
有时,您有一些方法可以对其参数进行操作,从而定义对称操作:
a.f(b); <-> b.f(a);
如果你知道b永远不能为null,你可以只交换它.它对equals最有用:而不是foo.equals("bar");
更好"bar".equals(foo);
.
- @Supericy基本上是的,但是`equals`(或者其他任何方法)无论如何都必须检查`null`.或明确说明它没有.
- 但是你必须假设`equals`(可以是任何方法)将正确处理null.实际上,所有这一切都是将责任转嫁给其他人(或其他方法).
而不是Null对象模式 - 它有其用途 - 你可能会考虑null对象是一个bug的情况.
抛出异常时,检查堆栈跟踪并完成错误.
- 问题是通常你松散上下文,因为NullPointerException并不表示WHICH变量为null,并且你可能在行上有几个"." - 操作.使用"if(foo == null)抛出新的RuntimeException("foo == null")"允许您明确说明什么是错误的,为您的堆栈跟踪提供更多的价值给那些必须修复它的人.
- 我有一位教授反对方法调用链.他的理论是你应该警惕超过2种方法的调用链.我不知道这是否是一个硬规则,但它确实消除了NPE堆栈跟踪的大部分问题.
Null不是'问题'.它是完整建模工具集的组成部分.软件旨在模拟世界的复杂性,而null承担其负担.Null表示 Java等中的"无数据"或"未知".因此,为这些目的使用null是合适的.我不喜欢'Null对象'模式; 我认为它会提升 ' 谁将保护监护人 '的问题.
如果你问我女朋友的名字我会告诉你我没有女朋友.在Java语言中,我将返回null.另一种方法是抛出有意义的异常来指示一些不能(或不希望)在那里解决的问题并将其委托给堆栈中更高的位置来重试或向用户报告数据访问错误.
-
public Photo getPhotoOfThePerson(Person person) { if (person == null) return null; // Grabbing some resources or intensive calculation // using person object anyhow. }
上一步导致正常的逻辑流程,以便从我的照片库中找不到不存在的女朋友的照片.
getPhotoOfThePerson(me.getGirlfriend())
它适合新的Java API(期待)
getPhotoByName(me.getGirlfriend()?.getName())
虽然对于某些人来说,找不到存储在数据库中的照片是"正常的业务流程",但我过去常常使用下面的配对来处理其他一些情况
public static MyEnum parseMyEnum(String value); // throws IllegalArgumentException public static MyEnum parseMyEnumOrNull(String value);
并且不要厌恶键入
<alt> + <shift> + <j>
(在Eclipse中生成javadoc)并为您的公共API编写三个额外的单词.对于那些不阅读文档的人来说,这将是绰绰有余的./** * @return photo or null */
要么
/** * @return photo, never null */
-
public Photo getGirlfriendPhoto() { try { return appContext.getPhotoDataSource().getPhotoByName(me.getGirlfriend().getName()); } catch (NullPointerException e) { return null; } }
可能会出现问题:
问:如果
getPhotoDataSource()
返回null会怎么样?
答:这取决于业务逻辑.如果我找不到相册,我会告诉你没有照片.如果未初始化appContext怎么办?这种方法的业务逻辑就是这样.如果相同的逻辑应该更严格,那么抛出异常它就是业务逻辑的一部分,应该使用null的显式检查(情况3).在新的Java空安全的API更适合的位置,指定选择性意味着什么,什么不意味着被初始化是快速失败的编程错误的情况下.问:可以执行冗余代码并且可以获取不必要的资源.
答:如果getPhotoByName()
尝试打开数据库连接,最后创建PreparedStatement
并使用人名作为SQL参数,则可能会发生这种情况.针对未知问题的方法给出了一个未知的答案(案例1).在获取资源之前,该方法应检查参数并在需要时返回"未知"结果.问:由于尝试关闭开放,这种方法具有性能损失.
答:软件应该易于理解和修改.只有在此之后,才能考虑性能,并且只在需要时才考虑!并在需要的地方!(来源)和许多其他人).PS.这种方法使用起来也是合理的,因为"常规"代码原则的单独错误处理代码在某些地方使用是合理的.考虑下一个例子:
public SomeValue calculateSomeValueUsingSophisticatedLogic(Predicate predicate) { try { Result1 result1 = performSomeCalculation(predicate); Result2 result2 = performSomeOtherCalculation(result1.getSomeProperty()); Result3 result3 = performThirdCalculation(result2.getSomeProperty()); Result4 result4 = performLastCalculation(result3.getSomeProperty()); return result4.getSomeProperty(); } catch (NullPointerException e) { return null; } } public SomeValue calculateSomeValueUsingSophisticatedLogic(Predicate predicate) { SomeValue result = null; if (predicate != null) { Result1 result1 = performSomeCalculation(predicate); if (result1 != null && result1.getSomeProperty() != null) { Result2 result2 = performSomeOtherCalculation(result1.getSomeProperty()); if (result2 != null && result2.getSomeProperty() != null) { Result3 result3 = performThirdCalculation(result2.getSomeProperty()); if (result3 != null && result3.getSomeProperty() != null) { Result4 result4 = performLastCalculation(result3.getSomeProperty()); if (result4 != null) { result = result4.getSomeProperty(); } } } } } return result; }
PPS.对于那些快速下载(而不是那么快阅读文档)的人,我想说我的生活中从未发现空指针异常(NPE).但这种可能性是由Java创建者故意设计的,因为NPE是其子类
Exception
.我们在Java历史上有一个先例,当时ThreadDeath
它Error
不是因为它实际上是一个应用程序错误,而仅仅是因为它不打算被抓住!多少NPE适合成为一个Error
比ThreadDeath
!但事实并非如此. -
public void updatePersonPhoneNumber(Long personId, String phoneNumber) { if (personId == null) return; DataSource dataSource = appContext.getStuffDataSource(); Person person = dataSource.getPersonById(personId); if (person != null) { person.setPhoneNumber(phoneNumber); dataSource.updatePerson(person); } else { Person = new Person(personId); person.setPhoneNumber(phoneNumber); dataSource.insertPerson(person); } }
和
public void updatePersonPhoneNumber(Long personId, String phoneNumber) { if (personId == null) return; DataSource dataSource = appContext.getStuffDataSource(); Person person = dataSource.getPersonById(personId); if (person == null) throw new SomeReasonableUserException("What are you thinking about ???"); person.setPhoneNumber(phoneNumber); dataSource.updatePerson(person); }
如果未初始化appContext或dataSource,则未处理的运行时NullPointerException将终止当前线程,并将由Thread.defaultUncaughtExceptionHandler处理(您可以定义和使用您喜欢的记录器或其他通知机制).如果未设置,ThreadGroup#uncaughtException将打印stacktrace到系统错误.应该监视应用程序错误日志并为每个未处理的异常打开Jira问题,这实际上是应用程序错误.程序员应该在初始化的东西中修复bug.
- 如果我有声誉,我会投票.不仅null不是必需的,它是类型系统中的一个漏洞.将树分配给列表是一种类型错误,因为树不是List类型的值; 通过相同的逻辑,分配null应该是一个类型错误,因为null不是Object类型的值,或者任何有用的类型.[甚至发明了无效的人](http://en.wikipedia.org/wiki/Tony_Hoare)认为这是他"十亿美元的错误"."值可能是T或类型值的值"的概念是它自己的类型,应该这样表示(例如Maybe <T>或Optional <T>).
- 捕获`NullPointerException`并返回`null`是非常糟糕的调试.无论如何你最后都会使用NPE,而且很难弄清楚原来是什么.
- @MykhayloAdamovych:`Maybe <T>`或`Optional <T>`的好处不在于你的`T`可能为null,但在它应该永远不为null的情况下.如果你有一个明确意味着"这个值*可能*为空 - 请谨慎使用"的类型,并且你一直使用并返回这样的类型,那么只要你在代码中看到一个普通的旧`T`,你就可以假设它永远不会为空.(当然,如果编译器可以强制执行,这会更有用.)
- 从"Maybe <T>或Optional <T>"开始,你仍然需要编写像`if(maybeNull.hasValue()){...}`这样的代码,那么与`if(maybeNull!= null)的区别是什么? {...}`?
Java 7有一个新的java.util.Objects
实用程序类,其中有一个requireNonNull()
方法.所有这一切都是抛出一个NullPointerException
如果它的参数为null,但它会稍微清理一下代码.例:
Objects.requireNonNull(someObject);
someObject.doCalc();
该方法对于在构造函数中的赋值之前进行检查非常有用,其中每次使用它都可以保存三行代码:
Parent(Child child) {
if (child == null) {
throw new NullPointerException("child");
}
this.child = child;
}
变
Parent(Child child) {
this.child = Objects.requireNonNull(child, "child");
}
- 实际上,你的例子构成了代码膨胀:第一行是多余的,因为NPE将被抛入第二行.;-)
- 这不只是代码臃肿.最好通过一种导致副作用或使用时间/空间的方法检查前方.
最终,完全解决此问题的唯一方法是使用不同的编程语言:
- 在Objective-C中,你可以相当于调用一个方法
nil
,绝对不会发生任何事情.这使得大多数空检查变得不必要,但它可以使错误更难以诊断. - 在Nice是一种Java派生语言,所有类型都有两个版本:潜在的null版本和非null的版本.您只能在非null类型上调用方法.通过显式检查null,可以将可能为空的类型转换为非null类型.这使得更容易知道哪些空检查是必要的,哪些不是.
- 我不熟悉Nice,但是Kotlin实现了相同的想法,在语言的类型系统中构建了nullable和not-null类型.比Optionals或null Object模式更简洁.
Java中常见的"问题"确实如此.
首先,我对此的看法:
我认为在传递NULL时"吃"某些东西是不好的,其中NULL不是有效值.如果您没有以某种错误退出该方法,那么这意味着您的方法没有出错,这是不正确的.然后你可能在这种情况下返回null,并且在接收方法中你再次检查null,它永远不会结束,你最终得到"if!= null"等.
因此,IMHO,null必须是一个严重的错误,它会阻止进一步执行(即null不是有效值).
我解决这个问题的方法是这样的:
首先,我遵循这个惯例:
- 所有公共方法/ API总是检查其参数是否为null
- 所有私有方法都不检查null,因为它们是受控方法(如果上面没有处理,只需使用nullpointer异常)
- 唯一不检查null的其他方法是实用程序方法.它们是公开的,但是如果你出于某种原因打电话给它们,你就知道你传递了什么参数.这就像试图在没有提供水的情况下在水壶中煮水...
最后,在代码中,public方法的第一行如下:
ValidationUtils.getNullValidator().addParam(plans, "plans").addParam(persons, "persons").validate();
请注意,addParam()返回self,因此您可以添加更多参数进行检查.
如果任何参数为null,validate()
则抛出方法将检查ValidationException
(选中或取消选中更多是设计/品味问题,但我ValidationException
的选中).
void validate() throws ValidationException;
例如,如果"plans"为null,则该消息将包含以下文本:
" 参数[plans]遇到非法参数值null "
正如您所看到的,用户消息需要addParam()方法(字符串)中的第二个值,因为您无法轻松检测传入的变量名称,即使使用反射(无论如何都不是此帖子的主题......).
是的,我们知道超出这一行我们将不再遇到空值,所以我们只是安全地调用这些对象的方法.
这样,代码就干净,易于维护和读取.
- 绝对。仅仅引发错误和崩溃的应用程序具有更高的质量,因为毫无疑问,当它们不工作时。吞下错误的应用程序最多只能正常降级,但通常无法以难以察觉且无法修复的方式运行。当发现问题时,它们将很难调试。
问这个问题指出你可能对错误处理策略感兴趣.您的团队的架构师应该决定如何处理错误.做这件事有很多种方法:
-
- 检查错误情况并适当处理它们
当然也要看看面向方面编程 - 他们有简洁的方法插入if( o == null ) handleNull()
你的字节码.
除了使用之外,assert
您还可以使用以下内容:
if (someobject == null) {
// Handle null here then move on.
}
这略好于:
if (someobject != null) {
.....
.....
.....
}
- @Mudu作为一般规则,我更喜欢if语句中的表达式是一个更"积极"的语句,而不是"否定"语句.所以,如果我看到`if(!something){x(); } else {y(); 我倾向于把它重构为`if(something){y(); } else {x(); }(虽然有人可能会说`!= null`是更积极的选择......).但更重要的是,代码的重要部分并没有包含在`{}`中,并且对于大多数方法而言,你的缩进程度要低一级.我不知道这是不是fastcodejava的推理,但那将是我的.
- 嗯,为什么?请不要觉得任何防御,我只是想了解更多关于Java 🙂
- 这也是我倾向于做的事情..在我看来保持代码干净。
只是不要使用null.不要允许它.
在我的类中,大多数字段和局部变量都具有非空的默认值,并且我在代码中的任何地方添加了契约语句(always-on asserts)以确保执行它(因为它更简洁,更具表现力而不是让它作为NPE出现,然后必须解决行号等).
一旦我采用了这种做法,我注意到问题似乎已经解决了.你会在开发过程中更早地发现事情,并意识到你有一个弱点......更重要的是......它有助于封装不同模块的问题,不同的模块可以互相"信任",不再乱扔垃圾代码与if = null else
构造!
这是防御性编程,从长远来看会产生更清晰的代码.始终清理数据,例如通过强制执行严格的标准,问题就会消失.
class C {
private final MyType mustBeSet;
public C(MyType mything) {
mustBeSet=Contract.notNull(mything);
}
private String name = "<unknown>";
public void setName(String s) {
name = Contract.notNull(s);
}
}
class Contract {
public static <T> T notNull(T t) { if (t == null) { throw new ContractException("argument must be non-null"); return t; }
}
合同就像迷你单元测试一样,即使在生产中也一直在运行,当事情失败时,你知道为什么,而不是随机的NPE,你必须以某种方式弄清楚.
- 这种方法的问题在于,如果从未设置name,则它具有值"<unknown>",其行为类似于设置值.现在让我们说我需要检查名称是否从未设置(未知),我必须对特殊值"<unknown>"进行字符串比较.
Guava是Google非常有用的核心库,它有一个很好的有用的API来避免空值.我发现UsingAndAvoidingNullExplained非常有帮助.
如维基中所述:
用法:
Optional<Integer> possible = Optional.of(5);
possible.isPresent(); // returns true
possible.get(); // returns 5
这对每个Java开发人员来说都是一个非常普遍的问题 因此,Java 8中有官方支持来解决这些问题,而不会出现混乱的代码.
Java 8已经推出java.util.Optional<T>
.它是一个容器,可能包含也可能不包含非null值.Java 8提供了一种更安全的方法来处理在某些情况下其值可能为null的对象.它的灵感来自Haskell和Scala的想法.
简而言之,Optional类包括明确处理存在或不存在值的情况的方法.但是,与null引用相比的优点是Optional <T>类强制您在值不存在时考虑这种情况.因此,您可以防止意外的空指针异常.
在上面的示例中,我们有一个家庭服务工厂,它返回家中可用的多个设备的句柄.但是这些服务可能有用,也可能没有用; 这意味着它可能导致NullPointerException.不要if
在使用任何服务之前添加空条件,而是将其包装到Optional <Service>中.
包装选项<T>
让我们考虑一种从工厂获取服务引用的方法.而不是返回服务引用,请使用Optional包装它.它允许API用户知道返回的服务可能或可能不可用/可用,防御性地使用
public Optional<Service> getRefrigertorControl() {
Service s = new RefrigeratorService();
//...
return Optional.ofNullable(s);
}
如您所见,Optional.ofNullable()
提供了一种简单的方法来获取引用.还有另一种方法可以获得Optional的引用,或者Optional.empty()
&Optional.of()
.一个用于返回空对象而不是重新调整null,另一个用于包装不可为空的对象.
所以它如何帮助避免空检查?
一旦包装了引用对象,Optional就会提供许多有用的方法来在没有NPE的情况下调用包装引用上的方法.
Optional ref = homeServices.getRefrigertorControl();
ref.ifPresent(HomeServices::switchItOn);
Optional.ifPresent如果它是非null值,则使用引用调用给定的Consumer.否则,它什么都不做.
@FunctionalInterface
public interface Consumer<T>
表示接受单个输入参数但不返回结果的操作.与大多数其他功能接口不同,消费者需要通过副作用进行操作.它非常干净,易于理解.在上面的代码示例中,HomeService.switchOn(Service)
如果Optional保留引用为非null ,则调用gets.
我们经常使用三元运算符来检查空状态并返回替代值或默认值.Optional提供了另一种处理相同条件的方法,而不检查null.如果Optional具有空值,则Optional.orElse(defaultObj)返回defaultObj.我们在示例代码中使用它:
public static Optional<HomeServices> get() {
service = Optional.of(service.orElse(new HomeServices()));
return service;
}
现在HomeServices.get()做了同样的事情,但是以更好的方式.它检查服务是否已初始化.如果是,则返回相同或创建新的新服务.可选<T> .orElse(T)有助于返回默认值.
最后,这是我们的NPE以及无空检查代码:
import java.util.Optional;
public class HomeServices {
private static final int NOW = 0;
private static Optional<HomeServices> service;
public static Optional<HomeServices> get() {
service = Optional.of(service.orElse(new HomeServices()));
return service;
}
public Optional<Service> getRefrigertorControl() {
Service s = new RefrigeratorService();
//...
return Optional.ofNullable(s);
}
public static void main(String[] args) {
/* Get Home Services handle */
Optional<HomeServices> homeServices = HomeServices.get();
if(homeServices != null) {
Optional<Service> refrigertorControl = homeServices.get().getRefrigertorControl();
refrigertorControl.ifPresent(HomeServices::switchItOn);
}
}
public static void switchItOn(Service s){
//...
}
}
完整的帖子是NPE以及Null免检代码......真的吗?.
我喜欢Nat Pryce的文章.以下是链接:
- 用多态调度避免空值
- 用"告诉,不要问"风格避免空虚
在文章中还有一个指向Java Maybe Type的Git存储库的链接,我发现它很有趣,但我不认为它可以减少检查代码膨胀.在对互联网进行一些研究之后,我认为!=空代码膨胀主要是通过精心设计来减少的.
我试过了,NullObjectPattern
但对我来说并不总是最好的方式.有时候"不采取行动"是不恰当的.
NullPointerException
是一个运行时异常,这意味着它是开发人员的错误,并且有足够的经验它会告诉您确切的错误位置.
现在回答:
尽量使所有属性及其访问者尽可能保密,或者避免将它们暴露给客户端.当然,您可以在构造函数中包含参数值,但是通过减小范围,您不会让客户端类传递无效值.如果需要修改值,可以随时创建新值object
.您只检查构造函数中的值一次,在其余的方法中几乎可以确定值不为null.
当然,经验是理解和应用此建议的更好方式.
字节!
Java 8或更高版本的最佳替代方法可能是使用Optional
该类.
Optional stringToUse = Optional.of("optional is there");
stringToUse.ifPresent(System.out::println);
对于可能为空的值的长链,这尤其方便.例:
Optional<Integer> i = Optional.ofNullable(wsObject.getFoo())
.map(f -> f.getBar())
.map(b -> b.getBaz())
.map(b -> b.getInt());
有关如何在null上抛出异常的示例:
Optional optionalCarNull = Optional.ofNullable(someNull);
optionalCarNull.orElseThrow(IllegalStateException::new);
Java 7引入了Objects.requireNonNull
一种方法,当检查某些内容是否为非null时,该方法很方便.例:
String lowerVal = Objects.requireNonNull(someVar, "input cannot be null or empty").toLowerCase();
我可以更一般地回答它!
当方法以我们不期望的方式获取参数时,我们通常会遇到这个问题(错误的方法调用是程序员的错误).例如:您希望获得一个对象,而不是获得null.你希望得到一个至少有一个字符的字符串,而不是你得到一个空字符串......
所以两者之间没有区别:
if(object == null){
//you called my method badly!
}
要么
if(str.length() == 0){
//you called my method badly again!
}
在我们执行任何其他功能之前,他们都希望确保我们收到有效参数.
正如其他一些答案中所提到的,为了避免上述问题,您可以按照合同模式设计.请参阅http://en.wikipedia.org/wiki/Design_by_contract.
要在java中实现此模式,您可以使用核心java注释(如javax.annotation.NotNull)或使用更复杂的库(如Hibernate Validator).
只是一个样本:
getCustomerAccounts(@NotEmpty String customerId,@Size(min = 1) String accountType)
现在,您可以安全地开发方法的核心功能,而无需检查输入参数,它们可以保护您的方法免受意外参数的影响.
您可以更进一步,确保只能在您的应用程序中创建有效的pojos.(来自hibernate验证器站点的示例)
public class Car {
@NotNull
private String manufacturer;
@NotNull
@Size(min = 2, max = 14)
private String licensePlate;
@Min(2)
private int seatCount;
// ...
}
我高度忽略了建议在每种情况下使用null对象的答案.这种模式可能会破坏合同并将问题深入和深入地埋没而不是解决它们,没有提到使用不当会产生另一堆样板代码,需要将来维护.
实际上,如果从方法返回的内容可以为null并且调用代码必须对此做出决定,则应该有一个确保状态的早期调用.
还要记住,如果毫无使用,那么空对象模式将是内存饥饿.为此 - 应该在所有者之间共享NullObject的实例,而不是每个实例的unigue实例.
此外,我不建议使用此模式,其中类型是基本类型表示 - 如数学实体,不是标量:向量,矩阵,复数和POD(普通旧数据)对象,它们用于保持状态以Java内置类型的形式.在后一种情况下,您最终会调用具有任意结果的getter方法.例如,NullPerson.getName()方法应返回什么?
值得考虑这些案例以避免荒谬的结果.
- 永远不要将变量初始化为null.
- 如果(1)不可能,则将所有集合和数组初始化为空集合/数组.
在您自己的代码中执行此操作,您可以避免!= null检查.
大多数情况下,null检查似乎保护集合或数组的循环,因此只需将它们初始化为空,就不需要任何空检查.
// Bad
ArrayList<String> lemmings;
String[] names;
void checkLemmings() {
if (lemmings != null) for(lemming: lemmings) {
// do something
}
}
// Good
ArrayList<String> lemmings = new ArrayList<String>();
String[] names = {};
void checkLemmings() {
for(lemming: lemmings) {
// do something
}
}
这有一个很小的开销,但是对于更干净的代码和更少的NullPointerExceptions来说它是值得的.
- +1我同意.你永远不应该返回半个初始化对象.Jaxb相关的代码和bean代码对此是notiroius.这是不好的做法.应初始化所有集合,并且所有对象应存在(理想情况下)无空引用.考虑一个包含集合的对象.检查对象不为null,该集合不为null并且该集合不包含null对象是不合理和愚蠢的.
这是大多数开发人员最常见的错误.
我们有很多方法可以解决这个问题.
方法1:
org.apache.commons.lang.Validate //using apache framework
notNull(Object object,String message)
方法2:
if(someObject!=null){ // simply checking against null
}
方法3:
@isNull @Nullable // using annotation based validation
方法4:
// by writing static method and calling it across whereever we needed to check the validation
static <T> T isNull(someObject e){
if(e == null){
throw new NullPointerException();
}
return e;
}
public static <T> T ifNull(T toCheck, T ifNull) {
if (toCheck == null) {
return ifNull;
}
return toCheck;
}
- 这个方法有什么问题,我认为@tltester只想提供一个默认值,如果它是null,这是有意义的.
Java 8在java.util包中引入了一个新类Optional.
Java 8的优点可选:
1.)不需要空检查.
2.)在运行时不再有NullPointerException.
3.)我们可以开发干净整洁的API.
可选 - 容器对象,可能包含也可能不包含非null值.如果存在值,则isPresent()将返回true,get()将返回该值.
有关详细信息,请参阅此处oracle docs: -
https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html
您可以使用FindBugs.他们还有一个Eclipse插件,可以帮助您找到重复的空值检查(除其他外),但请记住,有时您应该选择防御性编程.还有Java合同可能会有所帮助.
总而言之,要避免陈述
if (object != null) {
....
}
-
Objects.isNull(对象)
Objects.nonNull(对象)
Objects.requireNonNull(对象)
Objects.equals(object1,object2)
object.ifPresent(obj -> ...);
java 8
object.ifPresentOrElse(obj -> ..., () -> ...);
java 9
-
Preconditions.checkNotNull(对象);
public static boolean isEmpty(Collection coll)// apache StringUtils
public static boolean isEmpty(Map map)// apache MapUtils
public static boolean isNullOrEmpty(@Nullable String string)// Guava Strings
- 当你需要在null时使用apache commons lang分配默认值
我按照以下指南来避免空检查.
-
例如,假设application(
A.java
)中的一个类正在维护一个类似的集合HashMap
.不要public
在A.java中提供getter方法并允许B.java
直接添加元素Map
.而是提供一个APIA.java
,它将一个元素添加到集合中.// Avoid a.getMap().put(key,value) //recommended public void addElement(Object key, Object value){ // Have null checks for both key and value here : single place map.put(key,value); }
由于Java 7
班级java.util.Objects
存在.
但是,因为Java 8
,你可以使用Objects.isNull(var)
与Objects.nonNull(var)
方法Objects
课上做的空指针检查.
例如,
String var1 = null;
Date var2 = null;
Long var3 = null;
if(Objects.isNull(var1) && Objects.isNull(var2) && Objects.isNull(var3))
System.out.println("All Null");
else if (Objects.nonNull(var1) && Objects.nonNull(var2) && Objects.nonNull(var3))
System.out.println("All Not Null");
如果您使用的是 java8 或更高版本,请选择isNull(yourObject)
from java.util.Objects
。
例子:-
String myObject = null;
Objects.isNull(myObject); //will return true
用法:下面的代码返回一个非空值(如果名称不为空,则将返回该值,否则将返回默认值)。
final String name = "Jobin";
String nonNullValue = Optional.ofNullable(name).filter(Objects::nonNull).orElse("DefaultName");
NullPointerException
通过仅仅关注问题的大部分其他答案,你可以避免最多的避免,我只想添加一些已经引入的方法 Java 9
来优雅地处理这个场景,并且还展示了一些较旧的方法也可以使用,从而减少你的努力.
-
如果提供的引用为null,则返回true,否则返回false.
自Java 1.8以来
-
如果提供的引用为非null,则返回true,否则返回false.
自Java 1.8以来
-
如果它是非null,则返回第一个参数,否则返回非null的第二个参数.
自Java 9起
-
如果它是非null,则返回第一个参数,否则返回supplier.get()的非null值.
自Java 9起
-
检查指定的对象引用是否为null,否则抛出自定义的NullPointerException.
自Java 1.8以来
关于上述功能的更多细节可以在这里找到.
Java 8 在java.util
包中引入了一个新类 Optional 。它用于表示一个值存在或不存在。这种新构造的主要优点是不再有太多的空检查和NullPointerException
. 它避免了任何运行时NullPointerExceptions
并支持我们开发干净整洁的 Java API 或应用程序。像Collections
and一样arrays
,它也是一个最多容纳一个值的容器。
以下是您可以关注的一些有用链接
https://www.mkyong.com/java8/java-8-optional-in-depth/
https://dzone.com/articles/java-8-optional-avoid-null-and
还有一个选择:
以下简单的函数有助于隐藏空检查(我不知道为什么,但我没有发现它是同一个公共库的一部分):
public static <T> boolean isNull(T argument) {
return (argument == null);
}
你现在可以写了
if (!isNull(someobject)) {
someobject.doCalc();
}
这是IMO更好的表达方式!= null
.
- 我没有看到这比"someobject!= null"更简洁.
- 如果你经常要否定返回值,那么简单地写一个函数`isNotNull`不是更好吗?这不是更清楚地表明你的意图吗?
无论您传递数组还是Vector,只需将其初始化为空数,而不是null. - 这样你可以避免大量的空检查,一切都很好:)
public class NonNullThing {
Vector vectorField = new Vector();
int[] arrayField = new int[0];
public NonNullThing() {
// etc
}
}
在这种情况下,我发现Guava Preconditions非常有用.我不喜欢将空值留给空指针异常,因为了解NPE的唯一方法是找到行号.生产版本和开发版本中的行号可以不同.
使用Guava Preconditions,我可以检查空参数并在一行中定义有意义的异常消息.
例如,
Preconditions.checkNotNull(paramVal, "Method foo received null paramVal");
在 Java 8 中,如果T
本地变量/字段/方法参数/方法返回类型从未分配过null
(并且不检查null
)Optional<T>
,则可以使用类型作为 ,或者如果可以是类型,则使用类型null
。然后使用map
处理T ->
方法flatMap
和处理方法T -> Optional<R>
:
class SomeService {
@Inject
private CompanyDao companyDao;
// return Optional<String>
public Optional<String> selectCeoCityByCompanyId0(int companyId) {
return companyDao.selectById(companyId)
.map(Company::getCeo)
.flatMap(Person::getHomeAddress)
.flatMap(Address::getCity);
}
// return String + default value
public String selectCeoCityByCompanyId1(int companyId) {
return companyDao.selectById(companyId)
.map(Company::getCeo)
.flatMap(Person::getHomeAddress)
.flatMap(Address::getCity)
.orElse("UNKNOWN");
}
// return String + exception
public String selectCeoCityByCompanyId2(int companyId) throws NoSuchElementException {
return companyDao.selectById(companyId)
.map(Company::getCeo)
.flatMap(Person::getHomeAddress)
.flatMap(Address::getCity)
.orElseThrow(NoSuchElementException::new);
}
}
interface CompanyDao {
// real situation: no company for such id -> use Optional<Company>
Optional<Company> selectById(int id);
}
class Company {
// company always has ceo -> use Person
Person ceo;
public Person getCeo() {return ceo;}
}
class Person {
// person always has name -> use String
String firstName;
// person can be without address -> use Optional<Address>
Optional<Address> homeAddress = Optional.empty();
public String getFirstName() {return firstName;}
public Optional<Address> getHomeAddress() {return homeAddress;}
}
class Address {
// address always contains country -> use String
String country;
// city field is optional -> use Optional<String>
Optional<String> city = Optional.empty();
String getCountry() {return country;}
Optional<String> getCity() {return city;}
}