用于可选


271

现在已经使用Java 8超过6个月左右,我对新的API更改感到非常满意。我仍然不自信的一个领域是什么时候使用Optional。我似乎在想要在可能存在的null任何地方都使用它而根本不在任何地方使用之间切换。

我可以使用它的情况似乎很多,而且我不确定它是否会带来好处(可读性/空安全性)或仅仅是造成额外的开销。

因此,我有一些示例,我会对社区是否Optional有益的想法感兴趣。

1-作为方法可以返回时的公共方法返回类型null

public Optional<Foo> findFoo(String id);

2-当参数可以是null

public Foo doSomething(String id, Optional<Bar> barOptional);

3-作为bean的可选成员:

public class Book {

  private List<Pages> pages;
  private Optional<Index> index;

}

4-在Collections

总的来说,我不认为:

List<Optional<Foo>>

添加任何内容-尤其是因为可以filter()删除null值等,但是Optionalin集合有什么好的用处吗?

我错过了任何情况吗?


2
我发现有用的一种情况是,例如,如果您有替换图。例如Map<Character, String>。如果没有替代,我可以使用:Optional.ofNullable(map.get(c)).orElse(String.valueOf(c))。还要注意,Optional是从Guava那里偷来的,它的语法更好:Optional.fromNullable(map.get(c)).or(String.valueOf(c));
fge

3
同样,在集合中,有些集合不允许使用空值!可选适合这里的账单。您可以.filter(Optional::absent)
清空

2
@fge公平地说,我认为Optional的概念实际上源于FP。
VH-NZZ 2014年

2
@fge更好用getOrDefault()吗?
Jim Garrison

Answers:


206

的主要目的Optional是为函数提供一种返回值的手段,以指示不存在返回值。看到这个讨论。这使调用者可以继续进行一系列流畅的方法调用。

这与OP问题中的用例#1最接近。虽然,缺少值是比null更精确的表述,因为类似的东西IntStream.findFirst永远不会返回null。

对于用例2,将可选参数传递给方法,可以使之起作用,但这相当笨拙。假设您有一个方法,该方法接受一个字符串,然后是一个可选的第二个字符串。接受Optionalas作为第二个arg将导致如下代码:

foo("bar", Optional.of("baz"));
foo("bar", Optional.empty());

甚至接受null也会更好:

foo("bar", "baz");
foo("bar", null);

最好的办法是拥有一个重载的方法,该方法接受单个字符串参数并为第二个提供默认值:

foo("bar", "baz");
foo("bar");

这确实有局限性,但是比以上任何一种都好。

在类字段或数据结构中具有的用例#3#4Optional被视为对API的滥用。首先,它违反Optional了顶部所述的主要设计目标。其次,它不会增加任何价值。

可以使用三种方法来处理缺少值的情况Optional:提供替代值,调用函数以提供替代值或引发异常。如果要存储到字段中,则可以在初始化或分配时执行此操作。如果要将值添加到列表中(如OP所述),则可以选择不添加值,从而“拉平”缺少的值。

我确信有人可以提出一些他们确实想Optional在字段或集合中存储的人为设计的案例,但是总的来说,最好避免这样做。


46
我不同意#3。Optional在有或没有值的情况下要进行或不进行操作时,将其用作字段可能会很有用。
glglgl 2015年

15
我不明白为什么if(foo == null)内部做比将字段存储为会更好optional。而且我不明白为什么getOptionalFoo()内部显式调用比仅将字段存储为更好Optional。另外,如果一个字段可以是null和一个字段不能null,为什么不传达到编译器,使它们Optional与否Optional
mogronalol '16

27
@StuartMarks,当我听到所有Optional<>专家的来信时,这就是让我感到困惑的事情之一:“不要存放Optional<>在田野里”。我确实是在试图理解此建议的重点。考虑一个节点可能具有父节点的DOM树。在用例中似乎Node.getParent()会返回Optional<Node>。我真的希望null每次存储一个并包装结果吗?为什么?除了使我的代码看起来难看之外,这种额外的低效率还能给我带来什么?
Garret Wilson

