用法示例

现在我们已经学习了为何需要使用 Optional ,我们来看看我们在 Java 中可以做什么。这些代码示例(和 Sponge)使用 Java 8 中提供的 java.util.Optional 类。

获取被包装的值

get() 方法将解除 Optional 的包装并返回被包装的值。如果没有值,调用 get() 方法会抛出一个 NoSuchElementException ,所以一般情况下应首先进行一次检查。

Optional<String> opt = getOptionalString();
String wrappedString = opt.get();

处理值不在时的情况

使用 Optional 的目的就是处理可能存在也可能不存在的值。因此,许多使用案例都是围绕着处理不存在的值的。

检查是否存在

isPresent() 方法将在 Optional 的值存在时返回 true,这和一个经典的 null 检查类似。

Optional<String> opt = getOptionalString();
if (opt.isPresent()) {
    String wrappedString = opt.get();
    // more code
}

使用默认值

使用默认值是当值不存在时的常见行为。 orElse() 方法允许传入一个默认值以使得在 Optional 中的值不存在时返回默认值。

旧做法:

Optional<String> optionalString = getOptionalString();
String someString;
if (optionalString.isPresent()) {
    someString = optionalString.get();
} else {
    someString = DEFAULT_STRING;
}

新做法:

String someString = getOptionalString().orElse(DEFAULT_STRING);

在某些情况下,默认值会有着副作用或者特别昂贵的计算成本。在这种情况下,开发者希望仅在需要时计算默认值( 惰性计算 )。方法 orElseGet() 接受一个 Supplier 而不是预先计算的值作为参数。如果 Optional 本身没有值,则将调用 Supplier 。可以直接向 Supplier 传递 Lambda 表达式或方法引用。

旧做法:

Optional<String> optionalString = getOptionalString();
String someString;
if (optionalString.isPresent()) {
    someString = optionalString.get();
} else {
    someString = myPlugin.defaultString();
}

新做法:

String someString = getOptionalString().orElseGet(myPlugin::defaultString);

在不存在值时抛出异常

如果一个值不存在应该导致一个异常的抛出,一般情况下总是最好抛出一个自定义异常,而不是依赖于默认的 NoSuchElementException 。 如果你把一个 Supplier 传入 orElseThrow() 方法并加以调用,那么它会在 Optional 包含的值存在时返回该值,值不存在时抛出一个 Supplier 决定的 Throwable 。同样,你可以直接在 Supplier 中使用 Lambda 表达式或方法引用。

旧做法:

Optional<String> optionalString = getOptionalString();
if (!optionalString.isPresent()) {
    throw new MyException();
}
String someString = optionalString.get();

新做法:

String someString = getOptionalString().orElseThrow(MyException::new);

備註

如果方法参数提供的 Throwable 是一个需要被检查的异常,那么它也必须包含在被调用方法的签名(Signature)中(例如 public void doStuff() throws MyException )。

条件代码执行

如果没有默认值可以使用,那么对应的不存在情况下的代码往往就不应执行。虽然一个简单的条件判断就可以解决问题,不过我们还有一些其他的方法。

传入方法参数

如果你已经把处理当前值的逻辑封装在了 Consumer 或者单参数函数中,那么 ifPresent() 方法将接受它(或方法引用)作为参数。如果一个值出现在 Optional 上,它将被传递给该方法的参数。如果 Optional 是空的,那么,除了安全,什么都不会发生。

旧做法:

Optional<String> optionalString = getOptionalString();
if (optionalString.isPresent()) {
    myPlugin.doSomethingWithString(optionalString.get());
}

新做法:

Optional<String> optionalString = getOptionalString();
optionalString.ifPresent(s -> myPlugin.doSomethingWithString(s));

getOptionalString().ifPresent(myPlugin::doSomethingWithString);

過濾

