当前位置: 首页 > 科技观察

巧用 Java 8 的 Optional 优雅的规避 NPE

时间:2023-03-18 13:49:33 科技观察

使用Java8的Optional优雅规避NPE如下图所示,本例中有如下代码:user.getAddress().getProvince();这种写法在用户为null时可能会报NullPointerException。为了解决这个问题,采用如下写法:if(user!=null){Addressaddress=user.getAddress();if(address!=null){Stringprovince=address.getProvince();}}比较丑,为了避免上面写的丑,让丑的设计变得优雅。JAVA8提供了Optional类来优化这种写法,下文会详细介绍。API介绍首先介绍一下API。与其他文章不同的是,本文采用类比的方式,并结合了源码。与其他文章不同的是,API是一一罗列的,让人找不到重点。1、Optional(Tvalue)、empty()、of(Tvalue)、ofNullable(Tvalue)这四个函数是相互关联的,所以存放在一个组中。先说明一下,Optional(Tvalue),即构造函数,具有私有权限,不能被外部调用。其余三个函数是公有权限供我们调用。那么,Optional的本质就是里面存放了一个真实的值,构造的时候直接判断它的值是否为空。嗯,还是比较抽象。直接进入Optional(Tvalue)构造函数的源码,如下图所示。那么,of(Tvalue)的源码如下:publicstaticOptionalof(Tvalue){returnnewOptional<>(value);}也就是说里面调用了构造函数of(T值)函数。根据构造函数的源码,我们可以得出两个结论:通过of(Tvalue)函数构造的Optional对象,在Value值为空时,依然会报NullPointerException。of(Tvalue)函数构造的Optional对象在Value值不为空的情况下可以正常构造Optional对象。另外,Optional类还维护了一个值为null的对象,大概是这样的:publicfinalclassOptional{//Omit....privatestaticfinalOptionalEMPTY=newOptional<>();privateOptional(){this.value=null;}//省略...publicstaticOptionalempty(){@SuppressWarnings("unchecked")Optionalt=(Optional)EMPTY;返回吨;}}然后,empty()的作用就是返回EMPTY对象。好了,铺垫了这么多,可以说ofNullable(Tvalue)起作用了,下面是源码:publicstaticOptionalofNullable(Tvalue){returnvalue==null?empty():of(value);}嗯,大家应该明白什么意思了吧。与of(Tvalue)相比,不同的是当value为null时,of(Tvalue)会报NullPointerException;ofNullable(Tvalue)不会抛出Exception,ofNullable(Tvalue)会直接返回一个EMPTY对象。那是不是意味着我们在项目中只使用ofNullable函数,而不使用of函数呢?不,事物存在,自然就有存在的价值。当我们运行时,我们不想隐藏NullPointerException。相反,立即报告,在这种情况下使用Of函数。但不得不承认,这样的场景真的很少见。博主只在写junit测试用例的时候用过这个功能。2、orElse(Tother)、orElseGet(Supplierother)、orElseThrow(SupplierexceptionSupplier)这三个函数存储在一个组中,当构造函数传入的值为null时调用。orElse和orElseGet的用法如下,当值为null时相当于给定一个默认值:@Testpublicvoidtest(){Useruser=null;user=Optional.ofNullable(user).orElse(createUser());user=Optional.ofNullable(user).orElseGet(()->createUser());}publicUsercreateUser(){Useruser=newUser();user.setName("张三");returnuser;}这两个函数的区别:当user值不为null时,orElse函数还是会执行createUser()方法,但是orElseGet函数不会执行createUser()方法,可以测试它自己。至于orElseThrow,当值为null时,直接抛出异常。用法如下:Useruser=null;Optional.ofNullable(user).orElseThrow(()->newException("用户不存在"));3、map(Functionmapper)和flatMap(Function>mapper)这两个函数放在一组内存中,这两个函数就是用来进行值转换的。直接上源码:publicfinalclassOptional{//省略....publicOptionalmap(Functionmapper){Objects.requireNonNull(mapper);如果(!isPresent())返回空();else{returnOptional.ofNullable(mapper.apply(value));}}//省略...publicOptionalflatMap(Function>mapper){Objects.requireNonNull(mapper);如果(!isPresent())返回空();else{returnObjects.requireNonNull(mapper.apply(value));}}}这两个函数,在函数体上没有区别。唯一的区别是输入参数。map函数接受的入参类型为Function,而flapMap的入参类型是Function>。具体用法上,对于map:如果User结构如下:publicclassUser{privateStringname;publicStringgetName(){返回名称;}}此时获取名字的方式如下:Stringcity=Optional.ofNullable(user).map(u->u.getName()).get();对于flatMap:如果User结构如下:publicclassUser{privateStringname;publicOptionalgetName(){returnOptional.ofNullable(name);}}此时获取名字的方式如下:Stringcity=Optional.ofNullable(user).flatMap(u->u.getName()).get();4两个函数,isPresent()和ifPresent(Consumerconsumer),一起记忆。isPresent是判断value是否为空,ifPresent是当value不为空时做一些操作。这两个函数的源码如下:publicfinalclassOptional{//Omit....publicbooleanisPresent(){returnvalue!=null;}//省略...publicvoidifPresent(Consumerconsumer){if(value!=null)consumer.accept(value);}}需要补充的是一定不能这样写:if(user!=null){//TODO:dosomething}as:Useruser=Optional.ofNullable(user);if(Optional.isPresent()){.//TODO:dosomething}因此,代码结构仍然很难看。后面博主会给出正确的写法。至于ifPresent(Consumerconsumer),用法也很简单,如下:Optional.ofNullable(user).ifPresent(u->{//TODO:dosomething});5.filter(Predicatepredicate)话不多说,直接上传源码:publicfinalclassOptional{//省略....Objects.requireNonNull(predicate);如果(!isPresent())返回这个;否则返回predicate.test(value)?this:empty();}filter方法接受一个Predicate来过滤Optional中包含的值,如果包含的值满足条件,则返回thisOptional;否则返回Optional.empty。用法如下:Optionaluser1=Optional.ofNullable(user).filter(u->u.getName().length()<6);如上图,如果用户名长度小于6,则返回。如果大于6,则返回EMPTY对象。实际使用示例1之前在函数方法中写了:publicStringgetCity(Useruser)throwsException{if(user!=null){if(user.getAddress()!=null){Addressaddress=user.getAddress();if(address.getCity()!=null){返回地址.getCity();}}}thrownewExcpetion("值错误");}JAVA8写法:publicStringgetCity(Useruser)throwsException{returnOptional.ofNullable(user).map(u->u.getAddress()).map(a->a.getCity()).orElseThrow(()->newException("Instructionfetcherror"));}例2对于例子,在主程序之前写:if(user!=null){dosomething(user);}JAVA8写:Optional.ofNullable(user).ifPresent(u->{dosomething(u);});之前的例子3写作:publicUsergetUser(Useruser)throwsException{if(user!=null){Stringname=user.getName();if("zhangsan".equals(name)){返回用户;}}else{user=newUser();用户.setName("张三");返回用户;}}java8写法:publicUsergetUser(Useruser){returnOptional.ofNullable(user).filter(u->"zhangsan".equals(u.getName())).orElseGet(()->{Useruser1=newUser();user1.setName("zhangsan");returnuser1;});}其他例子,就不一一列举了,但是用这个链式编程,虽然代码很优雅。但是逻辑不是那么明显,可读性降低了。大家可以根据自己项目中的情况使用。