如何在Java 8中实现Elvis运算符?

问题描述 投票:0回答:5

我有一个经典的“Elvis 运算符”案例,其中我调用每个可能返回 null 的方法并将它们链接在一起:

thing?:nullableMethod1(a)?:nullableMethod2(b)?:nullableMethod3()

在 Java 8 中,我发现的最忠实的实现是这样的:

return Optional.ofNullable(thing)
    .flatMap(x -> Optional.ofNullable(x.nullableMethod1(a)))
    .flatMap(y -> Optional.ofNullable(y.nullableMethod2(b)))
    .flatMap(z -> Optional.ofNullable(z.nullableMethod3()))

我希望 Java 的

Optional
有类似于 elvis 运算符的东西:

public<U> Optional<U> elvisOperator(Function<? super T, ? extends U> mapper) {
    return flatMap(t -> Optional.ofNullable(mapper.apply(t));
}

这样我就不必包装每个返回值:

return Optional.ofNullable(thing)
    .elvisOperator(x -> x.nullableMethod1(a))
    .elvisOperator(y -> y.nullableMethod2(b))
    .elvisOperator(Z::nullableMethod3); // also nice

是否有更高效、更惯用的方式在 Java 8 中实现 Elvis 运算符模式?

java java-8 functional-programming option-type
5个回答
24
投票

也许我忽略了一些东西,但是有什么原因你不能使用

Optional#map
吗?

以下示例不打印任何内容,因为

Optional
短路,因为如果
Optional
内的值不存在(它是
null
Optional
为空),则将其处理为空。

Optional.ofNullable("test")
        .map(s -> null)
        .ifPresent(System.out::println);

因此,我认为您可以执行以下操作:

return Optional.ofNullable(thing)
               .map(x -> x.nullableMethod1(a))
               .map(y -> y.nullableMethod2(b))
               .map(Z::nullableMethod3);

这将映射您的

thing
(如果存在),否则返回空的
Optional


13
投票

在 Java 8 中,可以通过链接

.map(...)
调用
Optional.ofNullable(...)
并用
.orElse(...)
来模拟 Elvis 运算符:

Optional.ofNullable(dataObject)
.map(DataObject::getNestedDataObject)
.map(NestedDataObject::getEvenMoreNestedDataObject)
...
.orElse(null);

完整示例:

import java.util.Optional;

class Main {
  // Data classes:
  static class Animal {
    Leg leg;

    Animal(Leg leg) {
      this.leg = leg;
    }

    Leg getLeg(){return this.leg;}

    public String toString(){
      String out = "This is an animal";
      out += leg != null ? " with a leg" : "";
      return out;
    }
  }

  static class Leg {
    Toes toes;

    Leg(Toes toes) {
      this.toes = toes;
    }

    Toes getToes(){return this.toes;}

    public String toString(){
      String out = "This is a leg";
      out += toes != null ? " with a collection of toes" : "";
      return out;
    }
  }

  static class Toes {
    Integer numToes;

    Toes(Integer numToes) {
      this.numToes = numToes;
    }

    Integer getNumToes(){return this.numToes;}

    public String toString(){
      String out = "This is a collection of ";
      out += numToes != null && numToes > 0 ? numToes : "no";
      out += " toes";
      return out;
    }
  }

  // A few example Elvis operators:
  static Integer getNumToesOrNull(Animal a) {
    return Optional.ofNullable(a)
      .map(Animal::getLeg)
      .map(Leg::getToes)
      .map(Toes::getNumToes)
      .orElse(null);
  }

  static Toes getToesOrNull(Animal a) {
    return Optional.ofNullable(a)
      .map(Animal::getLeg)
      .map(Leg::getToes)
      .orElse(null);
  }

  static Leg getLegOrNull(Animal a) {
    return Optional.ofNullable(a)
      .map(Animal::getLeg)
      .orElse(null);
  }

  // Main function:
  public static void main(String[] args) {
    // Trying to access 'numToes':
    System.out.println(getNumToesOrNull(new Animal(new Leg(new Toes(4))))); // 4
    System.out.println(getNumToesOrNull(new Animal(new Leg(new Toes(null))))); // null
    System.out.println(getNumToesOrNull(new Animal(new Leg(null)))); // null
    System.out.println(getNumToesOrNull(new Animal(null))); // null
    System.out.println(getNumToesOrNull(null)); // null

    // Trying to access 'toes':
    System.out.println(getToesOrNull(new Animal(new Leg(new Toes(4))))); // This is a collection of 4 toes
    System.out.println(getToesOrNull(new Animal(new Leg(new Toes(null))))); // This is a collection of no toes
    System.out.println(getToesOrNull(new Animal(new Leg(null)))); // null
    System.out.println(getToesOrNull(new Animal(null))); // null
    System.out.println(getToesOrNull(null)); // null

    // Trying to access 'leg':
    System.out.println(getLegOrNull(new Animal(new Leg(new Toes(4))))); // This is a leg with a collection of toes
    System.out.println(getLegOrNull(new Animal(new Leg(new Toes(null))))); // This is a leg with a collection of toes
    System.out.println(getLegOrNull(new Animal(new Leg(null)))); // This is a leg
    System.out.println(getLegOrNull(new Animal(null))); // null
    System.out.println(getLegOrNull(null)); // null
  }
}

1
投票

有一个编译器插件我写了一个操作AST来在编译时生成

Optional.ofNullable

它有一些限制,但确实减少了重复代码

PS:我是插件的作者


0
投票

为了完成雅各布的一点想法,我创建了这个类:

public class OptionalUtils {
  public static <T, U> Optional<U> elvisOperator(T t, Function<? super T, ? extends U> mapper) {
    return elvisOperator(Optional.ofNullable(t), mapper);
  }

  public static <T, U> Optional<U> elvisOperator(Optional<T> optional, Function<? super T, ? extends U> mapper) {
    return optional.flatMap(t -> Optional.ofNullable(mapper.apply(t)));
  }
}

那么你可以做

elvisOperator(elvisOperator(dataObject, DataObject::getNestedDataObject)
    NestedDataObject::getEvenMoreNestedDataObject))...

另一种流畅的方法:

import java.util.function.Function;
import java.util.Optional;

public class OptionalChaining<T> {
  private final Optional<T> t;

  public OptionalChaining(T t) {
    this.t=Optional.ofNullable(t);
  }
  public OptionalChaining(Optional<T> t) {
    this.t=t;
  }
  public <U> OptionalChaining<U> get(Function<T, U> function) {
    return new OptionalChaining(this.t
        .flatMap(e -> Optional.ofNullable(function.apply(e))));
  }
  public Optional<T> get() {
    return t;
  }
}

我们可以这样写:

new OptionalChaining<>(dataObject)
    .get(DataObject::getNestedDataObject)
    .get(NestedDataObject::getEvenMoreNestedDataObject)
    .get()

我很沮丧,因为在常见的 Java 库(例如 Guava、Langs、Vavr...)中找不到这个。唯一的方法似乎是在 Stream 上使用“或”运算符,但我发现它太冗长了:-(


-1
投票

模拟 elvis 操作员的另一个选项是使用以下辅助函数:

public static <T> T get(final Supplier<T> it) {
    try {
        return it.get();
    } catch (final NullPointerException e) {
        return null;
    }
}

你可以这样使用它:

var x = get(() -> thing.nullableMethod1(a).nullableMethod2(b).nullableMethod3());
© www.soinside.com 2019 - 2024. All rights reserved.