【问题标题】:Functional style of Java 8's Optional.ifPresent and if-not-Present?Java 8 的 Optional.ifPresent 和 if-not-Present 的功能风格?
【发布时间】:2014-07-09 11:53:50
【问题描述】:

在 Java 8 中,如果 Optional 对象存在,我想对它做一些事情,如果它不存在,我想做另一件事。

if (opt.isPresent()) {
  System.out.println("found");
} else {
  System.out.println("Not found");
}

不过,这不是“功能风格”。

Optional 有一个 ifPresent() 方法,但我无法链接 orElse() 方法。

因此,我不能写:

opt.ifPresent( x -> System.out.println("found " + x))
   .orElse( System.out.println("NOT FOUND"));

在回复@assylias 时,我认为Optional.map() 不适用于以下情况:

opt.map( o -> {
  System.out.println("while opt is present...");
  o.setProperty(xxx);
  dao.update(o);
  return null;
}).orElseGet( () -> {
  System.out.println("create new obj");
  dao.save(new obj);
  return null;
});

在这种情况下,当opt 存在时,我会更新其属性并保存到数据库中。当它不可用时,我创建一个新的obj 并保存到数据库中。

注意在两个 lambda 表达式中我必须返回 null

但是当 opt 存在时,两个 lambdas 都会被执行。 obj 将被更新,一个新对象将被保存到数据库中。这是因为第一个 lambda 中的 return null。而orElseGet() 会继续执行。

【问题讨论】:

  • 使用您的第一个样本。它是美丽的
  • 我建议您在使用不是为该行为设计的 API 时停止强制执行某些行为。你的第一个例子对我来说看起来不错,除了一些小风格的评论,但那些都是自以为是的。
  • @smallufo:将return null; 替换为return o;(两者)。但是,我有强烈的感觉,你在错误的地方工作。您应该在产生该Optional 的站点工作。在那个地方应该有一种方法可以在没有中间 Optional 的情况下执行所需的操作。
  • Java 9 为您的问题实现了解决方案:iteratrlearning.com/java9/2016/09/05/java9-optional.html
  • 我认为这不能轻易完成的原因是故意的。 Optional 不应该做流量控制,而是价值转换。我知道ifPresent 与此相矛盾。所有其他方法都引用值而不是操作。

标签: java functional-programming java-8 optional


【解决方案1】:

如果你使用的是Java 9+,你可以使用ifPresentOrElse()方法:

opt.ifPresentOrElse(
   value -> System.out.println("Found: " + value),
   () -> System.out.println("Not found")
);

【讨论】:

  • 很好,因为它几乎和 Scala 中的模式匹配一​​样干净
  • 两个这样的 lambda 实在是太丑了。我认为 if/else 对于这些情况来说更干净。
  • @john16384 好的,如果你觉得它难看,那么我会删除我的答案(否)。
  • 这很好,但这个问题是专门针对 JDK8 的,因为 ifPresentOrElse 不可用。
【解决方案2】:

@Dane White 的回答对我来说还可以,首先我不喜欢使用Runnable,但我找不到任何替代方案。

这里是我更喜欢的另一个实现:

public class OptionalConsumer<T> {
    private Optional<T> optional;

    private OptionalConsumer(Optional<T> optional) {
        this.optional = optional;
    }

    public static <T> OptionalConsumer<T> of(Optional<T> optional) {
        return new OptionalConsumer<>(optional);
    }

    public OptionalConsumer<T> ifPresent(Consumer<T> c) {
        optional.ifPresent(c);
        return this;
    }

    public OptionalConsumer<T> ifNotPresent(Runnable r) {
        if (!optional.isPresent()) {
            r.run();
        }
        return this;
    }
}

然后:

Optional<Any> o = Optional.of(...);
OptionalConsumer.of(o).ifPresent(s -> System.out.println("isPresent " + s))
                .ifNotPresent(() -> System.out.println("! isPresent"));

更新 1:

当您拥有价值并想要处理它时,上述解决方案适用于传统开发方式,但如果我想定义功能并执行将如何,请检查以下增强;