7
@GarretWilson另一方面,假设您有Optional<Node> getParent() { return Optional.ofNullable(parent); }。这看起来很昂贵,因为它Optional每次分配一个!但是,如果调用者立即将其打开包装,则该对象的寿命极短,并且永远不会被提升到伊甸园空间之外。JIT的转义分析甚至可能消除了它。最重要的答案是“视情况而定”,但是Optional在字段中使用可能会浪费内存,并减慢数据结构的遍历速度。最后,我认为它会使代码混乱,但这只是一个问题。
斯图尔特·马克斯

6
@GarretWilson那么,您为此写过博客吗?我对此很感兴趣:)
akhil_mittal

78

我玩游戏迟到了,但是对于它的价值,我想加上2美分。他们违背的设计目标Optional,这是深受总结斯图尔特商标的答案,但我仍然相信它们的有效性(显然)的。

在任何地方使用可选

一般来说

我写了一篇有关使用Optional的完整博客文章,但基本上可以归结为:

  • 在可行的情况下设计类以避免可选性
  • 在所有其余情况下,默认值应为Optional而不是null
  • 可能为以下情况设置例外:
    • 局部变量
    • 返回值和参数给私有方法
    • 性能关键代码块(不要猜测,请使用探查器)

前两个异常可以减少对中包装和展开引用的感知开销Optional。选择它们是为了使空值永远不会合法地将边界从一个实例传递到另一个实例。

请注意,这几乎不会允许Optionals与nulls 一样糟糕。只是不要这样做。;)

关于你的问题

  1. 是。
  2. 如果没有重载选项,则可以。
  3. 如果没有其他方法(子类化,装饰等),则为是。
  4. 请不!

优点

这样做可以减少nulls在您的代码库中的存在,尽管它不会根除它们。但这还不是重点。还有其他重要优点:

明确意图

使用Optional清楚地表明变量是可选的。您的代码的任何阅读者或API的使用者都将被打败,因为那里可能什么也没有,并且在访问该值之前需要检查。

消除不确定性

没有发生Optional的含义null还不清楚。它可能是状态的合法表示(请参阅参考资料Map.get)或实现错误,例如缺少初始化或初始化失败。

持续使用会大大改变Optional。在这里,已经出现null表示存在错误。(因为如果允许该值丢失,Optional则将使用。)这使调试空指针异常变得更加容易,因为null已经回答了这个含义的问题。

更多空检查

现在什么都没有null了,可以在任何地方强制执行。无论是使用注释,断言还是简单检查,您都无需考虑此参数或该返回类型是否可以为null。不能!

缺点

当然,没有灵丹妙药...

性能

将值(尤其是基元)包装到一个额外的实例中可能会降低性能。在紧密的循环中,这可能变得明显甚至更糟。

请注意,对于Optionals的短生命期,编译器可能能够绕开额外的参考。在Java 10中,值类型可能会进一步减少或消除代价。

序列化

Optional不可序列化,解决方法也不会过于复杂。

不变性

由于Java中泛型类型的不变性,当将实际值类型推入泛型类型参数时,某些操作变得很麻烦。此处给出一个示例(请参阅“参数多态性”)


另一个缺点(或限制)是,您不能对Optional(xxx)实例进行排序,尽管Oracle明确地故意施加了该限制。我对在仅应在特定情况下使用这些类的情况下提供这些类的智慧表示怀疑,并假设开发人员将做正确的事情。解决一个问题可能会导致另一个问题。如果确实不打算使用Optional(xxx)类以“不希望的”方式(例如,作为方法参数,集合,构造函数等)使用Optional(xxx)类,则可能会有编译器警告来帮助开发人员?
skomisa

大约4,集合中的可选:实际上有时将可选存储在Lists中很有用。当某个元素位于某个索引很重要但该元素可以存在或不存在时,就是这种情况。显然可以通过一种List<Optional<T>>类型来传达。另一方面,如果只重要的是哪些对象是某个集合的成员,则没有意义。
2016年

3
我认为用例2仍然很成问题。一个Optional通入方法的可null。那你得到了什么?现在您要进行两项检查。请参阅stackoverflow.com/a/31923042/650176
David V

2
@DavidV看看我提供的优势列表-它们也都适用于该情况。此外,如果您全力以赴Optional(如果您愿意将其作为参数传递,那么情况null就是如此)永远都不是法律价值。因此,调用带有显式参数的null方法显然是一个错误。
Nicolai