你也可以传入一个 Predicate 。只有使得 Predicate 返回 true 的值才会被保留,也就是说,如果值本身并不存在,或者 Predicate 返回了 false,那么我们就会得到不包含值的 Optional 。因为这个方法返回一个 Optional ,所以这允许开发者使用链式的方式将它和其他方法在同一行代码中调用。

旧做法:

Optional<String> optionalString = getOptionalString();
if (optionalString.isPresent()) {
    String someString = optionalString.get();
    if (stringTester.isPalindromic(someString)) {
        myPlugin.doSomethingWithString(someString);
    }
}

新做法:

getOptionalString()
      .filter(stringTester::isPalindromic)
      .ifPresent(myPlugin::doSomethingWithString);

備註

这个作为筛选器的函数和下面描述的映射(Map)函数都不会修改它们被调用的实例。也就是说 Optional 总是不可变的。

映射

另一个可以进行链式调用的操作是将包含的值映射到不同的值。如果没有值,则任何值都不会改变。但是如果它存在,那么方法将返回一个给定 Function 的返回值(或在返回值是 null 时返回一个空 Optional )。

旧做法:

Optional<String> optionalString = getOptionalString();
if (optionalString.isPresent()) {
    String someString = optionalString.get().toLowerCase();
    myPlugin.doSomethingWithString(someString);
}

新做法:

getOptionalString()
      .map(s -> s.toLowerCase())
      .ifPresent(myPlugin::doSomethingWithString);

小訣竅

如果你的映射函数已经返回一个 Optional 了,那么请使用 flatMap() 方法。该方法的行为就像 map() 一样,仅有的区别就是它的返回值已经是一个 Optional 了,也就是说不需要再包装返回值。

結合範例

想象一个插件允许每个玩家有宠物跟随。假设存在以下方法:

  • petRegistry.getPetForPlayer() 需要传入一个 Player 作为参数并返回 Optional<Pet> 。该方法查找与给定玩家相关联的宠物。

  • petHelper.canSpawn() 需要传入一个 Pet 作为参数并返回一个 boolean 。此方法执行所有必要的检查,以确保给定的宠物可以产生。

  • petHelper.spawnPet() 需要传入一个 Pet 作为参数并没有返回值。此方法将产生一个之前不存在的宠物。

现在从某处(可能是执行一个命令),我们得到了 optionalPlayer 变量,它保存着一个 Optional<Player> 。我们现在想要获得这个玩家的宠物,检查宠物是否能够被产生,如果它不被产生则产生,同时执行相应的检查,这些操作都将在每一个 Optional 实际上包含一个值的情况下发生。只使用基本的 isPresent()get() 方法产生的代码在效率上的确很高。

if (optionalPlayer.isPresent()) {
    Player player = optionalPlayer.get();
    Optional<Pet> optionalPet = petRegistry.getPetForPlayer(player);
    if (optionalPet.isPresent()) {
        Pet pet = optionalPet.get();
        if (petHelper.canSpawn(pet)) {
            petHelper.spawnPet(pet);
        }
    }
}

然而,通过使用 Optional 的一些方法来执行条件代码,我们可以隐藏所有的这些存在检查,减少了不必要的代码和缩进级别,从而使代码可读性得到了提高:

optionalPlayer
      .flatMap(petRegistry::getPetForPlayer)
      .filter(petHelper::canSpawn)
      .ifPresent(petHelper::spawnPet);

创建 Optional

如果你选择提供一个 API ,遵循使用 Optional 而不是返回可能出现的 null 值,那么你需要首先创建 Optional 然后才能返回它们。通过调用三个静态构造方法中的一个,我们可以完成这一点。

Optional.empty() 将总是返回一个不存在任何值的 Optional

Optional.of() 将返回一个包含有给定参数的 Optional ,并且在给定参数为 null 时抛出 NullPointerException 异常。

Optional.ofNullable() 将在给定的参数为 null 时返回一个不包含有任何值的 Optional ,在给定的参数存在值时返回一个包含有该值的 Optional