Java 8的Optional.ifPresent和if-not-Present功能样式?


274

在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存在,则两个lambda都将被执行。obj将会更新,并且新对象将保存到数据库。这是因为return null在第一个lambda中。并且orElseGet()将继续执行。


53
使用您的第一个样本。很漂亮
Sotirios Delimanolis 2014年

3
我建议您在使用非针对该行为的API时停止强迫某些行为。除了一些小的样式说明,您的第一个示例对我来说看起来不错,但这些建议还是可以接受的。
2014年

4
@smallufo:替换return null;return o;(两者)。但是,我强烈感觉到您在错误的地方工作。您应该在产生该结果的站点上工作Optional。在那个地方应该有一种无需中间步骤即可执行所需操作的方法Optional
Holger 2014年

10
Java 9实现了针对您的问题的解决方案:iteratrlearning.com/java9/2016/09/05/java9-optional.html
pisaruk

2
我认为这样做不容易的原因是有目的的。可选的不应该进行流量控制,而应该进行价值转换。我知道这是ifPresent矛盾的。所有其他方法都引用值而不是操作。
AlikElzin-kilaka

Answers:


109

对我来说@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);

在此新代码中,您需要执行三件事:

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

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


3
应该使用Optional.ofNullable(o)而不是Optional.of(o)
traeper

2
如果不确定要使用的值是否为null且不需要面对NPE,并且如果您确定该值不为null或不关心是否获取get,则需要使用ofNullable。 NPE。
巴瑟姆·雷达·佐迪

1
我认为OptionalConsumer类比代码中的if / else更好。谢谢!:)
witek1902

207

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

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

3
不错,因为它几乎与Scala中的模式匹配一​​样干净
sscarduzio

这样的两个lambda很丑陋。我认为在这些情况下,if / else更为干净。
john16384

1
@ john16384好吧,如果您觉得它很丑,那么我要删除我的答案(否)。
ZhekaKozlov

这非常好,但是该问题专门针对JDK8,因为ifPresentOrElse不可用。
hreinn

81

Java 9引入

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

请参阅Java 8备忘单中的优秀Optional

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

简短摘要如下

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()-变空默认T的可选

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

orElseThrow()-将异常懒惰地抛出为空

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

66
这实际上并不能回答OP的问题。它回答了很多常见用途,但没有OP的要求。
曼队长

1
@CaptainMan实际上做到了;opt.map(“ found”)。orElse(“ not found”)表达式可以满足要求。
马特(Matt)

4
@Matt不,OP专门要求在存在/不存在可选项时要执行的操作,不存在或不存在时不返回值。OP甚至在使用orElseGet的问题中提到了类似的内容,解释了为什么它不起作用。
曼队长

2
@CaptainMan我明白你的意思了。我确实认为,如果他不从中返回null,那么他可以使它工作map,但是请求功能性解决方案以便您可以调用DAO有点奇怪。在我看来,从该map.orElse块返回更新的/新的对象,然后对返回的对象执行您需要做的事情,会更有意义。
马特

1
我认为map关注流本身,而不是旨在“根据流中此元素的状态对另一个对象执行操作”。很ifPresentOrElse
高兴

53

一种替代方法是:

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

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

或如Marko建议的那样,使用三元运算符:

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

2
感谢@assylias,但是我不认为Optional.map()适用于这种情况(请参阅我的上下文更新)。
smallufo 2014年

2
@smallufo您需要返回new Object();第一个lambda,但是老实说这变得非常丑陋。对于您更新的示例,我会坚持使用if / else。
亚述2014年

同意,map过去只是返回Optional链接,这使代码更难于理解,而map假定它实际上是映射到某些东西。
Tiina

40

另一个解决方案是使用如下的高阶函数

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

8
在我眼中,迄今为止最好的解决方案是没有JDK9。–
Semaphor

5
一个解释会很好。我在问自己,为什么需要使用可运行的映射(?),以及value -> () -> syso部件的含义。
froehli

感谢您的解决方案!我认为使用Runnable的原因是out映射不返回任何值,而Runnable则返回lambda,例如map的结果是lambda,我们在之后运行它。因此,如果您具有回报价值,则可以使用以下方法:String result = opt.map(value -> "withOptional").orElse("without optional");
nanotexnik

21

开箱即用的方法不是很好的方法。如果要定期使用更简洁的语法,则可以创建一个实用程序类来提供帮助:

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非常有帮助。无论如何,谢谢。
smallufo 2014年

是的,我喜欢结果以及它支持的样式。那么,为什么不使用标准API?
guthrie 2014年

好答案。我们也一样。我同意应该使用API​​(或语言!),但已被拒绝:bugs.openjdk.java.net/browse/JDK-8057557
加勒特·史密斯

真好 这应该成为JDK 8.1的一部分以供考虑。
peter_pilgrim 2015年

35
Optional.ifPresentOrElse()已被添加到JDK 9.
斯图尔特标记

9

如果只能使用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"));

2

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

Vavr提供的Optionmonad提供与Option类型一起使用的功能,例如:

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

Option遵循monad法则,与Java的“ pseudo-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提供的功能的一个很小的例子(模式匹配,流又名懒惰求值列表,单子类型,不可变集合等)。


1

另一个解决方案可能如下:

这是您的用法:

    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"));

或者,如果您遇到相反的用例,则为true:

    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);
    }

}

以及用于增强功能的Optional包装器类:

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);
    }
}

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

这里的基本思想如下。在非功能风格的编程世界中,您可能会实现一个带有两个参数的方法,其中第一个是一种可运行的代码,如果有可用值,则应执行该参数;另一个参数是可运行的代码,如果该参数可用,则应运行该代码。值不可用。为了提高可读性,您可以使用curr将两个参数的功能拆分为每个参数一个的两个功能。这基本上就是我在这里所做的。

提示:Opt还提供了另一种用例,在这种情况下您想执行一段代码以防万一该值不可用。也可以通过Optional.filter.stuff完成,但是我发现它更具可读性。

希望有帮助!

好的编程:-)


你能告诉什么不起作用吗?我再次对其进行了测试,对我来说它正在工作吗?
亚历山德罗·朱萨

请问,但是在哪里定义了ckass Fkt?最后但并非最不重要的一点是,我有一个编译问题:错误:(
35,17

上面将Fkt定义为接口。只需阅读整篇文章:-)
亚历山德罗·朱萨

是。我将EFunction接口的名称更改为Fkt。有一个错字。感谢您的审查:-)对此表示抱歉。
亚历山德罗·朱萨

我认为编写这样的代码不是一个好主意……您应该尽可能使用jdk实用程序。
Tyulpan Tyulpan


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.