Java 8's可选的函数式风格。ifPresent和if-not-Present?

在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()将继续执行。

505947 次浏览

另一种选择是:

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

但我不认为它能提高可读性。

或者像Marko建议的那样,使用三元操作符:

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

没有一个很好的方法可以打破常规。如果你想在常规的基础上使用更干净的语法,那么你可以创建一个实用工具类来帮助你:

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

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

Then可用作:

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. 没有为每个可选对象创建对象引用,只有一个,所以内存比GC更少。
  3. 它正在实现消费者,以便更好地与其他组件一起使用。

顺便说一下,现在它的名字更具有描述性它实际上是Consumer<Optional<?>>

Java 9引入了

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

参见excellent 在Java 8备忘单中可选

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

下面是简短的总结

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() - turning empty可选,默认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);

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

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

另一个解决方案是:

下面是它的用法:

    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. 稍微修改了函数界面,只是为了“elseApply”方法
  2. 可选增强
  3. 一点点卷曲:-)

“cosmetically"enhanced功能接口。

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

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

这里的基本思想是遵循。在非函数式编程世界中,你可能会实现一个带有两个参数的方法,其中第一个参数是一种可运行代码,在值可用时执行,另一个参数是可运行代码,在值不可用时运行。为了更好的可读性,可以使用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 */

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

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

如果你想存储这个值:

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

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

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

Vavr提供了Option单子,它提供了处理Option类型的函数,例如:

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

Option遵循单子法则,不同于Java的Optional "伪单子quot;并提供更丰富的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类,其中有很多方便的静态方法=)

进一步的阅读

空引用,十亿美元的错误

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

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

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

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

现在我知道对某些人来说,它不是那么可读,甚至很难理解,但对我个人来说,它看起来还不错,我想不出另一种流畅的方法来处理这种情况。

2),如果你足够幸运,你可以使用spring-data,最好的方法是 可选# ifPresentOrElse: < / p >

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"));
你不能在ifPresent之后调用orElse,原因是,orElse是在可选对象上调用的,但ifPresent返回void。所以最好的方法是ifPresentOrElse。 它可以是这样的:

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

问题是:

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(),因此null0返回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()的函数返回任何 other value - 任何 other value -代码将像你期望的那样工作,如果isPresent()则执行提供给map()的函数,如果!isPresent()则执行提供给orElseGet()的函数:

例如,这个:

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