28

就个人而言,我更喜欢使用的IntelliJ的代码检查工具来使用@NotNull@Nullable检查,因为这些在很大程度上是编译时(可能有一些运行时检查),这在代码的可读性和运行时性能方面的开销更低。它不像使用Optional那样严格,但是这种欠缺严格性应该得到不错的单元测试的支持。

public @Nullable Foo findFoo(@NotNull String id);

public @NotNull Foo doSomething(@NotNull String id, @Nullable Bar barOptional);

public class Book {

  private List<Pages> pages;
  private @Nullable Index index;

}

List<@Nullable Foo> list = ..

这适用于Java 5,无需包装和解包值。(或创建包装对象)


7
嗯,这些是IDEA注释吗?我更喜欢JSR 305的@Nonnull个人–与FindBugs合作;)
fge

@fge在这方面缺乏标准,但是我相信这些工具通常是可配置的,您不必最终使用@Nonnull @NotNull etc
Peter Lawrey 2014年

4
是的,那是真的。IDEA(13.x)提供了三种不同的选择...嗯,我总是最终使用JSR 305 anywa
fge 2014年

3
我知道这是旧的,但对注释和工具的讨论值得链接到stackoverflow.com/questions/35892063/... -这BTW专门针对Java的8的情况下
斯蒂芬·赫尔曼

25

我认为Guava Optional和他们的Wiki页面说得很好:

除了为null命名而提高了可读性外,Optional的最大优点是其防白痴。如果您要完全编译程序,它会迫使您积极考虑不存在的情况,因为您必须主动展开Optional并解决该情况。Null使得简单地忘记事情变得异常容易,尽管FindBugs有所帮助,但我们认为它几乎不能解决问题。

当您返回可能存在或不存在的值时,这一点尤其重要。您(和其他人)更可能忘记在实现other.method时,other.method(a,b)可能返回空值,而您可能忘记了a可能为null。返回Optional使调用者无法忘记这种情况,因为调用者必须自己解开对象才能编译代码。-(来源:Guava Wiki-使用和避免使用null-有什么意义?

Optional这会增加一些开销,但是我认为它的明显优势是使它明确 表明可能不存在对象,并强制程序员处理这种情况。它可以防止有人忘记心爱的!= null支票。

2为例,我认为编写代码要明确得多:

if(soundcard.isPresent()){
  System.out.println(soundcard.get());
}

if(soundcard != null){
  System.out.println(soundcard);
}

对我来说,Optional更好地抓住了没有声卡的事实。

关于您的观点,我的2美分:

  1. public Optional<Foo> findFoo(String id);-我不确定。也许我会返回一个Result<Foo>可能为或包含的Foo。这是一个类似的概念,但实际上不是Optional
  2. public Foo doSomething(String id, Optional<Bar> barOptional);-我更喜欢@Nullable和findbugs检查,就像彼得·劳里(Peter Lawrey)的答案一样 -另请参阅此讨论
  3. 您的图书示例-我不确定是否在内部使用Optional,这可能取决于复杂程度。对于书籍的“ API”,我将使用Optional<Index> getIndex()来明确指示该书籍可能没有索引。
  4. 我不会在集合中使用它,而是不允许在集合中使用空值

通常,我会尽量减少nulls的传递。(一旦烧毁...)我认为值得找到适当的抽象并向其他程序员指示某个返回值实际上代表什么。


15
你会写soundcard.ifPresent(System.out::println)。在isPresent语义上,调用与检查相同null
更好的Oliver 2014年

我的问题是带有Optional类型的东西仍然可以为null。因此,要真正完全安全,您必须执行类似的操作if(soundcard != null && soundcard.isPresent())。虽然我希望制作一个可以返回Optional并还可以返回null的api,但我希望没人能做到。
西蒙·鲍姆加特·韦兰德

“如果您要完全编译程序,它会迫使您积极考虑不存在的情况,因为您必须主动拆开Optional并解决该情况。” 我不太确定它是如何强制执行此操作的,尽管我不太使用Java。您能解释一下Java强迫您解包并检查!isPresent情况以进行简单编译吗?
美眉

15

Oracle教程

Optional的目的不是替换代码库中的每个空引用,而是帮助设计更好的API,在这些API中,仅通过读取方法的签名,用户就可以知道是否期望可选值。此外,“可选方案”会强迫您积极展开“可选方案”以应对价值缺失的情况;结果,您可以保护代码免受意外的空指针异常的影响。


3
仅用于方法返回部分的API,对吧?由于类型擦除,不应将可选参数用作参数?有时,我在方法内部使用Optional,从可为空的参数转换为字段,或者从空构造函数的参数初始化为字段...仍然试图弄清楚这是否比将其保留为null更有用-任何人都有想法吗?
ycomp

@ycomp您可以找到有关在这些情况下相当多的信息,这个那个问题。这些问题的答案远不止是被问及涵盖其他情况的。有关更多信息,请浏览更多或提出您自己的问题,因为在这里您可能不会受到太多关注。; )
ctomek

