避免 != null 语句
-
06-07-2019 - |
题
我用 object != null
有很多需要避免的 NullPointerException
.
有没有好的替代方案?
例如:
if (someobject != null) {
someobject.doCalc();
}
这避免了 NullPointerException
, ,当未知物体是否是 null
或不。
请注意,接受的答案可能已过时,请参阅 https://stackoverflow.com/a/2386013/12943 以获得更新的方法。
解决方案
对我来说,这听起来像是初级到中级开发人员在某些时候往往会面临的一个相当常见的问题:他们要么不知道,要么不信任他们正在参与的合约,并防御性地过度检查空值。此外,在编写自己的代码时,他们倾向于依赖返回空值来指示某些内容,因此要求调用者检查空值。
换句话说,有两种情况会出现空检查:
如果无效,则表示合同条款中的有效答复;和
这不是一个有效的响应。
(2)很容易。要么使用 assert
声明(断言)或允许失败(例如, 空指针异常)。断言是 1.4 中添加的一项未被充分利用的 Java 功能。语法是:
assert <condition>
或者
assert <condition> : <object>
在哪里 <condition>
是一个布尔表达式并且 <object>
是一个对象,其 toString()
方法的输出将包含在错误中。
一个 assert
声明抛出一个 Error
(AssertionError
) 如果条件不成立。默认情况下,Java 会忽略断言。您可以通过传递选项来启用断言 -ea
到 JVM。您可以启用和禁用各个类和包的断言。这意味着您可以在开发和测试时使用断言验证代码,并在生产环境中禁用它们,尽管我的测试表明断言对性能几乎没有影响。
在这种情况下不使用断言是可以的,因为代码只会失败,如果使用断言就会发生这种情况。唯一的区别是,使用断言,它可能会以更有意义的方式更快地发生,并且可能会提供额外的信息,这可能会帮助您弄清楚如果您没有预料到它会发生的原因。
(1) 有点难。如果您无法控制所调用的代码,那么您就会陷入困境。如果 null 是有效响应,则必须检查它。
但是,如果您确实控制了代码(通常是这种情况),那么情况就不同了。避免使用空值作为响应。使用返回集合的方法,这很容易:几乎总是返回空集合(或数组)而不是空值。
对于非收藏可能会更困难。以此为例:如果您有这些接口:
public interface Action {
void doSomething();
}
public interface Parser {
Action findAction(String userInput);
}
解析器获取原始用户输入并找到要做的事情,也许如果您正在为某些事情实现命令行界面。现在,您可以制定合同,如果没有适当的操作,则返回 null。这导致了您所说的空检查。
另一种解决方案是永远不返回 null,而是使用 空对象模式:
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 方法抛出异常比调用方法因简单的 NullPointerException 而没有任何解释而崩溃要好得多。
try {
ParserFactory.getParser().findAction(someInput).doSomething();
} catch(ActionNotFoundException anfe) {
userConsole.err(anfe.getMessage());
}
或者,如果您认为 try/catch 机制太丑陋,那么您的默认操作应该向用户提供反馈,而不是“什么也不做”。
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);
}
}
}
其他提示
如果您使用(或计划使用)Java IDE,例如 JetBrains IntelliJ IDEA, 蚀 或者 网豆 或者像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注释.
如果不允许空值
如果您的方法是从外部调用的,请从以下内容开始:
public void method(Object object) {
if (object == null) {
throw new IllegalArgumentException("...");
}
然后,在该方法的其余部分中,您会知道 object
不为空。
如果它是内部方法(不是 API 的一部分),只需记录它不能为 null,仅此而已。
例子:
public String getFirst3Chars(String text) {
return text.subString(0, 3);
}
但是,如果您的方法只是传递值,而下一个方法传递它,等等。这可能会出现问题。在这种情况下,您可能需要检查上面的参数。
如果允许为空
这确实取决于。如果发现我经常做这样的事情:
if (object == null) {
// something
} else {
// something else
}
所以我分支并做了两件完全不同的事情。没有丑陋的代码片段,因为我确实需要根据数据做两件不同的事情。例如,我应该处理输入,还是应该计算一个好的默认值?
其实我很少用这个成语”if (object != null && ...
".
如果您展示通常使用该习语的示例,那么向您提供示例可能会更容易。
哇,当我们有 57 种不同的方式来推荐时,我几乎不想添加另一个答案 NullObject pattern
, ,但我认为对这个问题感兴趣的一些人可能想知道Java 7有一个提案可以添加 “空安全处理”——if-not-equal-null 逻辑的简化语法。
Alex Miller 给出的例子如下:
public String getPostcode(Person person) {
return person?.getAddress()?.getPostcode();
}
这 ?.
表示仅取消引用左侧标识符(如果它不为空),否则将表达式的其余部分计算为 null
. 。有些人,比如 Java Posse 成员 Dick Wall 和 Devoxx 的选民 我真的很喜欢这个提议,但也有人反对,因为它实际上会鼓励更多地使用 null
作为哨兵值。
更新: 一个 官方提案 Java 7 中的空安全运算符已提交 项目币。 语法与上面的示例略有不同,但概念相同。
更新: null-safe 运算符提案没有进入 Project Coin。因此,您不会在 Java 7 中看到这种语法。
如果不允许使用未定义的值:
您可以将 IDE 配置为警告您潜在的空取消引用。例如。在 Eclipse 中,请参阅 首选项 > Java > 编译器 > 错误/警告/空分析.
如果允许未定义的值:
如果您想定义一个新的 API,其中未定义的值有意义, , 使用 期权模式 (可能对函数式语言很熟悉)。它具有以下优点:
- API 中明确说明了输入或输出是否存在。
- 编译器强制您处理“未定义”的情况。
- 选项是一个单子, ,因此不需要详细的 null 检查,只需使用 map/foreach/getOrElse 或类似的组合器即可安全地使用该值 (例子).
Java 8 有一个内置的 Optional
类(推荐);对于早期版本,有库替代品,例如 番石榴的 Optional
或者 函数式Java的 Option
. 。但与许多函数式模式一样,在 Java 中使用 Option(甚至 8 个)会产生相当多的样板文件,您可以使用不太冗长的 JVM 语言来减少这些样板文件,例如Scala 或 Xtend。
如果您必须处理可能返回 null 的 API, ,你在 Java 中不能做太多事情。Xtend 和 Groovy 有 埃尔维斯操作员 ?:
和 空安全解引用运算符 ?.
, ,但请注意,如果存在 null 引用,则返回 null,因此它只是“推迟”对 null 的正确处理。
只针对这种情况——
在调用 equals 方法之前不检查变量是否为 null(下面是字符串比较示例):
if ( foo.equals("bar") ) {
// ...
}
将导致 NullPointerException
如果 foo
不存在。
如果您比较您的情况,您可以避免这种情况 String
像这样:
if ( "bar".equals(foo) ) {
// ...
}
Java 8 带来了新的 java.util.Optional
可以说解决了一些问题的类。至少可以说,它提高了代码的可读性,并且在公共 API 的情况下,使 API 的契约对客户端开发人员来说更加清晰。
他们的工作方式是这样的:
给定类型的可选对象(Fruit
) 被创建为方法的返回类型。它可以为空或包含 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
(fruits
) 对于给定的 Fruit 实例:
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");
当然,对 null/空值的检查仍然是必要的,但至少开发人员意识到该值可能为空,并且忘记检查的风险是有限的。
在从头开始构建的 API 中使用 Optional
每当返回值可能为空时,仅当返回值不能为空时才返回普通对象 null
(约定),客户端代码可能会放弃对简单对象返回值的空检查......
当然 Optional
也可以用作方法参数,在某些情况下,这可能是比 5 或 10 个重载方法更好的指示可选参数的方法。
Optional
提供了其他方便的方法,例如 orElse
允许使用默认值,并且 ifPresent
与 lambda 表达式.
我邀请您阅读这篇文章(我撰写此答案的主要来源),其中 NullPointerException
(通常是空指针)有问题以及带来的(部分)解决方案 Optional
都得到了很好的解释: Java 可选对象.
根据您要检查的对象类型,您可能可以使用 apache commons 中的一些类,例如: 阿帕奇公共语言 和 阿帕奇公共集合
例子:
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 验证
public DOCUMENT read(String xml, ValidationEventHandler validationEventHandler) {
Validate.notNull(validationEventHandler,"ValidationHandler not Injected");
return read(new StringReader(xml), true, validationEventHandler);
}
如果您使用 Spring,Spring 的包中也有相同的功能,请参阅库(spring-2.4.6.jar)
有关如何使用 spring 中的静态 classf 的示例(org.springframework.util.Assert)
Assert.notNull(validationEventHandler,"ValidationHandler not Injected");
- 如果您认为对象不应该为 null(或者它是一个错误),请使用断言。
- 如果您的方法不接受 null 参数,请在 javadoc 中说明并使用断言。
仅当您想处理对象可能为 null 的情况时,才必须检查 object != null ...
有人建议在 Java7 中添加新的注释来帮助处理 null / notnull 参数:http://tech.puredanger.com/java7/#jsr308
我是“快速失败”代码的粉丝。问问自己 - 在参数为空的情况下,您是否正在做一些有用的事情?如果您对代码在这种情况下应该做什么没有明确的答案......IE。它首先不应该为 null,然后忽略它并允许抛出 NullPointerException。调用代码对 NPE 的理解与对 IllegalArgumentException 的理解一样,但如果抛出 NPE,开发人员会更容易调试和理解出了什么问题,而不是您的代码尝试执行其他一些意外的意外情况。逻辑 - 最终导致应用程序失败。
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();
有时,您有对其参数进行操作的方法,这些参数定义了对称操作:
a.f(b); <-> b.f(a);
如果你知道 b 永远不会为空,你可以交换它。它对于 equals 最有用:代替 foo.equals("bar");
更好做 "bar".equals(foo);
.
您可能会考虑空对象是一个错误的情况,而不是空对象模式(它有其用途)。
引发异常时,检查堆栈跟踪并解决错误。
Java 7 有一个新的 java.util.Objects
实用程序类上有一个 requireNonNull()
方法。这一切所做的就是抛出一个 NullPointerException
如果它的参数为空,但它会稍微清理代码。例子:
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");
}
空不是一个“问题”。它是一个不可分割的一部分 完全的 建模工具集。软件旨在模拟世界的复杂性,而 null 承担了它的负担。 Null 表示“无数据”或“未知” 在Java等中。因此,出于这些目的使用 null 是合适的。我不喜欢“空对象”模式;我认为它上升了'谁会保护监护人' 问题。
如果你问我女朋友叫什么名字,我会告诉你我没有女朋友。在 Java 语言中,我将返回 null。另一种方法是抛出有意义的异常来指示某些无法(或不想)立即解决的问题,并将其委托给堆栈中更高的位置以重试或向用户报告数据访问错误。
对于“未知问题”给出“未知答案”。 (在从业务角度来看这是正确的情况下,保持 null 安全)在使用之前在方法内部检查参数是否为 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 */
这是相当理论化的情况,在大多数情况下,您应该更喜欢 java null safe API(以防它再过 10 年发布),但是
NullPointerException
是一个子类Exception
. 因此它是一种形式Throwable
表明合理的应用程序可能想要捕获的条件(javadoc)!使用异常的第一个最大优点并将错误处理代码与“常规”代码分开(根据 Java 的创建者的说法)对于我来说,抓住是合适的NullPointerException
.public Photo getGirlfriendPhoto() { try { return appContext.getPhotoDataSource().getPhotoByName(me.getGirlfriend().getName()); } catch (NullPointerException e) { return null; } }
可能会出现以下问题:
问。如果什么
getPhotoDataSource()
返回空值?
A。这取决于业务逻辑。如果我找不到相册,我就不给你看照片。如果appContext没有初始化怎么办?该方法的业务逻辑可以忍受这一点。如果相同的逻辑应该更严格,那么抛出异常是业务逻辑的一部分,并且应该使用对 null 的显式检查(情况 3)。这 新的 Java Null-safe API 更适合这里,可以有选择地指定暗示和不暗示要初始化的内容 在程序员出错的情况下快速失败。问。可以执行冗余代码并获取不必要的资源。
A。如果getPhotoByName()
会尝试打开数据库连接,创建PreparedStatement
最后使用人名作为SQL参数。该方法 对于未知的问题给出未知的答案 (案例1)在这里工作。在获取资源之前,该方法应检查参数并在需要时返回“未知”结果。问。由于 try 闭包开放,这种方法会造成性能损失。
A。软件首先应该易于理解和修改。只有在此之后,人们才能考虑性能,而且只有在需要时!以及需要的地方!(来源),以及许多其他)。附言。这种方法的使用将是合理的 将错误处理代码与“常规”代码分开 原则在某些地方使用是合理的。考虑下一个例子:
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; }
聚苯硫醚。对于那些快速投反对票(而不是那么快阅读文档)的人,我想说我一生中从未捕获过空指针异常(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 (供您定义和使用您最喜欢的记录器或其他通知机制)。如果没有设置, 线程组#uncaughtException 将打印堆栈跟踪到系统错误。人们应该监视应用程序错误日志并针对每个未处理的异常(实际上是应用程序错误)打开 Jira 问题。程序员应该修复初始化内容中的某个错误。
最终,彻底解决这个问题的唯一方法是使用不同的编程语言:
- 在 Objective-C 中,您可以执行与调用方法等效的操作
nil
, ,并且绝对不会发生任何事情。这使得大多数空检查变得不必要,但它会使错误更难以诊断。 - 在 好的, ,一种源自 Java 的语言,所有类型都有两个版本:可能为空的版本和非空的版本。您只能调用非空类型的方法。通过显式检查 null ,可以将潜在 null 类型转换为非 null 类型。这使得更容易知道哪里需要空检查,哪里不需要。
确实是 Java 中常见的“问题”。
首先说一下我对此的看法:
我认为在 NULL 不是有效值的情况下传递 NULL 时“吃掉”某些东西是不好的。如果您没有因某种错误而退出该方法,那么这意味着您的方法没有出现任何问题,这是不正确的。那么在这种情况下你可能返回 null,并在接收方法中再次检查 null,它永远不会结束,最终得到“if != null”等。
因此,恕我直言,null 一定是一个严重错误,它会阻止进一步执行(即 null 不是有效值)。
我解决这个问题的方法是这样的:
首先,我遵循这个约定:
- 所有公共方法/API 始终检查其参数是否为 null
- 所有私有方法都不检查 null,因为它们是受控方法(如果上面没有处理,就让 nullpointer 异常结束)
- 唯一不检查 null 的其他方法是实用程序方法。它们是公共的,但如果您出于某种原因调用它们,您就知道传递的参数。这就像试图在不提供水的情况下将水壶中的水烧开一样......
最后,在代码中,公共方法的第一行如下所示:
ValidationUtils.getNullValidator().addParam(plans, "plans").addParam(persons, "persons").validate();
请注意,addParam() 返回 self,以便您可以添加更多参数进行检查。
方法 validate()
将抛出检查 ValidationException
如果任何参数为空(选中或未选中更多的是设计/品味问题,但我的 ValidationException
被选中)。
void validate() throws ValidationException;
例如,如果“plans”为空,则消息将包含以下文本:
"参数 [计划] 遇到非法参数值 null"
正如您所看到的,用户消息需要 addParam() 方法中的第二个值(字符串),因为即使使用反射,您也无法轻松检测传入的变量名称(无论如何不是本文的主题...)。
是的,我们知道,超出这一行,我们将不再遇到空值,因此我们只需安全地调用这些对象上的方法即可。
这样,代码就干净、易于维护和可读。
提出这个问题表明您可能对错误处理策略感兴趣。您团队的架构师应该决定如何处理错误。做这件事有很多种方法:
允许异常蔓延 - 在“主循环”或其他一些管理例程中捕获它们。
- 检查错误情况并进行适当处理
当然也应该看看面向方面的编程 - 他们有巧妙的插入方法 if( o == null ) handleNull()
到你的字节码中。
除了使用 assert
您可以使用以下内容:
if (someobject == null) {
// Handle null here then move on.
}
这比:稍微好一点:
if (someobject != null) {
.....
.....
.....
}
只是永远不要使用 null 。不允许。
在我的类中,大多数字段和局部变量都有非空默认值,并且我在代码中的任何地方添加契约语句(始终有效的断言),以确保强制执行这一点(因为它比让它更简洁,更具表现力)作为 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,你必须以某种方式找出答案。
Guava 是 Google 的一个非常有用的核心库,它有一个很好且有用的 API 来避免空值。我发现 使用和避免 Null 解释 很有帮助。
正如维基百科中所解释的:
Optional<T>
是用非零值代替可无效的t参考的一种方式。Optional 可以包含非空 T 引用 (在这种情况下,我们说引用是“存在的”),或者它可能包含 什么都没有(在这种情况下,我们说引用是“不存在的”)。它从来都不是 说“包含 null”。
用法:
Optional<Integer> possible = Optional.of(5);
possible.isPresent(); // returns true
possible.get(); // returns 5
对于每个 Java 开发人员来说,这是一个非常常见的问题。因此,Java 8 提供了官方支持来解决这些问题,而无需编写混乱的代码。
Java 8 引入了 java.util.Optional<T>
. 。它是一个可能包含也可能不包含非空值的容器。Java 8 提供了一种更安全的方法来处理在某些情况下其值可能为 null 的对象。它的灵感来自于 哈斯克尔 和 斯卡拉.
简而言之,Optional 类包含显式处理值存在或不存在的情况的方法。然而,与空引用相比的优点是,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,另一个用于分别包装不可为 null 的对象。
那么它到底如何帮助避免空检查呢?
一旦包装了引用对象,Optional 就提供了许多有用的方法来调用包装引用上的方法,而无需 NPE。
Optional ref = homeServices.getRefrigertorControl();
ref.ifPresent(HomeServices::switchItOn);
如果给定的 Consumer 是非空值,Optional.ifPresent 将使用引用调用该 Consumer。否则,它什么也不做。
@FunctionalInterface
public interface Consumer<T>
表示接受单个输入参数且不返回结果的操作。与大多数其他功能接口不同,消费者预计通过副作用进行操作。它是如此干净且易于理解。在上面的代码示例中, HomeService.switchOn(Service)
如果可选持有引用非空,则被调用。
我们经常使用三元运算符来检查 null 条件并返回替代值或默认值。Optional 提供了另一种方法来处理相同的条件而不检查 null。如果Optional 具有空值,Optional.orElse(defaultObj) 将返回defaultObj。让我们在示例代码中使用它:
public static Optional<HomeServices> get() {
service = Optional.of(service.orElse(new HomeServices()));
return service;
}
现在 HomeServices.get() 做同样的事情,但以更好的方式。它检查服务是否已经初始化。如果是,则返回相同的内容或创建一个新的新服务。Optional<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 可能类型的 Git 存储库的链接,我觉得这很有趣,但我认为仅凭它并不能减少 检查代码膨胀。在网上做了一些研究后,我认为 != 空 代码膨胀主要可以通过精心设计来减少。
我已经尝试过 NullObjectPattern
但对我来说并不总是最好的方法。有时“不采取行动”是不合适的。
NullPointerException
是一个 运行时异常 这意味着这是开发人员的错误,如果有足够的经验,它会告诉您错误到底在哪里。
现在给出答案:
尝试使所有属性及其访问器尽可能私有,或者完全避免将它们暴露给客户端。当然,您可以在构造函数中使用参数值,但是通过缩小范围,您不会让客户端类传递无效值。如果您需要修改这些值,您可以随时创建一个新的 object
. 。您仅检查构造函数中的值 一次 在其余方法中,您几乎可以确定这些值不为空。
当然,经验是理解和应用这一建议的更好方法。
字节!
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
当需要检查某些内容是否为非空时,该方法会很方便。例子:
String lowerVal = Objects.requireNonNull(someVar, "input cannot be null or empty").toLowerCase();
我可以更笼统地回答一下吗!
我们 通常 当方法以我们不期望的方式获取参数时,就会面临这个问题(错误的方法调用是程序员的错)。例如:你期望得到一个对象,但你得到的是一个空值。您希望获得一个至少包含一个字符的字符串,但您却得到一个空字符串...
所以两者之间没有区别:
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 或者使用更复杂的库,例如 休眠验证器.
只是一个示例:
getCustomerAccounts(@NotEmpty String customerId,@Size(min = 1) String accountType)
现在,您可以安全地开发方法的核心功能,而无需检查输入参数,它们可以保护您的方法免受意外参数的影响。
您可以更进一步,确保在您的应用程序中只能创建有效的 pojo。(来自 hibernate 验证器站点的示例)
public class Car {
@NotNull
private String manufacturer;
@NotNull
@Size(min = 2, max = 14)
private String licensePlate;
@Min(2)
private int seatCount;
// ...
}
我高度忽视建议在每种情况下使用空对象的答案。这种模式可能会破坏契约,将问题埋得越来越深,而不是解决问题,更不用说使用不当会创建另一堆需要将来维护的样板代码。
实际上,如果从方法返回的内容可能为空,并且调用代码必须对此做出决定,则应该有一个更早的调用来确保状态。
另请记住,如果不小心使用,空对象模式将占用内存。为此,NullObject 的实例应在所有者之间共享,而不是每个所有者的唯一实例。
另外,我不建议使用这种模式,其中类型是原始类型表示 - 就像数学实体一样,它们不是标量:向量、矩阵、复数和 POD(普通旧数据)对象,它们旨在以 Java 内置类型的形式保存状态。在后一种情况下,您最终会调用具有任意结果的 getter 方法。例如,NullPerson.getName() 方法应该返回什么?
为了避免荒谬的结果,值得考虑这种情况。
- 切勿将变量初始化为 null。
- 如果 (1) 不可能,则将所有集合和数组初始化为空集合/数组。
在您自己的代码中执行此操作,您可以避免 != 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,这是值得的。
这是大多数开发人员最常见的错误。
我们有多种方法来处理这个问题。
方法一:
org.apache.commons.lang.Validate //using apache framework
notNull(对象对象,字符串消息)
方法2:
if(someObject!=null){ // simply checking against null
}
方法三:
@isNull @Nullable // using annotation based validation
方法四:
// 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;
}