当前位置: 首页 > Web前端 > HTML5

Java:如何更优雅地处理空值?

时间:2023-04-04 23:45:01 HTML5

在笔者几年的开发经验中,经常看到项目中到处都是空值判断。这些判断会让人感到困惑,它们的出现可能与当前的业务逻辑无关。.但这会让你很头疼。有时候,更可怕的是,系统会因为这些空值而抛出空指针异常,导致业务系统出现问题。在本文中,我总结了几种处理空值的方法,希望对读者有所帮助。在这里,小编建了一个前端学习交流按钮群:132667127,自己整理的最新前端资料和进阶开发教程。如果愿意,可以进群学习交流业务中的空值场景。有一个UserSearchService用来提供用户查询功能:publicinterfaceUserSearchService{ListlistUser();用户获取(整数id);}问题场景对于面向对象的语言,抽象层次尤为重要。尤其是接口的抽象,在设计和开发中所占的比重很大,我们希望在开发的时候尽可能的面向接口编程。从上面描述的接口方法可以推断,它可能包含以下两种含义:是我们定义接口的一个很好的指南,因此我们使用TDD作为开发代码的“驱动程序”。对于以上接口,我们在第一次使用TDD做测试用例的时候,发现了潜在的问题:如果listUser()没有数据,返回的是空集合还是null?get(Integerid)如果没有这个对象,是抛异常还是返回null?深入研究listUser让我们首先讨论listUser()接口。我经常看到下面的实现:publicListlistUser(){ListuserList=userListRepostity.selectByExample(newUserExample());if(CollectionUtils.isEmpty(userList)){//springutil工具类返回null;}返回用户列表;}此代码返回null。从我多年的开发经验来看,像集合这样的返回值最好不要返回null,因为如果你返回null的话,会给调用者带来很大的麻烦。您将把这种呼叫风险置于呼叫者的控制之下。如果调用者是一个谨慎的人,他会做一个空的条件判断。如果他不谨慎,或者是面向接口编程的狂热者(当然,面向接口编程才是正确的方向),他会根据自己的理解去调用接口,而不做是否是的条件判断null,如果是的话,是很危险的,很有可能出现空指针异常!根据墨菲定律判断:“可能出现的问题,以后一定会出现!”基于此,我们将对其进行优化:publicListlistUser(){ListuserList=userListRepostity.selectByExample(newUserExample());if(CollectionUtils.isEmpty(userList)){returnLists.newArrayList();//guava类库提供的方法}returnuserList;}对于接口(ListlistUser()),它必须返回List,即使没有数据,它仍然会返回List(集合中没有元素);通过上面的修改,我们成功的避免了可能出现的空指针异常,更加安全!深入研究接口User的get方法get(Integerid)可以看到的现象是,如果我给了id,它肯定会给我返回User。但事实是,未必如此。我看到的实现:publicUserget(Integerid){returnuserRepository.selectByPrimaryKey(id);//通过id直接从数据库中获取实体对象}相信很多人也会这样写。通过代码可知,其返回值很可能为null!但是我们传递的接口是无法区分的!这是一件非常危险的事情。特别是对于来电者!我的建议是在接口清晰的情况下需要补充文档,比如异常的描述,使用注解@exception:publicinterfaceUserSearchService{/***根据用户id获取用户信息*@paramid用户id*@returnuserEntity*@exceptionUserNotFoundException*/Userget(Integerid);}我们添加接口定义后,调用者会看到如果调用这个接口,很可能会抛出“UserNotFoundException(找不到用户)”这样的异常。这个方法在调用者调用接口的时候可以看到接口的定义,但是这个方法是一个“弱提示”!如果调用者忽略注解,可能会给业务系统带来风险,而这个风险可能会损失一个亿!除了上面的“弱提示”方式,还有一种方式返回值可能为空。那么该怎么办?我认为我们需要添加一个接口来描述这种情况。引入jdk8的Optional,或者使用guava的Optional。见如下定义:publicinterfaceUserSearchService{/***根据用户id获取用户信息*@paramid用户id*@return用户实体,该实体可以是默认值*/OptionalgetOptional(Integerid);}Optional有两个含义:存在或默认。然后通过阅读接口getOptional(),我们可以快速理解返回值的意图。这其实是我们希望看到的,它消除了歧义。它的实现可以写成:publicOptionalgetOptional(Integerid){returnOptional.ofNullable(userRepository.selectByPrimaryKey(id));}深入入口通过以上所有接口的描述,可以确定入口id一定是必填项吗?我想答案应该是:不确定。除非接口的文档注释中另有说明。那么如何约束输入参数呢?给大家推荐两种方式:强制约束文档约束(弱提示)1.强制约束,我们可以通过jsr303进行严格约束声明:publicinterfaceUserSearchService{/***根据用户id获取用户信息*@paramid用户ID*@return用户实体*@exceptionUserNotFoundException*/Userget(@NotNullIntegerid);/***根据用户id获取用户信息*@paramid用户id*@returnuser实体,这个实体可以是默认值*/OptionalgetOptional(@NotNullIntegerid);}当然这样写需要用AOP操作来验证,不过spring已经提供了很好的集成方案,这里不再赘述。2、文档约束在很多情况下,我们都会遇到遗留代码,而对于遗留代码,整??体改造的可能性很小。我们更喜欢通过阅读接口的实现来描述接口。jsr305规范给了我们一种描述接口输入参数的方法(需要导入库com.google.code.findbugs:jsr305):接口描述可以使用注解:@Nullable@Nonnull@CheckForNull。例如:publicinterfaceUserSearchService{/***根据用户id获取用户信息*@paramiduserid*@returnuserentity*@exceptionUserNotFoundException*/@CheckForNullUserget(@NonNullIntegerid);/***根据userid获取用户信息*@paramiduserid*@returnUser实体,这个实体可以是默认值*/OptionalgetOptional(@NonNullIntegerid);}Summary通过空集合返回值,Optional,jsr303,jsr305这些方法可以让我们的代码更具可读性,错误率更低!空集合返回值:如果有这样一个返回值的集合,除非你真的有理由说服自己,否则,你必须返回一个空集合而不是null可选:如果你的代码是jdk8,引入它!不行就用Guava的Optional,或者升级jdk版本!大大增加了界面的可读性!jsr303:如果正在开发新项目,不妨加这个试试!一定有一种特别爽的感觉!jsr305:如果老项目在你手上,你可以尝试添加这种文档注解,有助于后期重构,或者增加新功能。对于老项目界面理解!空对象模式场景我们来看一个DTO转换场景,对象:@DatastaticclassPersonDTO{privateStringdtoName;私有字符串dtoAge;}@DatastaticclassPerson{私有字符串名称;私有字符串年龄;}需求是将Person对象转换成PersonDTO然后返回。当然实际操作,如果return为null,就会返回null,但是PersonDTO不能返回null(尤其是Rest接口返回的DTO)。这里,我们只关注转换操作,看下面代码:@TestpublicvoidshouldConvertDTO(){PersonDTOpersonDTO=newPersonDTO();Personperson=newPerson();如果(!Objects.isNull(人)){personDTO。setDtoAge(person.getAge());personDTO.setDtoName(person.getName());}else{personDTO.setDtoAge("");personDTO.setDtoName("");}}优化修改这样的数据转换,我们知道可读性很差。对于每个字段的判断,如果为空,则设置为空字符串("")。用另一种思维方式思考。我们拿到Person类的数据,然后进行赋值操作(setXXX),其实Person具体是谁实现的并不重要。然后我们可以创建一个Person子类:staticclassNullPersonextendsPerson{@OverridepublicStringgetAge(){return"";}@OverridepublicStringgetName(){返回“”;}}PersonExist的一个特例,如果Person为空时,返回get*的一些默认行为。所以代码可以修改为:@TestpublicvoidshouldConvertDTO(){PersonDTOpersonDTO=newPersonDTO();Personperson=getPerson();个人DTO。setDtoAge(person.getAge());personDTO.setDtoName(person.getName());}privatePersongetPerson(){returnnewNullPerson();//如果Person为null,则返回一个空对象}其中getPerson()方法,可以根据业务逻辑获取Person可能的对象(当前例子,如果Person不存在,则返回Person的特例NUllPerson),如果修改成这样,代码的可读性会变得很强。使用Optional可以优化空对象模式。它的缺点是需要创建一个特例对象,但是如果特例很多,我们需要创建多个特例对象吗?虽然我们也使用了面向对象的多态特性,但是,如果业务的复杂性真的让我们创建了多个特例对象,我们还是要三思这个模式,这可能会带来代码的复杂性。对于上面的代码,也可以使用Optional进行优化。@TestpublicvoidshouldConvertDTO(){PersonDTOpersonDTO=newPersonDTO();Optional.ofNullable(getPerson()).ifPresent(person->{personDTO.setDtoAge(person.getAge());personDTO.setDtoName(person.getName());});}privatePersongetPerson(){返回空值;}我觉得对null值使用Optional比较合适,只适用于“存在”的场景。如果只是判断控件的存在,我建议使用Optional。Optional的正确使用如此强大,表达了计算机最原始的特性(0或1),那么如何正确使用呢!如果您编写了公共方法,则不应将Optional用作参数。此方法指定一些输入参数。其中一些参数可以作为null传入。这个时候可以使用Optional吗?我的建议是:不要这样用!例如:publicinterfaceUserService{ListlistUser(Optionalusername);本例中的方法listUser可能会告诉我们,我们需要查询所有基于用户名的数据集合,如果用户名为空,则返回所有用户集合。看到这个方法,我们会觉得有些歧义:“如果没有username,是返回一个空集合?还是返回所有用户数据集合?”Optional是一种分支判断,那么我们应该注意Optional还是Optional.get()呢?我给大家的建议是,如果不想这样的歧义,就不要用!如果真的要表达两个意思,拆分成两个接口:publicinterfaceUserService{ListlistUser(Stringusername);列表<用户>listUser();}我觉得这样有更强的语义,更能满足软件设计原则中的“单一职责”。如果您认为您的入参确实有必要传null,请使用jsr303或jsr305进行说明和验证!请记住!可选不能作为入参!Optional作为返回值,作为实体返回Optioanl可以作为返回值吗?事实上,这个语义是否存在是非常令人满意的。如果说要根据id获取用户信息,这个用户可能存在也可能不存在。你可以这样使用它:publicinterfaceUserService{Optionalget(Integerid);}在调用这个方法的时候,调用者很清楚get方法返回的数据可能不存在,这样可以做出一些更合理的判断,更好的防止空指针错误!当然,如果业务方真的需要根据id查询User,就不要这么用了。请解释您要抛出的异常。只有在合理考虑返回null的情况下,才做Optionalreturnset并不是所有实体的返回值都可以这样使用!如果返回一个集合:publicinterfaceUserService{Optional>listUser();}这样的返回结果会让调用者无所适从。判断完Optional还需要判断isEmpty吗?由此带来的返回值歧义!我认为没有必要。我们不得不约定,对于List这样的集合的返回值,如果集合真的为null,请返回一个空集合(Lists.newArrayList);使用Optional变量OptionaluserOpt=...如果有这样的变量userOpt,请记住:不能直接使用get。如果你这样使用它,你将失去Optional本身的意义(比如userOp.get())。不要直接使用getOrThrow。如果你有这样的需求:获取不到就抛出异常。那么就得考虑调用的接口设计是否合理。对于一个javabean来说,所有的properties都可能返回null,是否需要将所有的getter都重写成Optional?我给大家的建议是不要像这样滥用Optional。即使我的javabean中的getter是符合Optional的,但是因为javabean太多了,这样会导致你50%以上的代码都在判断Optional,从而污染代码。(我想说的是,其实你实体里面的字段都应该有业务意义,你会认真思考它的价值,不能因为Optional的存在而滥用它)我们应该多注意商业,不只是空洞的价值判断。请不要在getter中滥用Optional。总结可以这样总结Optional的使用:当use值为空时,不是报错,可以使用Optional!可选不应该用于集合操作!不要滥用Optional,比如在javabean中的getter!