为什么使用可选的优先选择对变量进行空检查?


25

举两个代码示例:

if(optional.isPresent()) {
    //do your thing
}

if(variable != null) {
    //do your thing
}

据我所知,最明显的区别是Optional需要创建一个附加对象。

但是,许多人已经开始迅速采用Optionals。使用可选与空检查相比有什么优势?


4
您几乎永远不想使用isPresent,通常应该使用ifPresent或map
jk。

6
@jk。由于if报表是SOOOOO过去的十年里,大家的使用单子的抽象和lambda表达式现在。
user253751 '16

2
@immibis我曾经与另一个用户就此话题进行了长时间的辩论。关键是在if(x.isPresent) fails_on_null(x.get)退出类型系统时,必须保证条件和函数调用之间的距离(允许的很短)不会使代码“破脑”。在optional.ifPresent(fails_on_null)类型系统中,这为您提供了保证,您不必担心。
ziggystar'2

1
使用Optional.ifPresent(以及其他各种Java构造)在Java中的主要缺陷在于,您只能有效地修改最终变量,而不能引发检查异常。ifPresent不幸的是,这是足以避免的理由。
迈克(Mike)

Answers:


19

Optional利用类型系统来完成您原本必须做的所有工作:记住给定的引用是否可能是null。很好 让编译器处理无聊的药物工作,并保留人类的思想来进行创造性,有趣的工作,总是很聪明的。

没有Optional,代码中的每个引用都像一颗未爆炸的炸弹。访问它可能会做一些有用的事情,否则它可能会异常终止您的程序。

使用Optional和不null使用,对普通引用的每次访问都会成功,并且对Optional的每次引用都会成功,除非未设置它并且您无法检查它。这是可维护性的巨大胜利。

不幸的是,现在提供的大多数语言Optional都没有废除null,因此您只能通过制定严格的“绝对不行”政策来从中受益null。因此,Optional例如在Java中,它不如理想中的那么引人注目。


由于您的答案是最重要的,因此可能值得增加使用Lambda作为优势-对于以后阅读此书的任何人(请参阅我的答案)
William Dunne

大多数现代语言都提供非空类型,例如Kotlin,Typescript和AFAIK。
禅宗

5
IMO这可以通过@Nullable注释更好地处理。没有理由Optional仅提醒您该值可以为null
Hilikus

18

An Optional带来了更强的键入操作的能力,这些操作可能会失败,正如其他答案所涵盖的那样,但这远未Optionals带来最有趣或最有价值的东西。更有用的是延迟或避免检查故障的能力,以及轻松地组合可能会失败的许多操作的能力。

考虑一下您是否有optional示例代码中的变量,那么您必须执行另外两个步骤,每个步骤都可能会失败。如果此过程中的任何步骤失败,则您想返回一个默认值。Optionals正确使用,您将得到如下结果:

return optional.flatMap(x -> x.anotherOptionalStep())
               .flatMap(x -> x.yetAnotherOptionalStep())
               .orElse(defaultValue);

随着null我将不得不检查三次,null继续前,这增加了很多的复杂性和维护问题的代码。 Optionals将检查内置到flatMaporElse功能中。

请注意,我没有打过isPresent一次电话,使用时应将其视为代码气味Optionals。这并不一定意味着您永远不要使用isPresent,而应该仔细检查所有执行此操作的代码,以查看是否有更好的方法。否则,您是对的,与使用相比,您只会获得边际类型的安全好处null

还要注意,我并不担心将所有内容封装到一个函数中,以保护代码的其他部分免受中间结果产生的空指针的影响。.orElse(defaultValue)例如,如果让我拥有另一个函数更有意义,那么将其放置在另一个函数中的烦恼就更少了,并且根据需要在不同函数之间组合操作会容易得多。


10

它强调了将null作为有效响应的可能性,人们经常认为(正确或错误)将不会返回null。突出显示几次有效为null的次数可以省去大量无意义的null检查。

理想情况下,将变量设置为null可能会导致编译时错误,但在可选位置除外;消除运行时空指针异常。遗憾的是,显然向后兼容排除了这种情况


4

让我给你举个例子:

class UserRepository {
     User findById(long id);
}

很清楚此方法的目的是什么。但是,如果存储库不包含具有给定ID的用户,会发生什么情况?它可能引发异常,或者可能返回null?

第二个例子:

class UserRepository {
     Optional<User> findById(long id);
}

现在,如果没有给定ID的用户在这里会发生什么?正确,您将获得Optional.absent()实例,并且可以使用Optional.isPresent()对其进行检查。使用Optional的方法签名更明确地约定其合同。立即清楚地知道该方法的行为方式。

在读取客户端代码时,它也有好处:

User user = userRepository.findById(userId).get();