public class OptionalConsumer<T> implements Consumer<Optional<T>> {
private final Consumer<T> c;
private final Runnable r;

public OptionalConsumer(Consumer<T> c, Runnable r) {
    super();
    this.c = c;
    this.r = r;
}

public static <T> OptionalConsumer<T> of(Consumer<T> c, Runnable r) {
    return new OptionalConsumer(c, r);
}

@Override
public void accept(Optional<T> t) {
    if (t.isPresent()) {
        c.accept(t.get());
    }
    else {
        r.run();
    }
}

那么可以用作:

Consumer<Optional<Integer>> c = OptionalConsumer.of(
    System.out::println, 
    () -> System.out.println("Not fit")
);

IntStream.range(0, 100)
    .boxed()
    .map(i -> Optional.of(i)
    .filter(j -> j % 2 == 0))
    .forEach(c);

在这个新代码中你有 3 件事:

  1. 可以在对象存在之前轻松定义功能。
  2. 没有为每个 Optional 创建对象引用,只有一个,你的内存比 less GC 少。
  3. 它正在实现消费者以便更好地与其他组件一起使用。

顺便说一句,现在它的名称更具描述性,实际上是 Consumer>

【讨论】:

  • 应该使用 Optional.ofNullable(o) 而不是 Optional.of(o)
  • 如果你不确定你要使用的值是否为null并且不需要面对NPE,你需要使用ofNullable,如果你确定它不为null否则你不在乎是否得到 NPE。
  • 我认为 OptionalConsumer 类在代码中看起来比 if/else 更好。谢谢! :)
  • 小心使用可选参数作为构造函数参数。如果使用 null 作为输入,则可能会再次出现空指针异常,这首先会破坏使用选项。
  • 在这种情况下,您可以使用 optional.ofNullable(),这必须在上面的示例中的 OptionalConsumer 之外处理,我只是使用 Optional.of(i),因为我确定该值不会为 null但在其他情况下,如果您可以将值设为 null,则必须使用 optional.ofNullable()
【解决方案3】:

Java 9 引入

ifPresentOrElse 如果存在值,则使用该值执行给定的操作,否则执行给定的基于空的操作。

见优秀Optional in Java 8 cheat sheet

它为大多数用例提供了所有答案。

下面的简短摘要

ifPresent() - 设置 Optional 时做某事

opt.ifPresent(x -> print(x)); 
opt.ifPresent(this::print);

filter() - 拒绝(过滤掉)某些可选值。

opt.filter(x -> x.contains("ab")).ifPresent(this::print);

map() - 如果存在则转换值

opt.map(String::trim).filter(t -> t.length() > 1).ifPresent(this::print);

orElse()/orElseGet() - 转为空 Optional to default T

int len = opt.map(String::length).orElse(-1);
int len = opt.
    map(String::length).
    orElseGet(() -> slowDefault());     //orElseGet(this::slowDefault)

orElseThrow() - 在空 Optional 上懒惰地抛出异常

opt.
filter(s -> !s.isEmpty()).
map(s -> s.charAt(0)).
orElseThrow(IllegalArgumentException::new);

【讨论】:

  • 这实际上并没有回答 OP 的问题。它回答了许多常见用途,但不是 OP 所要求的。
  • @CaptainMan 实际上是这样; opt.map("found").orElse("not found") 表达式填补了账单。
  • @Matt no,OP 专门要求他在可选存在/不存在时执行操作,而不是在存在或不存在时返回值。 OP 甚至在使用 orElseGet 的问题中提到了类似的东西,解释了为什么它不起作用。
  • @CaptainMan 我明白你的意思。我确实认为如果他没有从map 返回null,他可以让它工作,但是要求一个功能性的解决方案有点奇怪,这样你就可以调用DAO。在我看来,从这个 map.orElse 块返回更新的/新的对象,然后对返回的对象做你需要做的事情会更有意义。
  • 我认为map 专注于流本身,而不是用于“根据流中此元素的状态对另一个对象执行操作”。很高兴知道ifPresentOrElse 是在 Java 9 中添加的。
【解决方案4】:

另一种选择是:

System.out.println(opt.map(o -> "Found")
                      .orElse("Not found"));

我认为它不会提高可读性。

或者按照 Marko 的建议,使用三元运算符:

System.out.println(opt.isPresent() ? "Found" : "Not found");

【讨论】:

  • 感谢@assylias,但我认为 Optional.map() 不适用于这种情况(请参阅我的上下文更新)。
  • @smallufo 您需要在您的第一个 lambda 中返回 new Object();,但老实说,这变得非常难看。对于您更新的示例,我会坚持使用 if/else。
  • 同意,使用map 只返回Optional 进行链接会使代码更难理解,而map 被假定为字面意义上的映射。
