Java函数式开发优雅的Optional空指针处理.docx
- 文档编号:7953124
- 上传时间:2023-01-27
- 格式:DOCX
- 页数:9
- 大小:20.54KB
Java函数式开发优雅的Optional空指针处理.docx
《Java函数式开发优雅的Optional空指针处理.docx》由会员分享,可在线阅读,更多相关《Java函数式开发优雅的Optional空指针处理.docx(9页珍藏版)》请在冰豆网上搜索。
Java函数式开发优雅的Optional空指针处理
空闲时会抽空学习同在jvm上运行的Groovy和Scala,发现他们对null的处理比早期版本Java慎重很多。
在Java8中,Optional为函数式编程的null处理给出了非常优雅的解决方案。
本文将说明长久以来Java中对null的蹩脚处理,然后介绍使用Optional来实现Java函数式编程。
那些年困扰着我们的null
在Java江湖流传着这样一个传说:
直到真正了解了空指针异常,才能算一名合格的Java开发人员。
在我们逼格闪闪的java码字符生涯中,每天都会遇到各种null的处理,像下面这样的代码可能我们每天都在反复编写:
if(null!
=obj1){
if(null!
=obje2){
//dosomething
}
}
稍微有点眼界javaer就去干一些稍有逼格的事,弄一个判断null的方法:
booleancheckNotNull(Objectobj){
returnnull==obj?
false:
true;
}
voiddo(){
if(checkNotNull(obj1)){
if(checkNotNull(obj2)){
//dosomething
}
}
}
然后,问题又来了:
如果一个null表示一个空字符串,那”"表示什么?
然后惯性思维告诉我们,”"和null不都是空字符串码?
索性就把判断空值升级了一下:
booleancheckNotBlank(Objectobj){
returnnull!
=obj&&!
"".equals(obj)?
true:
false;
}
voiddo(){
if(checkNotBlank(obj1)){
if(checkNotNull(obj2)){
//dosomething
}
}
}
有空的话各位可以看看目前项目中或者自己过往的代码,到底写了多少和上面类似的代码。
不知道你是否认真思考过一个问题:
一个null到底意味着什么?
1.浅显的认识——null当然表示“值不存在”。
2.对内存管理有点经验的理解——null表示内存没有被分配,指针指向了一个空地址。
3.稍微透彻点的认识——null可能表示某个地方处理有问题了,也可能表示某个值不存在。
4.被虐千万次的认识——哎哟,又一个NullPointerException异常,看来我得加一个if(null!
=value)了。
回忆一下,在咱们前面码字生涯中到底遇到过多少次java.lang.NullPointerException异常?
NullPointerException作为一个RuntimeException级别的异常不用显示捕获,若不小心处理我们经常会在生产日志中看到各种由NullPointerException引起的异常堆栈输出。
而且根据这个异常堆栈信息我们根本无法定位到导致问题的原因,因为并不是抛出NullPointerException的地方引发了这个问题。
我们得更深处去查询什么地方产生了这个null,而这个时候日志往往无法跟踪。
有时更悲剧的是,产生null值的地方往往不在我们自己的项目代码中。
这就存在一个更尴尬的事实——在我们调用各种良莠不齐第三方接口时,说不清某个接口在某种机缘巧合的情况下就会返回一个null……
回到前面对null的认知问题。
很多javaer认为null就是表示“什么都没有”或者“值不存在”。
按照这个惯性思维我们的代码逻辑就是:
你调用我的接口,按照你给我的参数返回对应的“值”,如果这条件没法找到对应的“值”,那我当然返回一个null给你表示没有“任何东西”了。
我们看看下面这个代码,用很传统很标准的Java编码风格编写:
classMyEntity{
intid;
Stringname;
StringgetName(){
returnname;
}
}
//main
publicclassTest{
publicstaticvoidmain(String[]args)
finalMyEntitymyEntity=getMyEntity(false);
System.out.println(myEntity.getName());
}
privategetMyEntity(booleanisSuc){
if(isSuc){
returnnewMyEntity();
}else{
returnnull;
}
}
}
这一段代码很简单,日常的业务代码肯定比这个复杂的多,但是实际上我们大量的Java编码都是按这种套路编写的,懂货的人一眼就可以看出最终肯定会抛出NullPointerException。
但是在我们编写业务代码时,很少会想到要处理这个可能会出现的null(也许API文档已经写得很清楚在某些情况下会返回null,但是你确保你会认真看完API文档后才开始写代码么?
),直到我们到了某个测试阶段,突然蹦出一个NullPointerException异常,我们才意识到原来我们得像下面这样加一个判断来搞定这个可能会返回的null值。
//main
publicclassTest{
publicstaticvoidmain(String[]args)
finalMyEntitymyEntity=getMyEntity(false);
if(null!
=myEntity){
System.out.println(myEntity.getName());
}else{
System.out.println("ERROR");
}
}
}
仔细想想过去这么些年,咱们是不是都这样干过来的?
如果直到测试阶段才能发现某些null导致的问题,那么现在问题就来了——在那些雍容繁杂、层次分明的业务代码中到底还有多少null没有被正确处理呢?
对于null的处理态度,往往可以看出一个项目的成熟和严谨程度。
比如Guava早在JDK1.6之前就给出了优雅的null处理方式,可见功底之深。
鬼魅一般的null阻碍我们进步
如果你是一位聚焦于传统面向对象开发的Javaer,或许你已经习惯了null带来的种种问题。
但是早在许多年前,大神就说了null这玩意就是个坑。
托尼.霍尔(你不知道这货是谁吗?
自己去查查吧)曾经说过:
“Icallitmybillion-dollarmistake.Itwastheinventionofthenullreferencein1965.Icouldn’tresistthetemptationtoputinanullreference,simplybecauseitwassoeasytoimplement.”(大意是:
“哥将发明null这事称为价值连城的错误。
因为在1965那个计算机的蛮荒时代,空引用太容易实现,让哥根本经不住诱惑发明了空指针这玩意。
”)。
然后,我们再看看null还会引入什么问题。
看看下面这个代码:
Stringaddress=person.getCountry().getProvince().getCity();
如果你玩过一些函数式语言(Haskell、Erlang、Clojure、Scala等等),上面这样是一种很自然的写法。
用Java当然也可以实现上面这样的编写方式。
但是为了完满的处理所有可能出现的null异常,我们不得不把这种优雅的函数编程范式改为这样:
if(person!
=null){
Countrycountry=person.getCountry();
if(country!
=null){
Provinceprovince=country.getProvince();
if(province!
=null){
address=province.getCity();
}
}
}
瞬间,高逼格的函数式编程Java8又回到了10年前。
这样一层一层的嵌套判断,增加代码量和不优雅还是小事。
更可能出现的情况是:
在大部分时间里,人们会忘记去判断这可能会出现的null,即使是写了多年代码的老人家也不例外。
上面这一段层层嵌套的null处理,也是传统Java长期被诟病的地方。
如果以Java早期版本作为你的启蒙语言,这种get->ifnull->return的臭毛病会影响你很长的时间(记得在某国外社区,这被称为:
面向entity开发)。
利用Optional实现Java函数式编程
好了,说了各种各样的毛病,然后我们可以进入新时代了。
早在推出JavaSE8版本之前,其他类似的函数式开发语言早就有自己的各种解决方案。
下面是Groovy的代码:
Stringversion=computer?
.getSoundcard()?
.getUSB()?
.getVersion():
"unkonwn";
Haskell用一个 Maybe类型类标识处理null值。
而号称多范式开发语言的Scala则提供了一个和Maybe差不多意思的Option[T],用来包裹处理null。
Java8引入了 java.util.Optional
先看看下面这个Java代码的例子:
publicclassTest{
publicstaticvoidmain(String[]args){
finalStringtext="Halloworld!
";
Optional.ofNullable(text)//显示创建一个Optional壳
.map(Test:
:
print)
.map(Test:
:
print)
.ifPresent(System.out:
:
println);
Optional.ofNullable(text)
.map(s->{
System.out.println(s);
returns.substring(6);
})
.map(s->null)//返回null
.ifPresent(System.out:
:
println);
}
//打印并截取str[5]之后的字符串
privatestaticStringprint(Stringstr){
System.out.println(str);
returnstr.substring(6);
}
}
//Consol输出
//num1:
Halloworld!
//num2:
world!
//num3:
//num4:
Halloworld!
(可以把上面的代码copy到你的IDE中运行,前提是必须安装了JDK8。
)
上面的代码中创建了2个Optional,实现的功能基本相同,都是使用Optional作为String的外壳对String进行截断处理。
当在处理过程中遇到null值时,就不再继续处理。
我们可以发现第二个Optional中出现s->null之后,后续的ifPresent不再执行。
注意观察输出的 //num3:
,这表示输出了一个”"字符,而不是一个null。
Optional提供了丰富的接口来处理各种情况,比如可以将代码修改为:
publicclassTest{
publicstaticvoidmain(String[]args){
finalStringtext="HalloWorld!
";
System.out.println(lowerCase(text));//方法一
lowerCase(null,System.out:
:
println);//方法二
}
privatestaticStringlowerCase(Stringstr){
returnOptional.ofNullable(str).map(s->s.toLowerCase()).map(s->s.replace("world","java")).orElse("NaN");
}
privatestaticvoidlowerCase(Stringstr,Consumer
consumer.accept(lowerCase(str));
}
}
//输出
//hallojava!
//NaN
这样,我们可以动态的处理一个字符串,如果在任何时候发现值为null,则使用orElse返回预设默认的“NaN”。
总的来说,我们可以将任何数据结构用Optional包裹起来,然后使用函数式的方式对他进行处理,而不必关心随时可能会出现的null。
我们看看前面提到的Person.getCountry().getProvince().getCity()怎么不用一堆if来处理。
第一种方法是不改变以前的entity:
importjava.util.Optional;
publicclassTest{
publicstaticvoidmain(String[]args){
System.out.println(Optional.ofNullable(newPerson())
.map(x->x.country)
.map(x->x.provinec)
.map(x->x.city)
.map(x->x.name)
.orElse("unkonwn"));
}
}
classPerson{
Countrycountry;
}
classCountry{
Provinceprovinec;
}
classProvince{
Citycity;
}
classCity{
Stringname;
}
这里用Optional作为每一次返回的外壳,如果有某个位置返回了null,则会直接得到”unkonwn”。
第二种办法是将所有的值都用Optional来定义:
importjava.util.Optional;
publicclassTest{
publicstaticvoidmain(String[]args){
System.out.println(newPerson()
.country.flatMap(x->x.provinec)
.flatMap(Province:
:
getCity)
.flatMap(x->x.name)
.orElse("unkonwn"));
}
}
classPerson{
Optional
}
classCountry{
Optional
}
classProvince{
Optional
Optional
:
returncity;
}
}
classCity{
Optional
}
第一种方法可以平滑的和已有的JavaBean、Entity或POJA整合,而无需改动什么,也能更轻松的整合到第三方接口中(例如spring的bean)。
建议目前还是以第一种Optional的使用方法为主,毕竟不是团队中每一个人都能理解每个get/set带着一个Optional的用意。
Optional还提供了一个filter方法用于过滤数据(实际上Java8里stream风格的接口都提供了filter方法)。
例如过去我们判断值存在并作出相应的处理:
if(Province!
=null){
Citycity=Province.getCity();
if(null!
=city&&"guangzhou".equals(getName()){
System.out.println(city.getName());
}else{
System.out.println("unkonwn");
}
}
现在我们可以修改为
Optional.ofNullable(province)
.map(x->x.city)
.filter(x->"guangzhou".equals(x.getName()))
.map(x->x.name)
.orElse("unkonw");
到此,利用Optional来进行函数式编程介绍完毕。
Optional除了上面提到的方法,还有orElseGet、orElseThrow等根据更多需要提供的方法。
orElseGet会因为出现null值抛出空指针异常,而orElseThrow会在出现null时,抛出一个使用者自定义的异常。
可以查看API文档来了解所有方法的细节。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Java 函数 开发 优雅 Optional 指针 处理