我已经训练好自己的眼睛,以将.get()视为立即的代码气味。这意味着客户端有意地忽略了所调用方法的协定。与不使用Optional相比,这要容易得多,因为在这种情况下,您不会立即知道被调用方法的约定(无论它允许返回null还是不允许返回),因此您需要首先进行检查。

可选与以下约定一起使用:具有可选返回类型的方法从不返回null,通常还与(较弱的)约定使用不具有可选返回类型的方法从不返回null(但最好使用@ Nonnull,@ NotNull或类似方法对其进行注释) 。


3

An Optional<T>允许您将“失败”或“无结果”作为方法的有效响应/返回值(例如,数据库查找)。使用a Optional代替使用null来指示失败/没有结果有一些优点:

  • 它清楚地表明“失败” 一种选择。您的方法的用户不必猜测是否null可能返回。
  • null至少在我看来,该值应用于表示任何内容,因为语义可能并不十分清楚。它应该用于告诉垃圾收集器可以收集先前引用的对象。
  • Optional类提供了许多很好的方法来与值有条件地工作(例如,ifPresent())或以透明的方式定义默认值(orElse())。

不幸的是,null由于Optional对象本身可能仍然存在,因此并不能完全消除对代码进行检查的需要null


4
这并不是Optional真正的目的。要报告错误,请使用异常。如果你不能做到这一点,使用含有一种或者结果或错误代码
James Youngman

我非常不同意。我认为Optional.empty()是显示失败或不存在的好方法。
LegendLength

@LegendLength,在失败的情况下,我必须强烈不同意。如果函数失败,则最好给我一个解释,不仅仅是一个空的“我失败了”
Winston Ewert

对不起,我同意,我的意思是“预期失败”,因为在搜索过程中没有找到名字为“ x”的员工。
LegendLength

3

除了其他答案外,Optionals在编写干净代码时的另一个主要优点是,可以在存在值的情况下使用Lambda表达式,如下所示:

opTr.ifPresent(tr -> transactions.put(tr.getTxid(), tr));

2

请考虑以下方法:

void dance(Person partner)

现在让我们看一些调用代码:

dance(null);

这会导致在其他地方可能难以跟踪崩溃,因为dance没有期望为空。

dance(optionalPerson)

这会导致编译错误,因为Dance期待的Person不是Optional<Person>

dance(optionalPerson.get())

如果我没有人,这将导致此行发生异常,这是我非法尝试将其转换为人的时候Optional<Person>。关键是,不同于传递空值,except跟踪到此处,而不是程序中的其他位置。

综上所述:滥用可选会更容易发现问题,滥用null会导致很难发现问题。


1
如果您在调用Optional.get()代码时写的不好而抛出异常,则几乎永远不要在没有Optional.isPresent()先检查的情况下调用Optional.get (如OP中所述),否则您可以编写optionalPersion.ifPresent(myObj::dance)
克里斯·库珀

@QmunkE,是的,只有在您知道optional不为空的情况下,才应调用Optional.get()(这是因为调用了isPresent或由于算法保证了它)。但是,我确实担心人们会盲目地检查isPresent,然后忽略缺少的值,从而创建许多无提示的故障,这将是很难跟踪的。
Winston Ewert'2

1
我喜欢可选的。但是我会说,普通的旧空指针在现实世界的代码中很少出现问题。他们几乎总是在令人讨厌的代码行附近失败。而且我认为在谈论这个主题时,人们会夸大其词。
LegendLength

@LegendLength,我的经验是,确实有95%的案例很容易被跟踪以确定NULL的来源。但是,剩余的5%极难追踪。
温斯顿·埃维尔

-1

在Objective-C中,所有对象引用都是可选的。因此,像您发布的代码很愚蠢。

if (variable != nil) {
    [variable doThing];
}

上面的代码在Objective-C中是闻所未闻的。相反,程序员只是:

[variable doThing];

如果variable包含一个值,doThing则将对其进行调用。如果没有,则无害。该程序会将其视为无操作并继续运行。

这是可选的真正好处。您不必使用乱扔代码if (obj != nil)


难道这不是一种默默失败的形式吗?在某些情况下,您可能会关心该值为null,并希望对此做一些事情。
LegendLength

-3

if(optional.isPresent()){

这里最明显的问题是,如果“可选”真的缺(即为空),那么你的代码将会有一个NullReferenceException(或类似)炸毁试图调用一个空对象引用的任何方法!

您可能想编写一个静态的Helper-class方法,该方法确定任何给定对象类型的空值,如下所示:

function isNull( object o ) 
{
   return ( null == o ); 
}

if ( isNull( optional ) ) ... 

或者,也许更有用:

function isNotNull( object o ) 
{
   return ( null != o ); 
}

if ( isNotNull( optional ) ) ... 

但是,其中任何一个真的比原始版本更具可读性/可理解性/可维护性吗?

if ( null == optional ) ... 
if ( null != optional ) ... 
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.