Optional
对象。在Java 8(样式1)之前
Employee employee = employeeServive.getEmployee();
if(employee!=null){
System.out.println(employee.getId());
}
在Java 8(样式2)之后,当服务本身返回可选值时,我看不到
Optional<Employee> employeeOptional = employeeService.getEmployee();
的附加值:来自Java在6个背景中,我认为样式1更清晰,代码行更少。我在这里没有真正的优势吗?
通过对所有答案的理解和博客的进一步研究得到巩固,
#1 楼
样式2不足以使用Java 8来获得全部好处。您根本不需要if ... use
。请参阅Oracle的示例。采纳他们的建议,我们会得到:样式3
// Changed EmployeeServive to return an optional, no more nulls!
Optional<Employee> employee = employeeServive.getEmployee();
employee.ifPresent(e -> System.out.println(e.getId()));
或更长的代码段
Optional<Employee> employee = employeeServive.getEmployee();
// Sometimes an Employee has forgotten to write an up-to-date timesheet
Optional<Timesheet> timesheet = employee.flatMap(Employee::askForCurrentTimesheet);
// We don't want to do the heavyweight action of creating a new estimate if it will just be discarded
client.bill(timesheet.orElseGet(EstimatedTimesheet::new));
评论
实际上,我们禁止使用isPresent的大多数用法(由代码审查捕获)
– jk。
18年1月18日在16:05
@jk。确实,如果有一个过载void ifPresent(Consumer
– Caleth
18年1月18日在16:23
@Caleth:您可能对Java 9的ifPresentOrElse(Consumer <?super T>,Runnable)感兴趣。
– wchargin
18年1月19日在4:47
与Java 6相比,我发现这种Java 8风格有一个缺点:它愚弄了代码覆盖工具。使用if语句,它将显示您何时错过分支,而不是在此处。
–蒂埃里(Thierry)
18年1月19日在9:10
@Aaron“大大降低了代码的可读性”。哪一部分完全降低了可读性?听起来更像是“我一直做事都不同,我不想改变自己的习惯”,而不是客观的推理。
– Voo
18年1月20日在12:35
#2 楼
如果将Optional
用作可能仍返回null
的较旧API之间的“兼容性”层,则在确定您拥有某些东西的最新阶段创建(非空)Optional可能会有所帮助。例如,您在哪里写道:Optional<Employee> employeeOptional = Optional.ofNullable(employeeService.getEmployee());
if(employeeOptional.isPresent()){
Employee employeeOptional= employeeOptional.get();
System.out.println(employee.getId());
}
我会选择:
Optional.of(employeeService) // definitely have the service
.map(EmployeeService::getEmployee) // getEmployee() might return null
.map(Employee::getId) // get ID from employee if there is one
.ifPresent(System.out::println); // and if there is an ID, print it
重点是您知道这里有一个非空的员工服务,因此您可以将其包装在带有
Optional
的Optional.of()
中。然后,当您致电getEmployee()
时,您可能会或可能不会雇用一名雇员。该雇员可能(或可能没有)ID。然后,如果您以ID结尾,则要打印它。无需在此代码中显式检查任何null,状态等。
评论
使用(...)。ifPresent(aMethod)优于if((...)。isPresent()){aMethod(...); }?似乎只是另一种语法可以执行几乎相同的操作。
–俄罗斯
18年1月20日在17:39
@Ruslan当您已经拥有一种适用于所有情况的通用解决方案时,为什么要对一个特定的调用使用一种特殊的语法?我们也在这里应用来自map和co的相同想法。
– Voo
18年1月20日在17:44
@Ruslan ... API返回null而不是空集合或数组。例如,您可以执行类似的操作(假设某个Parent类的getChildren()返回一组孩子,如果没有孩子,则返回null):Set
–约书亚·泰勒(Joshua Taylor)
18年1月20日在17:55
@Ruslan ...被委派给标准库中经过尝试的真实代码。这减少了我作为程序员必须编写,测试,调试等的代码量。
–约书亚·泰勒(Joshua Taylor)
18年1月20日在17:56
很高兴我正在使用Swift。如果让employee = employeeService.employee {print(employee.Id); }
– gnasher729
18年1月20日在20:01
#3 楼
Optional
为单个值,几乎没有附加值。如您所见,这只是用检查存在性来代替对null
的检查。拥有
Collection
这样的东西具有巨大的附加价值。引入的所有用于替换旧式低级循环的流方法都可以理解Optional
的事情并对其进行正确的处理(不对其进行处理或最终返回另一个未设置的Optional
)。如果您处理n个项目的频率比处理单个项目的频率高(所有现实程序中的99%都要处理),那么对可选内容的内置支持是一项巨大的进步。在理想情况下,您无需检查null
或存在。评论
除了集合/流之外,Optional还可以使API更具自记录性,例如Employee getEmployee()与Optional
–阿蒙
18年1月18日在15:01
单个值的可选项中有很多值。能够始终无需检查null就能使用.map()很棒。例如,Optional.of(service).map(Service :: getEmployee).map(Employee :: getId).ifPresent(this :: submitIdForReview);。
–约书亚·泰勒(Joshua Taylor)
18年1月18日在19:41
我不同意您最初没有附加值的评论。可选为您的代码提供上下文。快速浏览可以告诉您正确的功能,并且涵盖所有情况。鉴于使用null检查,是否应该对每个方法的所有单个Reference类型进行空检查?类似的情况可以应用于Class和public / private修饰符;它们会为您的代码添加零值,对吧?
– ArT
18年1月19日在4:18
@JoshuaTaylor老实说,与该表达式相比,我更喜欢老式检查是否为null。
–基连·福斯
18年1月19日在9:41
即使具有单个值的Optional的另一个大优点是,您不应忘记在应该执行时检查null。否则很容易忘记检查并最终导致NullPointerException ...
– Sean Burton
18年1月19日在15:35
#4 楼
只要您像使用Optional
的高级API一样使用isNotNull()
,那么是的,仅检查null
就不会发现任何区别。您不应该将
Optional
用于仅使用
Optional
来检查值是否存在错误代码:// BAD CODE ™ -- just check getEmployee() != null
Optional<Employee> employeeOptional = Optional.ofNullable(employeeService.getEmployee());
if(employeeOptional.isPresent()) {
Employee employee = employeeOptional.get();
System.out.println(employee.getId());
}
应将
Optional
用于避免使用不返回值,可以在使用null返回API方法时在必要时生成一个值:
Employee employee = Optional.ofNullable(employeeService.getEmployee())
.orElseGet(Employee::new);
System.out.println(employee.getId());
或者,如果创建新Employee的成本太高:
Optional<Employee> employee = Optional.ofNullable(employeeService.getEmployee());
System.out.println(employee.map(Employee::getId).orElse("No employee found"));
另外,还请大家注意您的方法可能不会返回值(如果由于某种原因而无法返回上述默认值):
// Your code without Optional
public Employee getEmployee() {
return someCondition ? null : someEmployee;
}
// Someone else's code
Employee employee = getEmployee(); // compiler doesn't complain
// employee.get...() -> NPE awaiting to happen, devs criticizing your code
// Your code with Optional
public Optional<Employee> getEmployee() {
return someCondition ? Optional.empty() : Optional.of(someEmployee);
}
// Someone else's code
Employee employee = getEmployee(); // compiler complains about incompatible types
// Now devs either declare Optional<Employee>, or just do employee = getEmployee().get(), but do so consciously -- not your fault.
最后,在其他答案中已经解释了所有类似流的方法,尽管这些并不是您真正决定使用
Optional
的方法,而是利用某人提供的Optional
其他。评论
@Kapep确实。我们在/ r / java进行了辩论,我说:«我认为Optional是错失的机会。 “在这里,我做了一件新的Optional东西,所以您被迫检查空值。哦,顺便说一句,我向它添加了一个get()方法,因此您实际上不必检查任何东西; );)“。 (...)可选的“ THIS MIGHT BE NULL”霓虹灯很不错,但是它的get()方法的裸露却使它变得残废»。但是OP要求使用Optional的原因,而不是针对它的选择或设计缺陷。
– Walen
18年1月19日在9:40
Employee员工= Optional.ofNullable(employeeService.getEmployee());在您的第三个代码段中,类型是否应该为Optional
–查理·哈丁(Charlie Harding)
18年1月19日在12:55
@immibis以什么方式残废?使用get的主要原因是您是否必须与一些旧代码交互。我想不出在新代码中使用过get的许多正当理由。这就是说,与遗留代码进行交互时,通常别无选择,但仍然有些抱怨,get的存在会导致初学者编写不良代码。
– Voo
18年1月20日在12:48
@immibis强迫人们明确处理“如果除数为0会发生什么”肯定会减少这些错误并使错误更加明显。但这不是我的意思:编写新代码时,我根本想不出要使用get的单个示例。因此,如果忽略旧版代码,那么您想到的是什么代码,如果没有获取将破坏接口呢?
– Voo
18年1月22日在7:25
@Voo Map是每个人都熟悉的示例。当然,由于向后兼容性,它们不能使Map.get返回Optional,但是您可以轻松地想象另一个没有此类兼容性约束的类似Map的类。说出类UserRepository {Optional
–user253751
18年1月25日在21:29
#5 楼
当开发人员需要检查函数是否返回值时,使用Optional的关键是要保持清楚。//service 1
Optional<Employee> employeeOptional = employeeService.getEmployee();
if(employeeOptional.isPresent()){
Employee employeeOptional= employeeOptional.get();
System.out.println(employee.getId());
}
//service 2
Employee employee = employeeService.getEmployeeXTPO();
System.out.println(employee.getId());
评论
令我感到困惑的是,所有带有可选功能的漂亮功能性东西,尽管确实很不错,但在这里比这要重要得多。在Java 8之前,您必须深入研究Javadoc以了解是否必须对调用的响应进行空检查。发布Java 8后,您将从返回类型中知道是否可以“一无所获”。
–嘘
18年1月19日在7:02
好吧,这里有一个“旧代码”问题,事情仍然可能返回null……但是,是的,能够从签名中分辨出来是很棒的。
– HaakonLøtveit
18年1月19日在13:50
是的,它在其中的可选
–杜蕾尔
18年1月20日在10:03
#6 楼
您的主要错误是您仍在使用更多程序性术语进行思考。这并不意味着对您作为一个人的批评,仅是一种观察。用更多的功能性思维思考需要时间和实践,因此方法是现成的,看起来像是最明显的正确事物需要您的配合。您的第二个小错误是在方法内部创建Optional。 Optional旨在帮助记录某些内容可能会或可能不会返回值的情况。您可能什么也不会得到。当然,问题很快就会变成“为什么isPresent甚至到达那里?”这里很多人想念的一件事是isPresent()并不是人们编写的新代码产生的充分了解了有用的lambda的功能,以及谁喜欢它的功能。
它的确为我们带来了几(两)个好,好,富有魅力的好处? />
简化了旧代码使用新功能的过渡。
简化了Optional的学习过程。
第一个很简单。
假设您有一个看起来像这样的API:
public interface SnickersCounter {
/**
* Provides a proper count of how many snickers have been consumed in total.
*/
public SnickersCount howManySnickersHaveBeenEaten();
/**
* returns the last snickers eaten.<br>
* If no snickers have been eaten null is returned for contrived reasons.
*/
public Snickers lastConsumedSnickers();
}
并且您有一个使用此类的旧类(填空) :
Snickers lastSnickers = snickersCounter.lastConsumedSnickers();
if(null == lastSnickers) {
throw new NoSuchSnickersException();
}
else {
consumer.giveDiabetes(lastSnickers);
}
可以肯定的例子。但是请允许我在这里。
Java 8现在已经启动,我们正努力加入。
所以我们要做的事情之一就是我们要用某种东西来替换旧界面。会返回Optional。
为什么?
因为其他人已经很亲切地提到过:
这使人们不必猜测某些内容是否可以为空
其他人已经指出了这一点。但是现在我们有一个问题。想象我们有(对不起,当我在一个无辜的方法上按下alt + F7时),在经过良好测试的旧代码中调用此方法的位置有46个,否则它将发挥出色的作用。现在您必须更新所有这些。
这就是isPresent发光的地方。
因为现在:
Snickers lastSnickers = snickersCounter.lastConsumedSnickers();
if(null == lastSnickers){
抛出新的NoSuchSnickersException();
}
else {
Consumer.giveDiabetes(lastSnickers);
}
变成:
Optional<Snickers> lastSnickers = snickersCounter.lastConsumedSnickers();
if(!lastSnickers.isPresent()) {
throw new NoSuchSnickersException();
}
else {
consumer.giveDiabetes(lastSnickers.get());
}
这是一个简单的更改,您可以给新任初级人员:他可以做一些有用的事情,并且他将继续探索代码库在同一时间。双赢。毕竟,类似于这种模式的东西非常普遍。现在,您不必使用lambda或其他任何东西就可以重写代码。
(在这种情况下,这是微不足道的,但是我想起了一些示例,这些示例对于读者来说是很困难的练习。)
请注意,这意味着您执行此操作本质上是一种无需处理昂贵的重写即可处理遗留代码的方法。那么新代码呢?
好吧,在您的情况下,您只想打印一些内容,只需执行以下操作:
这很简单,而且非常清晰。
然后慢慢冒出表面的一点是,存在get()的用例)和isPresent()。它们可以让您机械地修改现有代码,以使用较新的类型,而不必考虑太多。
因此,您在做什么,在以下方面会被误导:
您正在调用可能返回null的方法。正确的想法是该方法返回null。
您正在使用旧的创可贴方法来处理此可选方法,而不是使用包含lambda幻想的美味新方法。您应该做的很简单:
new Optional.ofNullable(employeeServive.getEmployee())
.map(Employee::getId)
.ifPresent(System.out::println);
当然,它的漂亮版本看起来像:
employeeService.getEmployee()
.map(Employee::getId)
.ifPresent(System.out::println);
顺便说一下,虽然它不是必需的,但我还是建议您在每次操作中使用新行,以便于阅读。
在一周的任何一天都易于阅读和理解节拍的简洁性。
这当然是一个非常简单的示例,在此示例中很容易理解我们正在尝试做的所有事情。在现实生活中并不总是那么简单。但是请注意,在此示例中,我们所表达的是我们的意图。我们想要获取员工,获取其ID,并在可能的情况下进行打印。这是Optional的第二大胜利。它使我们可以创建更清晰的代码。我也确实认为,做类似做一种方法的事情通常是个好主意,例如,做一个可以处理很多事情的方法,以便可以将其输入到地图中。
评论
这里的问题是,您试图使它们听起来像这些功能版本与旧版本一样易读,甚至在某些情况下,甚至说一个示例“完全清楚”。事实并非如此。这个例子很清楚,但并非完全如此,因为它需要更多的思考。 map(x).ifPresent(f)根本没有if(o!= null)f(x)那样直观的感觉。 if版本非常清楚。这是人们跳上流行的乐队的另一例,而不是真正的进步。
–亚伦
18年1月19日在20:36
注意,我并不是说使用函数式编程或使用Optional的好处为零。在此特定情况下,我仅指的是使用可选,而在可读性方面则更是如此,因为多个人的行为像这种格式比if具有相同或更多的可读性。
–亚伦
18年1月19日在20:40
这取决于我们的清晰度概念。尽管您确实提出了很好的观点,但我还要指出,对于许多人来说,lambda在某些时候是新奇的。我敢于打赌一个互联网cookie,很多人说它是isPresent,并得到令人愉快的安慰:他们现在可以继续更新旧代码,然后再学习lambda语法。另一方面,具有Lisp,Haskell或类似语言经验的人可能很高兴他们现在可以将熟悉的模式应用于Java问题...
– HaakonLøtveit
18年1月22日12:00
@Aaron-清晰度不是Optional类型的重点。我个人发现它们不会降低清晰度,并且在某些情况下(尽管不是这种情况)确实会提高清晰度,但是关键的好处是(只要避免使用isPresent并获得尽可能多的实际效果即可) )它们提高了安全性。如果类型是Optional
–法律
18 Mar 5 '18 at 16:52
即使您立即取出这些值,只要不使用get,就必须选择在出现缺失值时该怎么做:您可以使用orElse()(或orElseGet())来提供default或orElseThrow()引发一个记录了问题性质的选定异常,它比一般的NPE更好,后者在您深入研究堆栈跟踪之前是没有意义的。
–法律
18 Mar 5 '18 at 16:52
#7 楼
我看不到样式2的好处。您仍然需要认识到需要空检查,现在它更大了,因此可读性更差。 .getEmployee()将返回Optional,然后代码将变为:以某种方式处理可选值。并且,如果整个代码库中的方法从不返回null(或者几乎没有团队例外的规则),则它将增加针对NPE的安全性。
评论
当您使用isPresent()时,Optional变得毫无意义。我们使用可选的,所以我们不必测试。
–candied_orange
18年1月18日在15:53
就像其他人所说的,不要使用isPresent()。这是错误的选择方法。请改用map或flatMap。
– Andres F.
18年1月18日在19:45
@CandiedOrange然而,最受好评的答案(说使用ifPresent)只是测试的另一种方式。
–user253751
18年1月19日在4:44
@CandiedOrange ifPresent(x)与if(present){x}一样是测试的一部分。返回值无关紧要。
–user253751
18年1月19日在4:58
@CandiedOrange尽管如此,您最初说的是“所以我们不必测试”。实际上,当您仍在测试……您不能说“所以我们不必测试”。
–亚伦
18年1月19日在20:44
#8 楼
我认为最大的好处是,如果您的方法签名返回一个Employee
,则无需检查null。您知道通过该签名可以保证遣返员工。您无需处理失败案例。使用可选件,您知道自己会做。 在代码中,我看到过空检查永远不会失败,因为人们不想跟踪代码来确定是否可能存在空,因此他们会在所有防御性位置抛出空检查。这会使代码变慢一些,但更重要的是,它会使代码更嘈杂。
但是,要使其正常工作,您需要一致地应用此模式。
评论
实现此目的的更简单方法是将@Nullable和@ReturnValuesAreNonnullByDefault与静态分析一起使用。
– maaartinus
18年1月19日在8:07
@maaartinus在装饰器注释中以静态分析和文档的形式添加其他工具,是否比编译时API支持更简单?
–雪橇
18年1月23日在4:14
实际上,添加其他工具确实比较简单,因为它不需要更改代码。而且,无论如何,您都想拥有它,因为它也捕获了其他错误。 +++我编写了一个简单的脚本,将带有@ReturnValuesAreNonnullByDefault的package-info.java添加到我的所有包中,所以我要做的就是在必须切换到Optional的地方添加@Nullable。这意味着更少的字符,没有添加的垃圾,也没有运行时开销。将鼠标悬停在该方法上时,不必查看它显示的文档。静态分析工具可以捕获错误以及以后的可空性更改。
– maaartinus
18年1月23日在19:45
我对Optional的最大担心是,它添加了处理空性的另一种方法。如果从一开始就使用Java,就可以了,但是现在只是痛苦。恕我直言,走科特林的方式要好得多。 +++请注意,List <@Nullable String>仍然是字符串列表,而List
– maaartinus
18年1月23日在19:47
#9 楼
我的答案是:只是不要。至少要三思而后行,这是否真的是一种改进。Optional.of(employeeService) // definitely have the service
.map(EmployeeService::getEmployee) // getEmployee() might return null
.map(Employee::getId) // get ID from employee if there is one
.ifPresent(System.out::println); // and if there is an ID, print it
这样的表达式(从另一个答案中获取)似乎是正确的处理本可为空的返回方法的功能方法。它看起来不错,它永远不会抛出,也永远不会让您考虑处理“缺席”案件。它看起来比旧方法更好。
Employee employee = employeeService.getEmployee();
if (employee != null) {
ID id = employee.getId();
if (id != null) {
System.out.println(id);
}
}
,这显然可以存在
else
子句。 功能样式
看起来完全不同(对于不习惯它的人来说是不可读的)
调试起来很痛苦
可以不能轻易扩展以处理“缺席”案例(
orElse
并不总是足够的)误导了忽略“缺席”案例
在任何情况下都不应将其用作灵丹妙药。如果它是通用的,则应将
.
运算符的语义替换为?.
运算符的语义,将NPE遗忘,并忽略所有问题。虽然是Java迷,我必须说,函数风格只是Java人的一个可怜的人,用它来代替其他语言众所周知的语句。
employeeService
.getEmployee()
?.getId()
?.apply(id => System.out.println(id));
我们是否同意该语句(真的)不起作用?
与旧代码非常相似,只是简单得多?
更多比功能样式更具可读性?
调试器友好吗?
评论
您似乎在用语言功能描述C#对这一概念的实现,这与Java对核心库类的实现完全不同。他们对我来说看起来一样
– Caleth
18 Mar 5 '18 at 9:31
@Caleth没办法。我们可以谈论“简化null安全评估的概念”,但是我不会将其称为“概念”。可以说Java使用核心库类实现了相同的事情,但这只是一团糟。只需从employeeService.getEmployee()。getId()。apply(id => System.out.println(id))开始;并使用“安全”导航操作符和“可选”将其设置为null安全。当然,我们可以称其为“实现细节”,但是实现很重要。在某些情况下,lamda使代码更简单,更好,但在这里,这只是一个丑陋的滥用。
– maaartinus
18 Mar 5 '18 at 12:53
库类:Optional.of(employeeService).map(EmployeeService :: getEmployee).map(Employee :: getId).ifPresent(System.out :: println);语言功能:employeeService.getEmployee()?. getId()?. apply(id => System.out.println(id));。两种语法,但它们最终看起来与我非常相似。而“概念”是可选aka可空aka也许
– Caleth
18-3-5的13:00
我不明白您为什么认为其中之一是“丑陋的虐待”,而另一个是好的。我能理解您认为“丑陋的虐待”或两者都是好的。
– Caleth
18 Mar 5 '18 at 13:07
@Caleth的不同之处在于,您不必重写两个问号,而必须全部重写。问题不在于语言功能和库类代码是否相差很多。没关系。问题在于原始代码和库类代码相差很多。相同的想法,不同的代码。这太糟糕了。 +++被滥用的是处理空性的lamda。 Lamdas很好,但是Optional不是。可空性仅需要像Kotlin中那样的适当语言支持。另一个问题:List
– maaartinus
18 Mar 5 '18在13:22
#10 楼
如果“ employeeService”本身为空,将会发生什么?在这两个代码段中,都会引发空指针异常。无需使用可选的方法就可以处理该异常:
if(employeeServive != null) {
Employee employee = employeeServive.getEmployee();
if(employee!=null){
System.out.println(employee.getId());
}
}
您会看到有多个if检查,并且可以将其扩展为具有长嵌套结构的长层次结构,例如employeeService.getEmployee()。getDepartment()。getName().... getXXX()。getYYY()等。
要处理这种情况,我们可以使用Optional类,例如:
Optional.ofNullable(employeeService)
.map(service -> service.getEmployee())
.map(employee -> employee.getId())
.ifPresent(System.out::println);
它可以处理任何大小的嵌套。
要全面了解Java Optional,请阅读有关该主题的文章:Java 8中的Optional
评论
Oracle上有一篇有关使用Optional的广泛文章。! employeeOptional.isPresent()似乎完全缺少选项类型。根据@MikePartridge的评论,不建议这样做或惯用法。明确检查选项类型是某物还是什么都不是禁忌。您应该将它们简单地映射或flatMap。
查看Oracle的Java语言架构师Brian Goetz关于Optional的Stack Overflow答案。
当您以“最佳做法”的名义关闭大脑时,就会发生这种情况。
那是可选用的相当差的用法,但是即使这样做也有好处。如果为null,则所有内容都可能为null,因此您需要不断地进行检查,可选地调用此特定变量可能会丢失,请确保对其进行检查