我有一个经典的“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 运算符模式?
也许我忽略了一些东西,但是有什么原因你不能使用
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
。
在 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
}
}
为了完成雅各布的一点想法,我创建了这个类:
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 上使用“或”运算符,但我发现它太冗长了:-(
模拟 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());