【问题标题】:How to implement the Elvis operator in Java 8?如何在 Java 8 中实现 Elvis 运算符?
【发布时间】:2019-02-16 13:00:54
【问题描述】:

我有经典的“猫王操作员”案例,我正在调用每个可能返回 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 optional


    【解决方案1】:

    也许我忽略了一些东西,但是你有什么理由不能使用Optional#map

    以下示例不打印任何内容,因为Optional短路,如果Optional 中的值不存在(它是null 或@987654327 @ 为空),则视为空。

    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

    【讨论】:

    • 值得指向the documentation of Optional.map 以表明该行为是有意的:“如果映射函数返回null 结果,则此方法返回空Optional ”。
    • @Holger 和this 解释了为什么从长远来看,这是一个坏主意。或者,至少是次优的(从 FP 的角度来看)。
    • 他们实现了一个非常重要的功能,但出于某种原因,他们决定在此过程中严重损坏可信赖的旧地图。如果他们真的用这种行为将必要的第二种方法命名为“elvis”,那就更容易原谅了?
    【解决方案2】:

    在 Java 8 中,可以通过在 Optional.ofNullable(...) 上链接 .map(...) 调用并用 .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
      }
    }
    

    【讨论】:

    • 我认为您要添加到已接受的答案中的是,某些语言以这样一种方式模糊了可选性和可空性的概念,以至于可以使用 Elvis 运算符而无需在最后解包结果.在 Java 中,方法实现有时会模糊这些概念,但类型系统有不同的概念,所以是的,如果你想要一个 @Nullable Foo 而不是 Optional,你可能会想使用 orElse 在结束。
    • @Mickalot 是的,这就是我想要表达的观点。在我尝试这个例子之前,我已经看到了其他一些答案,并且对没有Optional 的情况下如何做到这一点感到困惑。我认为这可能会使其他人受益。
    【解决方案3】:

    我写过一个compiler plugin,它操纵AST在编译时生成Optional.ofNullable

    它几乎没有限制,但确实减少了重复代码

    PS:我是插件作者

    【讨论】:

      猜你喜欢
      • 2013-01-27
      • 1970-01-01
      • 1970-01-01
      • 2013-03-17
      • 2017-01-07
      • 1970-01-01
      • 1970-01-01
      • 2018-04-11
      相关资源
      最近更新 更多