【解决方案5】:

另一种解决方案是使用高阶函数,如下所示

opt.<Runnable>map(value -> () -> System.out.println("Found " + value))
   .orElse(() -> System.out.println("Not Found"))
   .run();

【讨论】:

  • 在我看来,目前没有 JDK 9 的最佳解决方案。
  • 一个解释会很好。我在问自己,为什么需要使用可运行的地图 (?) 以及 value -&gt; () -&gt; syso 部分的含义。
  • 感谢您的解决方案!我认为使用 Runnable 的原因是 out map 不返回任何值,而使用 Runnable 它返回 lambda ,例如 map 的结果是 lambda 我们运行它。因此,如果您有返回值,您可以使用以下内容:String result = opt.map(value -&gt; "withOptional").orElse("without optional");
【解决方案6】:

没有一种开箱即用的好方法。如果您想定期使用更简洁的语法,那么您可以创建一个实用程序类来提供帮助:

public class OptionalEx {
    private boolean isPresent;

    private OptionalEx(boolean isPresent) {
        this.isPresent = isPresent;
    }

    public void orElse(Runnable runner) {
        if (!isPresent) {
            runner.run();
        }
    }

    public static <T> OptionalEx ifPresent(Optional<T> opt, Consumer<? super T> consumer) {
        if (opt.isPresent()) {
            consumer.accept(opt.get());
            return new OptionalEx(true);
        }
        return new OptionalEx(false);
    }
}

然后你可以在别处使用静态导入来获得接近你所追求的语法:

import static com.example.OptionalEx.ifPresent;

ifPresent(opt, x -> System.out.println("found " + x))
    .orElse(() -> System.out.println("NOT FOUND"));

【讨论】:

  • 谢谢。这个解决方案很漂亮。我知道可能没有内置解决方案(除非 JDK 包含这种方法)。您的 OptionalEx 非常有帮助。无论如何,谢谢。
  • 是的,我喜欢这个结果和它支持的风格。那么,为什么不在标准 API 中呢?
  • 好答案。我们也这样做。我同意它应该在 API(或语言!)中,但它已被拒绝:bugs.openjdk.java.net/browse/JDK-8057557
  • 不错。这应该是 JDK 8.1 的一部分以供考虑。
  • Optional.ifPresentOrElse() 已添加到 JDK 9。
【解决方案7】:

如果您只能使用 Java 8 或更低版本:

1) 如果您没有spring-data,目前最好的方法是:

opt.<Runnable>map(param -> () -> System.out.println(param))
      .orElse(() -> System.out.println("no-param-specified"))
      .run();

现在我知道它对某人来说不是那么可读,甚至难以理解,但对我个人来说看起来不错,我没有看到另一种很好的流畅方式。

2) 如果你足够幸运并且可以使用spring-data,最好的方法是 Optionals#ifPresentOrElse:

Optionals.ifPresentOrElse(opt, System.out::println,
      () -> System.out.println("no-param-specified"));

如果你可以使用 Java 9,你绝对应该使用:

opt.ifPresentOrElse(System.out::println,
      () -> System.out.println("no-param-specified"));