8

1-当方法可以返回null时,作为公共方法的返回类型:

这是一篇很好的文章,展示了用例#1的有用性。有这个代码

...
if (user != null) {
    Address address = user.getAddress();
    if (address != null) {
        Country country = address.getCountry();
        if (country != null) {
            String isocode = country.getIsocode();
            isocode = isocode.toUpperCase();
        }
    }
}
...

变成了这个

String result = Optional.ofNullable(user)
  .flatMap(User::getAddress)
  .flatMap(Address::getCountry)
  .map(Country::getIsocode)
  .orElse("default");

通过使用Optional作为各个getter方法的返回值。


2

(我相信)这是...测试的一种有趣用法。

我打算严格测试我的一个项目,因此建立了断言。只有有些事情我需要验证,而其他则不需要。

因此,我构建了要断言的东西,并使用一个断言来验证它们,如下所示:

public final class NodeDescriptor<V>
{
    private final Optional<String> label;
    private final List<NodeDescriptor<V>> children;

    private NodeDescriptor(final Builder<V> builder)
    {
        label = Optional.fromNullable(builder.label);
        final ImmutableList.Builder<NodeDescriptor<V>> listBuilder
            = ImmutableList.builder();
        for (final Builder<V> element: builder.children)
            listBuilder.add(element.build());
        children = listBuilder.build();
    }

    public static <E> Builder<E> newBuilder()
    {
        return new Builder<E>();
    }

    public void verify(@Nonnull final Node<V> node)
    {
        final NodeAssert<V> nodeAssert = new NodeAssert<V>(node);
        nodeAssert.hasLabel(label);
    }

    public static final class Builder<V>
    {
        private String label;
        private final List<Builder<V>> children = Lists.newArrayList();

        private Builder()
        {
        }

        public Builder<V> withLabel(@Nonnull final String label)
        {
            this.label = Preconditions.checkNotNull(label);
            return this;
        }

        public Builder<V> withChildNode(@Nonnull final Builder<V> child)
        {
            Preconditions.checkNotNull(child);
            children.add(child);
            return this;
        }

        public NodeDescriptor<V> build()
        {
            return new NodeDescriptor<V>(this);
        }
    }
}

在NodeAssert类中,我这样做:

public final class NodeAssert<V>
    extends AbstractAssert<NodeAssert<V>, Node<V>>
{
    NodeAssert(final Node<V> actual)
    {
        super(Preconditions.checkNotNull(actual), NodeAssert.class);
    }

    private NodeAssert<V> hasLabel(final String label)
    {
        final String thisLabel = actual.getLabel();
        assertThat(thisLabel).overridingErrorMessage(
            "node's label is null! I didn't expect it to be"
        ).isNotNull();
        assertThat(thisLabel).overridingErrorMessage(
            "node's label is not what was expected!\n"
            + "Expected: '%s'\nActual  : '%s'\n", label, thisLabel
        ).isEqualTo(label);
        return this;
    }

    NodeAssert<V> hasLabel(@Nonnull final Optional<String> label)
    {
        return label.isPresent() ? hasLabel(label.get()) : this;
    }
}

这意味着断言实际上仅在我要检查标签时才触发!


2

Optional类使您避免使用null并提供更好的替代方法:

  • 这鼓励开发人员进行状态检查,以避免未捕获NullPointerException的状态。

  • API可以更好地记录文档,因为可以看到在哪里可以预期到可能缺少的值。

