我现在是时候谈论处理NULL引用的软件工程中真正的“ Optional ”了。
托尼·霍尔(Tony Hoare)坦言,他发明了空(Null)犯了数十亿美元的错误。 如果您还没有看过他的演讲,那么我建议您看一下Null-References-The-Billion-Dollar-Mistake 。
我将分享一些null的反模式 ,以及如何使用Optional或MayBe之类的抽象方法解决它。
在此示例中,我们将使用可以包含一些空值的简单值对象。
public class Person {
final String firstName;
final String lastName;
final String email; // This can be null
final String phone; //This can be null
}
该值对象的电子邮件和电话号码可以为空值。
方案:电子邮件和电话号码上的联系人
不使用可选
第一次尝试将基于检查null,如下所示
//Not using optional
if (p.email != null) {
System.out.println("Sending email to " + p.email);
}
if (p.phone != null) {
System.out.println("Calling " + p.phone);
}
这就是多年来所做的。 具有收集结果的另一种常见模式。
List p = searchPersonById("100");
if (p.isEmpty()) {
System.out.println("No result");
} else {
System.out.println("Person" + p.get(0));
}
以错误的方式使用可选
Optional phone = contactNumber(p);
Optional email = email(p);
if (phone.isPresent()) {
System.out.println("Calling Phone " + phone.get());
}
if (email.isPresent()) {
System.out.println("Sending Email " + email.get());
}
这样做好一点,但是通过在代码中添加if / else块,将Optional的所有好处都抛弃了。
永远快乐可选
//Always Happy
Optional phone = contactNumber(p);
Optional email = email(p);
System.out.println("Calling Phone " + phone.get());
System.out.println("Sending Email " + email.get());
很高兴感到高兴,但是当您尝试使用Optional时,您所做的假设很大,或者您不需要Optional。
嵌套属性可选
在这种情况下,我们将扩展Person对象并添加Home属性。 并非每个人都可以拥有房屋,因此最好不要使用该房屋。 让我们看看在这种情况下联系人场景如何工作
//Nested Property
if (p.getHome() != null) {
System.out.println("Sending Postal mail " + p.getHome().address);
}
if (p.getHome() != null && p.getHome().getInsurance() != null) {
System.out.println("Sending Notification to insurance " + p.getHome().getInsurance().getAgency());
}
在这里,代码将具有大量嵌套的空检查变得越来越糟。
基于优先级的默认
对于这种情况,我们首先尝试通过家庭住址与他人联系,如果该人不可用,则请通过办公地点与他人联系。
//Address has priority , first home and then Office
if (p.home != null) {
System.out.println("Contacted at home address " + p.home.address);
return; // Magical return for early exit
}
if (p.office != null) {
System.out.println("Contacted at office address " + p.office.address);
return; // Magical return for early exit
}
这种类型的场景需要使用提前控制流来尽早返回,并使代码难以理解和维护。
这些是一些常见模式,其中未使用可选选项或使用了错误的方式。
可选使用方式
让我们看看一些使用可选的好方法。
根据领域知识使属性可选
使属性成为可选属性非常容易。
public Optional getEmail() {
return Optional.ofNullable(email);
}
public Optional getPhone() {
return Optional.ofNullable(phone);
}
是的,允许将其设为“可选”,没有人会为此而绞尽脑汁,并且可以毫无恐惧地随意这样做。 更改完成后,我们可以编写如下内容
//Use Optional
p.getEmail().ifPresent(email -> System.out.println("Sending email to " + email));
p.getPhone().ifPresent(phone -> System.out.println("Calling " + phone));
//Optional for Collection or Search type of request
Optional
它看起来很整洁,第一步代码没有显式的if else在应用层。
使用一些Optional功能
//Use IfPresent & other cool things
phone
.filter(number -> hasOptIn(number))
.ifPresent(number -> System.out.println("Calling Phone " + number));
email
.filter(m -> hasOptIn(m))
.ifPresent(m -> System.out.println("Sending Email " + m));
可选就像流,我们得到所有功能映射,过滤器等支持。在上面的例子中,我们在联系之前正在检查OptIn。
永远快乐可选
“get”不检查将导致周日午夜运行时错误,因此建议使用ifPresent
//Don't do this
System.out.println("Calling Phone " + phone.get());
System.out.println("Sending Email " + email.get());
//Use ifPresent to avoid runtime error
phone.ifPresent(contact -> System.out.println("Sending email to " + contact));
email.ifPresent(contact -> System.out.println("Calling " + contact));
嵌套可选
p.getHome().ifPresent(a -> System.out.println("Sending Postal mail " + a.address));
p.getHome()
.flatMap(Person.Home::getInsurance)
.ifPresent(a -> System.out.println("Sending Notification to insurance " + a.agency));
Flatmap执行魔术,并处理home的空检查和转换保险对象。
基于优先级的默认
//Address has priority , first home and then Office
Optional address = Stream
.of(person.getHome().map(Home::getAddress), person.getOffice().map(Office::getAddress))
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
address
.ifPresent(add -> System.out.println("Contacting at address " + add));
这个例子是取家庭和办公室地址,并选择第一个有发送通知价值的地址。这种特殊的模式避免了大量的嵌套循环。
其他分支
可选的有什么不好呢
内存间接寻址
没有序列化
翻译自: https://www.javacodegeeks.com/2020/03/hands-on-optional-value.html