为什么Java堆栈Optionals?

时间:2017-09-08 12:10:51

标签: java optional language-design

我通常对Java语言的设计非常满意,但今天我遇到了一个奇怪的设计选择。 我不知道您可以建议更改版本9或10的Java语言的任何平台,因此我在这里写这个问题。

如果值为null,则使用Optionals可以更好地控制行为。

今天我将流中的值映射到Optional。 我真的很惊讶,findFirst然后给了Optional<Optional<T>>而不只是Optional<T>

Optional<Optional<T>>

的重点在哪里?

对于Java 9和Streams,这可以通过.flatMap(Optional::stream)解决,但这只是一个解决方法和样板代码。

如果Optionals通常不会堆叠并且总是平缓下来会不会更好?

Optional<Optional<T>> => Optional<T>
Optional.of(Optional.of("")) => Optional.of("")

进一步解释:

如果子可选项为null或实际对象为null,则使用Optionals并不关心。它总是归结为Optional.empty()

String s = null;
Optional<String> opt = null;
Optional.of(s) => Optional.empty()
Optional.of(opt) => Optional.empty()

此外,如果您正在使用选项,您只对该对象感兴趣。因此,如果您尝试获取对象,则必须双倍获取并检查可选项。

if(optionalOfOptional.isPresent()) {
    optional = optionalOfOptional.get();
    if(optional.isPresent()) {
        objectOfInterest = optional.get();
    }
}

我甚至认为这是错误的来源,因为如果你的对象存在条件,你总是需要检查两个选项。

if(optionalOfOptional.isPresent() && optionalOfOptional.get().isPresent()) {
    ...
}

只检查第一个选项很容易导致错误。

此外,为什么首先拥有null的Optional是有意义的?我不能选择摆脱使用null吗?

在方法层面,这可以肯定地解决。

例如Optional.of可能类似于:

public static <T> Optional<T> of(T value) {
    return value instanceof Optional ? value : new Optional<>(value);
}

在类型级别上,这可能并不容易。

2 个答案:

答案 0 :(得分:4)

禁止Optional<Optional<T>>只是没有意义。

假设你有一个List<Optional<T>>。以下列表不同:

List<Optional<T>> listA = Arrays.asList(Optional.empty());
List<Optional<T>> listB = Arrays.asList();

它们显然是不同的:第一个是非空的,第二个是空的。

相应地,在他们的流上调用findFirst()的结果是不同的:

System.out.println(listA.stream().findFirst().isPresent()); // true
System.out.println(listB.stream().findFirst().isPresent()); // false

现在,我知道Java无法知道的那些列表中数据的含义。假设列表包含调用Callable<Optional<T>>列表的结果。在这种情况下,可能与我的应用程序之间的重要区别:

  • 我调用了N&gt; 0个callables,并且所有这些都返回了Optional.empty()
  • 我调用了零callables。

所以我真的不希望语言或API假设两者之间没有区别,并将Optional<Optional<T>>转换为Optional<T>

由于List<E>.stream().findFirst() 总是返回Optional<E>,因此无法阻止Optional<Optional<T>>而不会阻止我创建List<Optional<T>>(或CollectionSetHashMap<K, Optional<T>>等)。所以,你基本上必须完全禁止嵌套泛型,这将是一个重大的功能损失。

我对Optional<Optional<T>>的存在非常满意,因为它与语言的其余部分完全一致。如果您不喜欢它们,请避免创建它们;但不要认为这对其他人都是正确的。

答案 1 :(得分:1)

我在其他地方谈到an Optional<T> should never be null.它违反了Optional<T>承诺:摆脱NullPointerException s。

因此,我认为在Optional API中设置不足以允许Optional<Optional<T>>。给定Optional时,获取Optional实例的方法应该表现得更好。但话虽如此,他们不能。如果Optional.of超载除了一个小问题,它们就可以完成。

由于您可以使用类型为Optional的泛型类型T,因此不能有两个Optional.of方法(一个用于Optional,一个用于T)。如果有人有一个明智的想法,它会阻止某些代码编译Optional<Optional<T>>。拿下面的代码,它将无法编译。

package otherCode;

public class Abc<T> {
    public static void main(String[] args) {
        Abc<B> test1 = new Abc<>();
        test1.foo(new A());
        test1.foo(new B());

        Abc<Base> test2 = new Abc<>();
        test2.foo(new A());
        test2.foo(new B());

        Abc<A> test3 = new Abc<>();
        // these two don't compile
        test3.foo(new A());
        test3.foo(new B());
    }

    public void foo(A item) {
        System.out.println("Foo");
    }

    public void foo(T item) {
        System.out.println("bar");
    }

    public static class Base {
    }

    public static class A extends Base {

    }

    public static class B extends Base {

    }
}

不强制对整个语言进行语言和语法更改(禁止Optional<Optional<T>>),自动转换并非易事。

Java 8一般我发现需要开发人员一段时间才能掌握。当它第一次出现时,Map<String, Optional<T>>Map<String, T>已经足够或我看到Optional<Optional<T>>时,我会看到Map<Optional<T>, Optional<Set<Optional<T>>>>。我甚至看到了Map<T, Set<T>>,而不仅仅是Optional<Optional<T>>。随着时间的推移和人们对语言的斗争,我觉得我们学会了更好地管理这些打字并获得更明智的打字。

我认为不幸的是,Java不会自动进行转换,但对于可能需要Stream<Optional<T>>.first一天的穷人,我愿意使用map,flatMap,或Else,以及of null每隔一段时间就能保持我的打字理智。使用这些方法可能有助于解决您的特定困境。

修改

我看到,无论是错过了还是编辑,OP都看到Optional<Optional<T>>返回class B(val x: A) extends AnyVal。 OP打算做什么会影响妥协。他们可以.map流图形去除内部可选或他们可以.filter&amp; .map将流去掉空的Optionals。一个令人遗憾的额外代码。