首页 文章

用于可选

提问于
浏览
195

现在已经使用Java 8大约6个月了,我仍然没有信心何时使用 Optional . 我似乎想要在任何地方使用它可能是 null ,而且根本没有 .

似乎有很多情况我可以使用它,我不知道它是否增加了好处(可读性/无效安全性)或只是导致额外的开销 .

所以,我有一些例子,我对 Optional 是否有益的想法 .

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

public Optional<Foo> findFoo(String id);

2 - 当param可能是 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 值等,但是 Optional 在集合中是否有任何好的用途?

我错过了什么案例?

13 回答

  • 0

    如果你不喜欢这样检查

    if(obj!=null){
    
    }
    

    那么你可以这样做,这就是我所看到的

    if(Optional.ofNullable(obj).isPresent()){
    
    }
    

    基本上,它给出了一种幻觉,即您可以更有效地处理空值 .

  • 59

    我认为Guava Optional和他们的wiki页面相当不错:

    除了通过赋予null名称而增加可读性之外,Optional的最大优点是它的白痴证明 . 如果您希望程序完全编译,它会强制您主动考虑缺席情况,因为您必须主动解包可选并解决该情况 . Null使得简单地忘记事情变得非常容易,虽然FindBugs有所帮助,但我们认为它几乎不会解决这个问题 . 当您返回可能存在或可能不存在的值时,这尤其重要 . 你(和其他人)更有可能忘记other.method(a,b)可以返回一个空值而不是你可能会忘记当你实现other.method时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¢:

    • public Optional<Foo> findFoo(String id); - 我不确定这个 . 也许我会返回 Result<Foo> ,它可能是空的或包含 Foo . 这是一个类似的概念,但不是真正的 Optional .

    • public Foo doSomething(String id, Optional<Bar> barOptional); - 我更喜欢@Nullable和一个findbugs检查,如Peter Lawrey's answer - 另见this discussion .

    • 你的书籍例子 - 我不确定我是否会在内部使用Optional,这可能取决于复杂性 . 对于一本书的"API",我会使用 Optional<Index> getIndex() 明确指出该书可能没有索引 .

    • 我不会在集合中使用它,而不是在集合中允许空值

    一般来说,我会尽量减少绕过 null 的传递 . (一旦被烧掉......)我认为找到合适的抽象是值得的,并向其他程序员表明某个返回值实际代表什么 .

  • 22
  • 20

    Optional class允许您避免使用 null 并提供更好的替代方案:

    • 这鼓励开发者对存在进行检查,以避免被捕获 NullPointerException .

    • API变得更好记录,因为它可以看到,在哪里可以缺少值 .

    Optional 为进一步使用该对象提供了方便的API:isPresent(); get(); orElse(); orElseGet(); orElseThrow(); map(); filter(); flatmap() .

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

  • 2

    Optional 的要点是为函数提供一种返回值的方法,以指示缺少返回值 . 见this discussion . 这允许调用者继续一系列流畅的方法调用 .

    这与OP的问题中的用例 #1 最匹配 . 虽然缺少值是比null更精确的公式,因为像 IntStream.findFirst 这样的东西永远不会返回null .

    对于用例 #2 ,将可选参数传递给方法,可以使其工作,但它相当笨拙 . 假设您有一个方法,它接受一个字符串后跟一个可选的第二个字符串 . 接受 Optional 作为第二个arg会产生如下代码:

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

    即使接受null也更好:

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

    可能最好的方法是使用一个重载方法接受单个字符串参数并为第二个提供默认值:

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

    这确实有局限性,但它比上述任何一个都要好得多 .

    用例 #3#4 ,在类字段或数据结构中具有 Optional 被认为是对API的滥用 . 首先,它违背了 Optional 的主要设计目标,如顶部所述 . 其次,它没有增加任何 Value .

    有三种方法可以处理_472799中缺少值:提供替换值,调用函数以提供替换值或抛出异常 . 如果你're storing into a field, you' d在初始化或分配时执行此操作 . 如果您正在将值添加到列表中,正如OP所提到的那样,您可以选择不添加值,从而使"flattening"缺少值 .

    我敢肯定有人可能会想出一些他们真正想要在场或集合中存储 Optional 的人为案例,但总的来说,最好避免这样做 .

  • 2

    似乎 Optional 仅在Optional中的类型T是 intlongchar 等原始类型时才有用 . 对于"real"类,它对我来说没有意义,因为无论如何你都可以使用 null 值 .

    我认为这是从这里(或从另一个类似的语言概念) .

    Nullable<T>

    在C#中,很久以前就引入了 Nullable<T> 来包装值类型 .

  • 157

    我认为Optional不是可能返回null值的方法的一般替代品 .

    基本思想是:缺少 Value 并不意味着它可能在未来可用 . 这是findById(-1)和findById(67)之间的区别 .

    调用者的Optionals的主要信息是他可能不会指望给定的值,但可能在某个时间可用 . 也许它会再次消失并再次回来 . 这就像一个开/关开关 . 您可以使用“选项”来打开或关闭灯 . 但如果您没有打开灯,则无法选择 .

    所以我觉得在任何地方引入可能会返回null之前的Optionals太乱了 . 我仍然会使用null,但仅限于树的根,延迟初始化和显式查找方法等限制区域 .

  • 3

    就个人而言,我更喜欢使用IntelliJ's Code Inspection Tool来使用 @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,无需包装和解包值 . (或创建包装器对象)

  • -1

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

    您可以使用null值创建空Optional或Optional .

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

    这是一个带有非null值的Optional:

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

    Do Something If a Value Is Present

    现在您有了一个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());
    
            }
    
        }
    }
    
  • 10

    来自Oracle tutorial

    Optional的目的不是替换代码库中的每个空引用,而是帮助设计更好的API,只需读取方法的签名 - 用户可以判断是否期望可选值 . 此外,Optional强制您主动解包Optional以处理缺少值;因此,您可以保护代码免受意外的空指针异常的影响 .

  • 0

    我值得,我想加上我的2美分 . 他们反对design goal of Optional,这是由Stuart Marks's answer很好地总结,但我仍然相信它们的有效性(显然) .

    使用Optional Everywhere

    一般

    我写了一整个blog post about using Optional但它基本上归结为:

    • 设计你的课程,以避免在可能的情况下选择性

    • 在所有剩余的情况下,默认应该是使用 Optional 而不是 null

    • 可能会例外:

    • 局部变量

    • 返回私有方法的值和参数

    • 性能关键代码块(没有猜测,使用分析器)

    前两个异常可以减少 Optional 中包装和解包引用的感知开销 . 选择它们使得null永远不会合法地将边界从一个实例传递到另一个实例 .

    请注意,这几乎不会允许集合中的 Optional s几乎与 null 一样糟糕 . 只是不要这样做 . ;)

    关于你的问题

    • 是的 .

    • 如果超载是没有选择的,是的 .

    • 如果其他方法(子类化,装饰......)别无选择,是的 .

    • 请不要!

    优点

    这样做可以减少代码库中 null 的存在,尽管它不能消除它们 . 但这甚至不是主要观点 . 还有其他重要的优点:

    澄清意图

    使用 Optional 清楚地表明该变量是可选的 . 您的代码的任何读者或您的API的消费者都将遭受殴打,因为在访问该值之前可能没有任何内容并且需要进行检查 .

    消除不确定性

    如果没有 Optionalnull 事件的含义尚不清楚 . 它可能是状态的合法表示(请参阅Map.get)或初始化缺失或失败的实现错误 .

    随着 Optional 的持续使用,这种情况发生了巨大变化 . 在这里, null 的出现已经表明存在错误 . (因为如果允许丢失该值,则会使用 Optional . )这使得调试空指针异常变得更加容易,因为已经回答了 null 的含义问题 .

    更多空检查

    既然没有任何东西可以 null 了,这可以在任何地方强制执行 . 无论是注释,断言还是普通检查,您都不必考虑此参数或返回类型是否为null . 它不能!

    缺点

    当然,没有银弹......

    表现

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

    请注意,编译器可能能够绕过 Optional s的短寿命的额外引用 . 在Java 10中,value types可能会进一步减少或消除惩罚 .

    序列化

    Optional is not serializableworkaround并不过分复杂 .

    不变性

    由于Java中泛型类型的不变性,当将实际值类型推入泛型类型参数时,某些操作会变得很麻烦 . 给出了一个例子here (see "Parametric polymorphism") .

  • 2

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

    我打算对我的一个项目进行大量测试,因此我 Build 了断言;只有我需要验证的东西和其他我没有验证的东西 .

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

    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;
        }
    }
    

    这意味着如果我想检查标签,断言真的只会触发!

  • 1

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

    这是good article,它显示了用例#1的用处 . 有这个代码

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

    转化为这个

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

    通过使用Optional作为相应getter方法的返回值 .

相关问题