Optional提供方便的API,以进一步处理该对象: isPresent(); get(); orElse(); orElseGet(); orElseThrow(); map(); filter(); flatmap()

另外,许多框架都主动使用此数据类型并从其API返回它。


2

在Java中,除非您沉迷于函数式编程,否则请不要使用它们。

它们没有方法参数的位置(我希望某人有一天会为您传递一个null可选内容,而不仅仅是一个空的可选内容)。

它们对于返回值有意义,但它们邀请客户类继续延伸行为构建链。

FP和链在像Java这样的命令性语言中几乎没有位置,因为它使得调试非常困难,而不仅仅是读取。当您上线时,您将不知道程序的状态或意图。您必须介入以解决这个问题(进入到通常不是您自己的代码中,并且尽管有步过滤器也进入很多堆栈框架),并且必须添加大量断点以确保它可以停在您添加的代码/ lambda中,而不是简单地走if / else / call平凡的行。

如果要进行函数式编程,请选择Java以外的其他工具,并希望您有调试该工具的工具。


1

我不认为Optional是可能返回空值的方法的一般替代方法。

基本思想是:缺少值并不意味着它将来可能可用。findById(-1)和findById(67)之间是有区别的。

呼叫者的Optionals的主要信息是,他可能不依靠给定的值,但有时可能会使用它。也许它将再次消失,稍后再返回一次。这就像一个开/关开关。您具有“选项”来打开或关闭灯。但是,如果您没有照明灯,则别无选择。

因此,我发现在以前可能返回null的任何地方引入Optionals太混乱了。我仍将使用null,但仅在诸如树根,延迟初始化和显式查找方法之类的受限区域中使用。


0

似乎Optional是唯一有用的,如果在可选的T类型是基本类型一样intlongchar,等。对于“真实”的类,它没有任何意义,我因为你可以使用一个null反正值。

我认为它是从这里(或从另一种类似的语言概念)获得的。

Nullable<T>

在C#中,Nullable<T>很早就引入了这种包装值类型的方法。


2
这是番石榴的骗人货的Optional,其实
FGE

2
@fge可以,但是当C#(2005,MS.NET 2.0)中出现这种情况时,我认为没有番石榴。还有……谁知道C#从何处获得的。
peter.petrov 2014年

3
@fge“ Guip的Optional的Ripoff”是一种有趣的表达方式,因为Guava的人参加了讨论,提供了他们的经验,并普遍赞成java.util.Optional
斯图尔特·马克

6
@fge毫无疑问,Java的API受到Guava的强烈影响。我遇到了“盗窃”的问题,这听起来像是在偷东西。番石榴人贡献了很多宝贵的意见对Java 8
斯图尔特商标

6
您错过了重点。将使用可选而不是返回null。它比潜在地抛出NullPointerException更安全。请参阅:oracle.com/technetwork/articles/java/…–
Kilizo


-1

Java SE 8引入了一个名为java.util.Optional的新类,

您可以创建一个空的Optional或NULL值的Optional。

Optional<String> emptyOptional = Optional.empty(); 

这是一个非空值的Optional:

String valueString = new String("TEST");
Optional<String> optinalValueString = Optional.of(valueString );

如果存在值则执行某项操作

现在您有了一个Optional对象,您可以访问可用于显式处理值存在或不存在的方法。不必记住要执行空检查,如下所示:

String nullString = null;
if (nullString != null) {
    System.out.println(nullString);
}

您可以使用ifPresent()方法,如下所示:

Optional<String> optinalString= null;
optinalString.ifPresent(System.out::println);

package optinalTest;

import java.util.Optional;

public class OptionalTest {
    public Optional<String> getOptionalNullString() {
        return null;
//      return Optional.of("TESt");
    }

    public static void main(String[] args) {

        OptionalTest optionalTest = new OptionalTest();

        Optional<Optional<String>> optionalNullString = Optional.ofNullable(optionalTest.getOptionalNullString());

        if (optionalNullString.isPresent()) {
            System.out.println(optionalNullString.get());
        }
    }
}

此复制粘贴的目的是获得廉价声誉,但与OP的问题无关
stinger
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.