【讨论】:

    【解决方案8】:

    你不能在ifPresent 之后调用orElse,原因是orElse 是在一个可选参数上调用的,但ifPresent 返回无效。所以最好的实现方法是ifPresentOrElse。 可能是这样的:

    op.ifPresentOrElse( 
                (value) 
                    -> { System.out.println( 
                             "Value is present, its: "
                             + value); }, 
                () 
                    -> { System.out.println( 
                             "Value is empty"); }); 
    

    【讨论】:

      【解决方案9】:

      所描述的行为可以通过使用Vavr(以前称为 Javaslang)来实现,这是一个用于 Java 8+ 的对象函数库,它实现了大多数 Scala 结构(Scala 是一种更具表现力的语言,具有更丰富的类型系统建立在JVM上)。它是一个非常好的库,可以添加到您的 Java 项目中以编写纯函数式代码。

      Vavr 提供了 Option monad,它提供了使用 Option 类型的函数,例如:

      • fold:在两种情况下映射选项的值(定义/空)
      • onEmpty:允许在选项为空时执行Runnable
      • peek:允许使用选项的值(定义时)。
      • 它也是Serializable,与Optional相反,这意味着您可以安全地将其用作方法参数和实例成员。

      Option 遵循与 Java 的 Optional “伪monad”不同的 monad 法则,并提供更丰富的 API。当然,您可以通过 Java 的 Optional 来实现(反之亦然):Option.ofOptional(javaOptional) –Vavr 专注于互操作性。

      进入示例:

      // AWESOME Vavr functional collections (immutable for the gread good :)
      // fully convertible to Java's counterparts.
      final Map<String, String> map = Map("key1", "value1", "key2", "value2");
      
      final Option<String> opt = map.get("nonExistentKey"); // you're safe of null refs!
              
      final String result = opt.fold(
              () -> "Not found!!!",                // Option is None
              val -> "Found the value: " + val     // Option is Some(val)
      );
      

      此外,所有 Vavr 类型都可以转换为其 Java 对应类型,例如:Optional javaOptional = opt.toJava(),非常简单:) 当然转换也存在于其他方式中:Option option = Option.ofOptional(javaOptional)

      注意Vavr 提供了一个io.vavr.API 类,里面有很多方便的静态方法 =)

      进一步阅读

      Null reference, the billion dollar mistake

      注意这只是 Vavr 提供的一个非常小的例子(模式匹配、流,也就是惰性求值列表、单子类型、不可变集合……)。

      【讨论】:

        【解决方案10】:

        以下是另一种解决方案:

        这就是你如何使用它:

            final Opt<String> opt = Opt.of("I'm a cool text");
            opt.ifPresent()
                .apply(s -> System.out.printf("Text is: %s\n", s))
                .elseApply(() -> System.out.println("no text available"));
        

        或者如果你在相反的用例的情况下为真:

            final Opt<String> opt = Opt.of("This is the text");
            opt.ifNotPresent()
                .apply(() -> System.out.println("Not present"))
                .elseApply(t -> /*do something here*/);
        

        这是成分:

        1. Function 接口稍作修改,仅用于“elseApply”方法
        2. 可选增强功能
        3. 有点卷曲:-)

        “美容”增强的功能界面。

        @FunctionalInterface
        public interface Fkt<T, R> extends Function<T, R> {
        
            default R elseApply(final T t) {
                return this.apply(t);
            }
        
        }
        

        以及用于增强的可选包装类:

        public class Opt<T> {
        
            private final Optional<T> optional;
        
            private Opt(final Optional<T> theOptional) {
                this.optional = theOptional;
            }
            
            public static <T> Opt<T> of(final T value) {
                return new Opt<>(Optional.of(value));
            }
        
            public static <T> Opt<T> of(final Optional<T> optional) {
                return new Opt<>(optional);
            }
            
            public static <T> Opt<T> ofNullable(final T value) {
                return new Opt<>(Optional.ofNullable(value));
            }
            
            public static <T> Opt<T> empty() {
                return new Opt<>(Optional.empty());
            }
        
            private final BiFunction<Consumer<T>, Runnable, Void> ifPresent = (present, notPresent) -> {
                if (this.optional.isPresent()) {
                    present.accept(this.optional.get());
                } else {
                    notPresent.run();
                }
                return null;
            };
        
           private final BiFunction<Runnable, Consumer<T>, Void> ifNotPresent = (notPresent, present) -> {
                if (!this.optional.isPresent()) {
                    notPresent.run();
                } else {
                    present.accept(this.optional.get());
                }
                return null;
            };
        
            public Fkt<Consumer<T>, Fkt<Runnable, Void>> ifPresent() {
                return Opt.curry(this.ifPresent);
            }
        
            public Fkt<Runnable, Fkt<Consumer<T>, Void>> ifNotPresent() {
                return Opt.curry(this.ifNotPresent);
            }
        
            private static <X, Y, Z> Fkt<X, Fkt<Y, Z>> curry(final BiFunction<X, Y, Z> function) {
                return (final X x) -> (final Y y) -> function.apply(x, y);
            }
        }
        

        这应该可以解决问题,并且可以作为处理此类要求的基本模板。

        这里的基本思想如下。在非函数式编程世界中,您可能会实现一个采用两个参数的方法,其中第一个是一种可运行代码,如果值可用,则应执行该代码,另一个参数是可运行代码,以防万一值不可用。为了更好的可读性,您可以使用curring将两个参数的函数拆分为两个各一个参数的函数。这就是我在这里基本上所做的。

        提示:Opt 还提供了另一个用例,您希望执行一段代码以防万一值不可用。这也可以通过 Optional.filter.stuff 完成,但我发现这更具可读性。

        希望有帮助!

        其他信息:

        还有另一种使用柯里化表达“if then else”的方式:

        public static <X, Y> Function<Predicate<X>, Function<Function<X, Y>, Function<Function<X, Y>, Y>>> ifThenElse(X input) {
          return (final Predicate<X> pred) -> (final Function<X, Y> ifPresent) -> (final Function<X, Y> ifNotPresent) -> pred.test(input) ? ifPresent.apply(input) : ifNotPresent.apply(input);
        }
        

        这样可以说:

        final String result = ifThenElse("fancy")
          .apply(input -> input.contains("fancy")) /* test      */
          .apply(input -> input.toUpperCase())     /* if-case   */
          .apply(input -> input.toLowerCase());    /* else-case */
        

        【讨论】:

        • 你能说出什么不起作用吗?我再次对其进行了测试,对我来说它有效吗?
        • 请问,ckass Fkt 是在哪里定义的?最后但并非最不重要的是我有一个编译问题:错误:(35、17)java:变量可选可能没有被初始化
        • Fkt 在上面定义为接口。只需阅读整篇文章 :-)
        • 是的。我将 EFunction 接口更改为 Fkt 作为名称。有一个错字。感谢您的评论 :-) 抱歉。
        • 我不认为写这样的代码是个好主意......你应该尽可能使用 jdk 实用程序。
        【解决方案11】:

        这里的问题:

        optional
          .map(object -> {
            System.out.println("If present.");
            return null;
          })
          .orElseGet( () -> {
            System.out.println("If empty.");
            return null;
          });
        

        是不是map()把第一个函数返回的null转换成empty();然后它返回empty()。当它返回empty() 时,它会提示调用第二个函数。请注意,orElseGet() 不会将第二个函数返回的null 转换为empty(),因此它返回null

        查看map()的实现:

        public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
            Objects.requireNonNull(mapper);
            if (!isPresent())
                return empty();
            else {
                return Optional.ofNullable(mapper.apply(value));
            }
        }
        

        以及orElseGet()的实现:

        public T orElseGet(Supplier<? extends T> other) {
            return value != null ? value : other.get();
        }
        

        因此执行时:

        • 如果是optional.isPresent(),系统将打印If present.,然后是If empty.,表达式的计算结果为null。

        • 如果!optional.isPresent(),系统将打印If empty.,表达式的计算结果为null。


        如果提供给 map() 的函数返回 any 其他值 - any 其他值 - 代码将按照您的预期工作,提供给 map() 的函数正在执行 if isPresent() 和提供给 orElseGet() if !isPresent() 的函数:

        例如,这个:

        optional
          .map(data -> {
            System.out.println("If present.");
            return 0;
          })
          .orElseGet( () -> {
            System.out.println("If empty.");
            return 0;
          });
        

        执行时:

        • 如果optional.isPresent(),系统将打印If present.,表达式的计算结果为0。

        • 如果!optional.isPresent(),系统将打印If empty.,表达式的计算结果为0。

        如果您的具体情况,我建议您的 insertupdate 方法返回持久对象,或持久对象的 id,或类似有用的东西;那么你可以使用类似这样的代码:

        final Object persist = optional
          .map(object -> {
            System.out.println("If present.");
            return update(object);
          })
          .orElseGet( () -> {
            System.out.println("If empty.");
            return insert(new Object());
          });
        

        【讨论】:

          【解决方案12】:

          如果你想存储值:

          Pair.of<List<>, List<>> output = opt.map(details -> Pair.of(details.a, details.b))).orElseGet(() -> Pair.of(Collections.emptyList(), Collections.emptyList()));
          

          【讨论】:

          • 不要认为这对是最好的解决方案。出于这个原因,您可以创建一些 POJO。在 Java 15 中,它可以使用记录。
          【解决方案13】:

          假设您有一个列表并避免 isPresent() 问题(与选项相关),您可以使用 .iterator().hasNext() 检查是否不存在。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-10-18
            • 1970-01-01
            • 2018-09-20
            • 1970-01-01
            • 1970-01-01
            • 2011-06-27
            • 1970-01-01
            相关资源
            最近更新 更多