安庆大理运城常德铜陵江西
投稿投诉
江西南阳
嘉兴昆明
铜陵滨州
广东西昌
常德梅州
兰州阳江
运城金华
广西萍乡
大理重庆
诸暨泉州
安庆南充
武汉辽宁

你见过哪些目瞪口呆的Java代码技巧?

10月10日 霸王亭投稿
  本文不是一个吹嘘的文章,不会讲很多高深的架构,相反,会讲解很多基础的问题和写法问题,如果读者自认为基础问题和写法问题都是不是问题,那请忽略这篇文章,节省出时间去做一些有意义的事情。
  基于SpringBootMyBatisPlusVueElement实现的后台管理系统用户小程序,支持RBAC动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能。
  项目地址:https:github。comYunaiVruoyivuepro
  开发工具
  不知道有多少老程序员还在使用Eclipse,这些程序员们要不就是因循守旧,要不就是根本就不知道其他好的开发工具的存在,Eclipse吃内存卡顿的现象以及各种偶然莫名异常的出现,都告知我们是时候寻找新的开发工具了。
  更换IDE
  根本就不想多解释要换什么样的IDE,如果你想成为一个优秀的Java程序员,请更换IntelliJIDEA。使用IDEA的好处,请搜索谷歌。
  别告诉我快捷键不好用
  更换IDE不在我本文的重点内容中,所以不想用太多的篇幅去写为什么更换IDE。在这里,我只能告诉你,更换IDE只为了更好、更快的写好Java代码。原因略。
  别告诉我快捷键不好用,请尝试新事物。
  基于微服务的思想,构建在B2C电商场景下的项目实战。核心技术栈,是SpringBootDubbo。未来,会重构成SpringCloudAlibaba。
  项目地址:https:github。comYunaiVonemall
  Bean
  bean使我们使用最多的模型之一,我将以大篇幅去讲解bean,希望读者好好体会。
  domain包名
  根据很多Java程序员的经验来看,一个数据库表则对应着一个domain对象,所以很多程序员在写代码时,包名则使用:com。xxx。domain,这样写好像已经成为了行业的一种约束,数据库映射对象就应该是domain。但是你错了,domain是一个领域对象,往往我们再做传统Java软件Web开发中,这些domain都是贫血模型,是没有行为的,或是没有足够的领域模型的行为的,所以,以这个理论来讲,这些domain都应该是一个普通的entity对象,并非领域对象,所以请把包名改为:com。xxx。entity。
  如果你还不理解我说的话,请看一下VaughnVernon出的一本叫做《IMPLEMENTINGDOMAINDRIVENDESIGN》(实现领域驱动设计)这本书,书中讲解了贫血模型与领域模型的区别,相信你会受益匪浅。
  DTO
  数据传输我们应该使用DTO对象作为传输对象,这是我们所约定的,因为很长时间我一直都在做移动端API设计的工作,有很多人告诉我,他们认为只有给手机端传输数据的时候(inputoroutput),这些对象成为DTO对象。请注意!这种理解是错误的,只要是用于网络传输的对象,我们都认为他们可以当做是DTO对象,比如电商平台中,用户进行下单,下单后的数据,订单会发到OMS或者ERP系统,这些对接的返回值以及入参也叫DTO对象。
  我们约定某对象如果是DTO对象,就将名称改为XXDTO,比如订单下发OMS:OMSOrderInputDTO。
  DTO转化
  正如我们所知,DTO为系统与外界交互的模型对象,那么肯定会有一个步骤是将DTO对象转化为BO对象或者是普通的entity对象,让service层去处理。
  场景
  比如添加会员操作,由于用于演示,我只考虑用户的一些简单数据,当后台管理员点击添加用户时,只需要传过来用户的姓名和年龄就可以了,后端接受到数据后,将添加创建时间和更新时间和默认密码三个字段,然后保存数据库。RequestMapping(v1apiuser)
  RestController
  publicclassUserApi{
  Autowired
  privateUserServiceuserS
  PostMapping
  publicUseraddUser(UserInputDTOuserInputDTO){
  UserusernewU
  user。setUsername(userInputDTO。getUsername);
  user。setAge(userInputDTO。getAge);
  returnuserService。addUser(user);
  }
  }
  我们只关注一下上述代码中的转化代码,其他内容请忽略:UserusernewU
  user。setUsername(userInputDTO。getUsername);
  user。setAge(userInputDTO。getAge);
  请使用工具
  上边的代码,从逻辑上讲,是没有问题的,只是这种写法让我很厌烦,例子中只有两个字段,如果有20个字段,我们要如何做呢?一个一个进行set数据吗?当然,如果你这么做了,肯定不会有什么问题,但是,这肯定不是一个最优的做法。
  网上有很多工具,支持浅拷贝或深拷贝的Utils。举个例子,我们可以使用org。springframework。beans。BeanUtilscopyProperties对代码进行重构和优化:PostMapping
  publicUseraddUser(UserInputDTOuserInputDTO){
  UserusernewU
  BeanUtils。copyProperties(userInputDTO,user);
  returnuserService。addUser(user);
  }
  BeanUtils。copyProperties是一个浅拷贝方法,复制属性时,我们只需要把DTO对象和要转化的对象两个的属性值设置为一样的名称,并且保证一样的类型就可以了。如果你在做DTO转化的时候一直使用set进行属性赋值,那么请尝试这种方式简化代码,让代码更加清晰!
  转化的语义
  上边的转化过程,读者看后肯定觉得优雅很多,但是我们再写Java代码时,更多的需要考虑语义的操作,再看上边的代码:UserusernewU
  BeanUtils。copyProperties(userInputDTO,user);
  虽然这段代码很好的简化和优化了代码,但是他的语义是有问题的,我们需要提现一个转化过程才好,所以代码改成如下:PostMapping
  publicUseraddUser(UserInputDTOuserInputDTO){
  UseruserconvertFor(userInputDTO);
  returnuserService。addUser(user);
  }
  privateUserconvertFor(UserInputDTOuserInputDTO){
  UserusernewU
  BeanUtils。copyProperties(userInputDTO,user);
  
  }
  这是一个更好的语义写法,虽然他麻烦了些,但是可读性大大增加了,在写代码时,我们应该尽量把语义层次差不多的放到一个方法中,比如:UseruserconvertFor(userInputDTO);
  returnuserService。addUser(user);
  这两段代码都没有暴露实现,都是在讲如何在同一个方法中,做一组相同层次的语义操作,而不是暴露具体的实现。
  如上所述,是一种重构方式,读者可以参考MartinFowler的《RefactoringImporvingtheDesignofExistingCode》(重构改善既有代码的设计)这本书中的ExtractMethod重构方式。
  抽象接口定义
  当实际工作中,完成了几个API的DTO转化时,我们会发现,这样的操作有很多很多,那么应该定义好一个接口,让所有这样的操作都有规则的进行。
  如果接口被定义以后,那么convertFor这个方法的语义将产生变化,它将是一个实现类。
  看一下抽象后的接口:publicinterfaceDTOConvertS,T{
  Tconvert(Ss);
  }
  虽然这个接口很简单,但是这里告诉我们一个事情,要去使用泛型,如果你是一个优秀的Java程序员,请为你想做的抽象接口,做好泛型吧。
  我们再来看接口实现:publicclassUserInputDTOConvertimplementsDTOConvert{
  Override
  publicUserconvert(UserInputDTOuserInputDTO){
  UserusernewU
  BeanUtils。copyProperties(userInputDTO,user);
  
  }
  }
  我们这样重构后,我们发现现在的代码是如此的简洁,并且那么的规范:RequestMapping(v1apiuser)
  RestController
  publicclassUserApi{
  Autowired
  privateUserServiceuserS
  PostMapping
  publicUseraddUser(UserInputDTOuserInputDTO){
  UserusernewUserInputDTOConvert。convert(userInputDTO);
  returnuserService。addUser(user);
  }
  }
  reviewcode
  如果你是一个优秀的Java程序员,我相信你应该和我一样,已经数次重复review过自己的代码很多次了。
  我们再看这个保存用户的例子,你将发现,API中返回值是有些问题的,问题就在于不应该直接返回User实体,因为如果这样的话,就暴露了太多实体相关的信息,这样的返回值是不安全的,所以我们更应该返回一个DTO对象,我们可称它为UserOutputDTO:PostMapping
  publicUserOutputDTOaddUser(UserInputDTOuserInputDTO){
  UserusernewUserInputDTOConvert。convert(userInputDTO);
  UsersaveUserResultuserService。addUser(user);
  UserOutputDTOresultnewUserOutDTOConvert。convertToUser(saveUserResult);
  
  }
  这样你的API才更健全。
  不知道在看完这段代码之后,读者有是否发现还有其他问题的存在,作为一个优秀的Java程序员,请看一下这段我们刚刚抽象完的代码:UserusernewUserInputDTOConvert。convert(userInputDTO);
  你会发现,new这样一个DTO转化对象是没有必要的,而且每一个转化对象都是由在遇到DTO转化的时候才会出现,那我们应该考虑一下,是否可以将这个类和DTO进行聚合呢,看一下我的聚合结果:publicclassUserInputDTO{
  privateS
  
  publicStringgetUsername{
  
  }
  publicvoidsetUsername(Stringusername){
  this。
  }
  publicintgetAge{
  
  }
  publicvoidsetAge(intage){
  this。
  }
  publicUserconvertToUser{
  UserInputDTOConvertuserInputDTOConvertnewUserInputDTOC
  UserconvertuserInputDTOConvert。convert(this);
  
  }
  privatestaticclassUserInputDTOConvertimplementsDTOConvertUserInputDTO,User{
  Override
  publicUserconvert(UserInputDTOuserInputDTO){
  UserusernewU
  BeanUtils。copyProperties(userInputDTO,user);
  
  }
  }
  }
  然后API中的转化则由:UserusernewUserInputDTOConvert。convert(userInputDTO);
  UsersaveUserResultuserService。addUser(user);
  变成了:UseruseruserInputDTO。convertToU
  UsersaveUserResultuserService。addUser(user);
  我们再DTO对象中添加了转化的行为,我相信这样的操作可以让代码的可读性变得更强,并且是符合语义的。
  再查工具类
  再来看DTO内部转化的代码,它实现了我们自己定义的DTOConvert接口,但是这样真的就没有问题,不需要再思考了吗?
  我觉得并不是,对于Convert这种转化语义来讲,很多工具类中都有这样的定义,这中Convert并不是业务级别上的接口定义,它只是用于普通bean之间转化属性值的普通意义上的接口定义,所以我们应该更多的去读其他含有Convert转化语义的代码。
  我仔细阅读了一下GUAVA的源码,发现了com。google。common。base。Convert这样的定义:publicabstractclassConverterimplementsFunction{
  protectedabstractBdoForward(Aa);
  protectedabstractAdoBackward(Bb);
  其他略
  }
  从源码可以了解到,GUAVA中的Convert可以完成正向转化和逆向转化,继续修改我们DTO中转化的这段代码:privatestaticclassUserInputDTOConvertimplementsDTOConvertUserInputDTO,User{
  Override
  publicUserconvert(UserInputDTOuserInputDTO){
  UserusernewU
  BeanUtils。copyProperties(userInputDTO,user);
  
  }
  }
  修改后:privatestaticclassUserInputDTOConvertextendsConverterUserInputDTO,User{
  Override
  protectedUserdoForward(UserInputDTOuserInputDTO){
  UserusernewU
  BeanUtils。copyProperties(userInputDTO,user);
  
  }
  Override
  protectedUserInputDTOdoBackward(Useruser){
  UserInputDTOuserInputDTOnewUserInputDTO;
  BeanUtils。copyProperties(user,userInputDTO);
  returnuserInputDTO;
  }
  }
  看了这部分代码以后,你可能会问,那逆向转化会有什么用呢?其实我们有很多小的业务需求中,入参和出参是一样的,那么我们变可以轻松的进行转化,我将上边所提到的UserInputDTO和UserOutputDTO都转成UserDTO展示给大家。
  DTO:publicclassUserDTO{
  privateS
  
  publicStringgetUsername{
  
  }
  publicvoidsetUsername(Stringusername){
  this。
  }
  publicintgetAge{
  
  }
  publicvoidsetAge(intage){
  this。
  }
  publicUserconvertToUser{
  UserDTOConvertuserDTOConvertnewUserDTOC
  UserconvertuserDTOConvert。convert(this);
  
  }
  publicUserDTOconvertFor(Useruser){
  UserDTOConvertuserDTOConvertnewUserDTOC
  UserDTOconvertuserDTOConvert。reverse。convert(user);
  
  }
  privatestaticclassUserDTOConvertextendsConverterUserDTO,User{
  Override
  protectedUserdoForward(UserDTOuserDTO){
  UserusernewU
  BeanUtils。copyProperties(userDTO,user);
  
  }
  Override
  protectedUserDTOdoBackward(Useruser){
  UserDTOuserDTOnewUserDTO;
  BeanUtils。copyProperties(user,userDTO);
  returnuserDTO;
  }
  }
  }
  API:PostMapping
  publicUserDTOaddUser(UserDTOuserDTO){
  UseruseruserDTO。convertToU
  UsersaveResultUseruserService。addUser(user);
  UserDTOresultuserDTO。convertFor(saveResultUser);
  
  }
  当然,上述只是表明了转化方向的正向或逆向,很多业务需求的出参和入参的DTO对象是不同的,那么你需要更明显的告诉程序:逆向是无法调用的:privatestaticclassUserDTOConvertextendsConverterUserDTO,User{
  Override
  protectedUserdoForward(UserDTOuserDTO){
  UserusernewU
  BeanUtils。copyProperties(userDTO,user);
  
  }
  Override
  protectedUserDTOdoBackward(Useruser){
  thrownewAssertionError(不支持逆向转化方法!);
  }
  }
  看一下doBackward方法,直接抛出了一个断言异常,而不是业务异常,这段代码告诉代码的调用者,这个方法不是准你调用的,如果你调用,我就断言你调用错误了。
  关于异常处理的更详细介绍,可以参考我之前的文章:如何优雅的设计Java异常(http:lrwinx。github。io20160428E5A682E4BD95E4BC98E99B85E79A84E8AEBEE8AEA1javaE5BC82E5B8B8),应该可以帮你更好的理解异常。
  Bean的验证
  如果你认为我上边写的那个添加用户API写的已经非常完美了,那只能说明你还不是一个优秀的程序员。我们应该保证任何数据的入参到方法体内都是合法的。
  为什么要验证
  很多人会告诉我,如果这些API是提供给前端进行调用的,前端都会进行验证啊,你为什还要验证?
  其实答案是这样的,我从不相信任何调用我API或者方法的人,比如前端验证失败了,或者某些人通过一些特殊的渠道(比如Charles进行抓包),直接将数据传入到我的API,那我仍然进行正常的业务逻辑处理,那么就有可能产生脏数据!
  对于脏数据的产生一定是致命,这句话希望大家牢记在心,再小的脏数据也有可能让你找几个通宵!
  jsr303验证
  hibernate提供的jsr303实现,我觉得目前仍然是很优秀的,具体如何使用,我不想讲,因为谷歌上你可以搜索出很多答案!
  再以上班的API实例进行说明,我们现在对DTO数据进行检查:publicclassUserDTO{
  Not
  privateS
  Not
  
  其他代码略
  }
  API验证:PostMapping
  publicUserDTOaddUser(ValidUserDTOuserDTO){
  UseruseruserDTO。convertToU
  UsersaveResultUseruserService。addUser(user);
  UserDTOresultuserDTO。convertFor(saveResultUser);
  
  }
  我们需要将验证结果传给前端,这种异常应该转化为一个api异常(带有错误码的异常)。PostMapping
  publicUserDTOaddUser(ValidUserDTOuserDTO,BindingResultbindingResult){
  checkDTOParams(bindingResult);
  UseruseruserDTO。convertToU
  UsersaveResultUseruserService。addUser(user);
  UserDTOresultuserDTO。convertFor(saveResultUser);
  
  }
  privatevoidcheckDTOParams(BindingResultbindingResult){
  if(bindingResult。hasErrors){
  thrownew带验证码的验证错误异常
  }
  }
  BindingResult是SpringMVC验证DTO后的一个结果集,可以参考spring官方文档(http:spring。io)。
  检查参数后,可以抛出一个带验证码的验证错误异常,具体异常设计可以参考如何优雅的设计Java异常(http:lrwinx。github。io20160428E5A682E4BD95E4BC98E99B85E79A84E8AEBEE8AEA1javaE5BC82E5B8B8)。
  拥抱lombok
  上边的DTO代码,已经让我看的很累了,我相信读者也是一样,看到那么多的Getter和Setter方法,太烦躁了,那时候有什么方法可以简化这些呢。
  请拥抱lombok,它会帮助我们解决一些让我们很烦躁的问题
  去掉Setter和Getter
  其实这个标题,我不太想说,因为网上太多,但是因为很多人告诉我,他们根本就不知道lombok的存在,所以为了让读者更好的学习,我愿意写这样一个例子:Setter
  Getter
  publicclassUserDTO{
  Not
  privateS
  Not
  
  publicUserconvertToUser{
  UserDTOConvertuserDTOConvertnewUserDTOC
  UserconvertuserDTOConvert。convert(this);
  
  }
  publicUserDTOconvertFor(Useruser){
  UserDTOConvertuserDTOConvertnewUserDTOC
  UserDTOconvertuserDTOConvert。reverse。convert(user);
  
  }
  privatestaticclassUserDTOConvertextendsConverterUserDTO,User{
  Override
  protectedUserdoForward(UserDTOuserDTO){
  UserusernewU
  BeanUtils。copyProperties(userDTO,user);
  
  }
  Override
  protectedUserDTOdoBackward(Useruser){
  thrownewAssertionError(不支持逆向转化方法!);
  }
  }
  }
  看到了吧,烦人的Getter和Setter方法已经去掉了。
  但是上边的例子根本不足以体现lombok的强大。我希望写一些网上很难查到,或者很少人进行说明的lombok的使用以及在使用时程序语义上的说明。
  比如:Data,AllArgsConstructor,NoArgsConstructor。。这些我就不进行一一说明了,请大家自行查询资料。
  bean中的链式风格
  什么是链式风格?我来举个例子,看下面这个Student的bean:publicclassStudent{
  privateS
  
  publicStringgetName{
  
  }
  publicStudentsetName(Stringname){
  this。
  
  }
  publicintgetAge{
  
  }
  publicStudentsetAge(intage){
  
  }
  }
  仔细看一下set方法,这样的设置便是chain的style,调用的时候,可以这样使用:StudentstudentnewStudent
  。setAge(24)
  。setName(zs);
  相信合理使用这样的链式代码,会更多的程序带来很好的可读性,那看一下如果使用lombok进行改善呢,请使用Accessors(chaintrue),看如下代码:Accessors(chaintrue)
  Setter
  Getter
  publicclassStudent{
  privateS
  
  }
  这样就完成了一个对于bean来讲很友好的链式操作。
  静态构造方法
  静态构造方法的语义和简化程度真的高于直接去new一个对象。比如new一个List对象,过去的使用是这样的:ListStringlistnewArrayL
  看一下guava中的创建方式:ListStringlistLists。newArrayL
  Lists命名是一种约定(俗话说:约定优于配置),它是指Lists是List这个类的一个工具类,那么使用List的工具类去产生List,这样的语义是不是要比直接new一个子类来的更直接一些呢,答案是肯定的,再比如如果有一个工具类叫做Maps,那你是否想到了创建Map的方法呢:HashMapString,StringobjectObjectHashMapMaps。newHashM
  好了,如果你理解了我说的语义,那么,你已经向成为Java程序员更近了一步了。
  再回过头来看刚刚的Student,很多时候,我们去写Student这个bean的时候,他会有一些必输字段,比如Student中的name字段,一般处理的方式是将name字段包装成一个构造方法,只有传入name这样的构造方法,才能创建一个Student对象。
  接上上边的静态构造方法和必传参数的构造方法,使用lombok将更改成如下写法(RequiredArgsConstructor和Non):Accessors(chaintrue)
  Setter
  Getter
  RequiredArgsConstructor(staticNameofName)
  publicclassStudent{
  NonprivateS
  
  }
  测试代码:StudentstudentStudent。ofName(zs);
  这样构建出的bean语义是否要比直接new一个含参的构造方法(包含name的构造方法)要好很多。
  当然,看过很多源码以后,我想相信将静态构造方法ofName换成of会先的更加简洁:Accessors(chaintrue)
  Setter
  Getter
  RequiredArgsConstructor(staticNameof)
  publicclassStudent{
  NonprivateS
  
  }
  测试代码:StudentstudentStudent。of(zs);
  当然他仍然是支持链式调用的:StudentstudentStudent。of(zs)。setAge(24);
  这样来写代码,真的很简洁,并且可读性很强。
  使用builder
  Builder模式我不想再多解释了,读者可以看一下《HeadFirst》(设计模式)的建造者模式。
  今天其实要说的是一种变种的builder模式,那就是构建bean的builder模式,其实主要的思想是带着大家一起看一下lombok给我们带来了什么。
  看一下Student这个类的原始builder状态:publicclassStudent{
  privateS
  
  publicStringgetName{
  
  }
  publicvoidsetName(Stringname){
  this。
  }
  publicintgetAge{
  
  }
  publicvoidsetAge(intage){
  this。
  }
  publicstaticBuilderbuilder{
  returnnewB
  }
  publicstaticclassBuilder{
  privateS
  
  publicBuildername(Stringname){
  this。
  
  }
  publicBuilderage(intage){
  this。
  
  }
  publicStudentbuild{
  StudentstudentnewS
  student。setAge(age);
  student。setName(name);
  
  }
  }
  }
  调用方式:StudentstudentStudent。builder。name(zs)。age(24)。
  这样的builder代码,让我是在恶心难受,于是我打算用lombok重构这段代码:Builder
  publicclassStudent{
  privateS
  
  }
  调用方式:StudentstudentStudent。builder。name(zs)。age(24)。
  代理模式
  正如我们所知的,在程序中调用rest接口是一个常见的行为动作,如果你和我一样使用过spring的RestTemplate,我相信你会我和一样,对他抛出的非http状态码异常深恶痛绝。
  所以我们考虑将RestTemplate最为底层包装器进行包装器模式的设计:publicabstractclassFilterRestTemplateimplementsRestOperations{
  protectedvolatileRestTemplaterestT
  protectedFilterRestTemplate(RestTemplaterestTemplate){
  this。restTemplaterestT
  }
  实现RestOperations所有的接口
  }
  然后再由扩展类对FilterRestTemplate进行包装扩展:publicclassExtractRestTemplateextendsFilterRestTemplate{
  privateRestTemplaterestT
  publicExtractRestTemplate(RestTemplaterestTemplate){
  super(restTemplate);
  this。restTemplaterestT
  }
  publicTRestResponseDTOTpostForEntityWithNoException(Stringurl,Objectrequest,ClassTresponseType,Object。。。uriVariables)
  throwsRestClientException{
  RestResponseDTOTrestResponseDTOnewRestResponseDTOT;
  ResponseEntityTtResponseE
  try{
  tResponseEntityrestTemplate。postForEntity(url,request,responseType,uriVariables);
  restResponseDTO。setData(tResponseEntity。getBody);
  restResponseDTO。setMessage(tResponseEntity。getStatusCode。name);
  restResponseDTO。setStatusCode(tResponseEntity。getStatusCodeValue);
  }catch(Exceptione){
  restResponseDTO。setStatusCode(RestResponseDTO。UNKNOWNERROR);
  restResponseDTO。setMessage(e。getMessage);
  restResponseDTO。setD
  }
  returnrestResponseDTO;
  }
  }
  包装器ExtractRestTemplate很完美的更改了异常抛出的行为,让程序更具有容错性。在这里我们不考虑ExtractRestTemplate完成的功能,让我们把焦点放在FilterRestTemplate上,实现RestOperations所有的接口,这个操作绝对不是一时半会可以写完的,当时在重构之前我几乎写了半个小时,如下:publicabstractclassFilterRestTemplateimplementsRestOperations{
  protectedvolatileRestTemplaterestT
  protectedFilterRestTemplate(RestTemplaterestTemplate){
  this。restTemplaterestT
  }
  Override
  publicTTgetForObject(Stringurl,ClassTresponseType,Object。。。uriVariables)throwsRestClientException{
  returnrestTemplate。getForObject(url,responseType,uriVariables);
  }
  Override
  publicTTgetForObject(Stringurl,ClassTresponseType,MapString,?uriVariables)throwsRestClientException{
  returnrestTemplate。getForObject(url,responseType,uriVariables);
  }
  Override
  publicTTgetForObject(URIurl,ClassTresponseType)throwsRestClientException{
  returnrestTemplate。getForObject(url,responseType);
  }
  Override
  publicTResponseEntityTgetForEntity(Stringurl,ClassTresponseType,Object。。。uriVariables)throwsRestClientException{
  returnrestTemplate。getForEntity(url,responseType,uriVariables);
  }
  其他实现代码略
  }
  我相信你看了以上代码,你会和我一样觉得恶心反胃,后来我用lombok提供的代理注解优化了我的代码(Delegate):AllArgsConstructor
  publicabstractclassFilterRestTemplateimplementsRestOperations{
  Delegate
  protectedvolatileRestTemplaterestT
  }
  这几行代码完全替代上述那些冗长的代码。
  是不是很简洁,做一个拥抱lombok的程序员吧。
  重构
  需求案例
  项目需求
  项目开发阶段,有一个关于下单发货的需求:如果今天下午3点前进行下单,那么发货时间是明天,如果今天下午3点后进行下单,那么发货时间是后天,如果被确定的时间是周日,那么在此时间上再加1天为发货时间。
  思考与重构
  我相信这个需求看似很简单,无论怎么写都可以完成。
  很多人可能看到这个需求,就动手开始写Calendar或Date进行计算,从而完成需求。
  而我给的建议是,仔细考虑如何写代码,然后再去写,不是说所有的时间操作都用Calendar或Date去解决,一定要看场景。
  对于时间的计算我们要考虑jodatime这种类似的成熟时间计算框架来写代码,它会让代码更加简洁和易读。
  请读者先考虑这个需求如何用Java代码完成,或先写一个你觉得完成这个代码的思路,再来看我下边的代码,这样,你的收获会更多一些:finalDateTimeDISTRIBUTIONTIMESPLITTIMEnewDateTime。withTime(15,0,0,0);
  privateDatecalculateDistributionTimeByOrderCreateTime(DateorderCreateTime){
  DateTimeorderCreateDateTimenewDateTime(orderCreateTime);
  DatetomorroworderCreateDateTime。plusDays(1)。toD
  DatetheDayAfterTomorroworderCreateDateTime。plusDays(2)。toD
  returnorderCreateDateTime。isAfter(DISTRIBUTIONTIMESPLITTIME)?wrapDistributionTime(theDayAfterTomorrow):wrapDistributionTime(tomorrow);
  }
  privateDatewrapDistributionTime(DatedistributionTime){
  DateTimecurrentDistributionDateTimenewDateTime(distributionTime);
  DateTimeplusOneDaycurrentDistributionDateTime。plusDays(1);
  booleanisSunday(DateTimeConstants。SUNDAYcurrentDistributionDateTime。getDayOfWeek);
  returnisSunday?plusOneDay。toDate:currentDistributionDateTime。toD
  }
  读这段代码的时候,你会发现,我将判断和有可能出现的不同结果都当做一个变量,最终做一个三目运算符的方式进行返回,这样的优雅和可读性显而易见,当然这样的代码不是一蹴而就的,我优化了3遍产生的以上代码。读者可根据自己的代码和我写的代码进行对比。
  提高方法
  如果你做了3年的程序员,我相信像如上这样的需求,你很轻松就能完成,但是如果你想做一个会写Java的程序员,就好好的思考和重构代码吧。
  写代码就如同写字一样,同样的字,大家都会写,但是写出来是否好看就不一定了。如果想把程序写好,就要不断的思考和重构,敢于尝试,敢于创新,不要因循守旧,一定要做一个优秀的Java程序员。
  提高代码水平最好的方法就是有条理的重构!(注意:是有条理的重构)
  设计模式
  设计模式就是工具,而不是提现你是否是高水平程序员的一个指标。
  我经常会看到某一个程序员兴奋的大喊,哪个程序哪个点我用到了设计模式,写的多么多么优秀,多么多么好。我仔细去翻阅的时候,却发现有很多是过度设计的。
  业务驱动技术or技术驱动业务
  业务驱动技术or技术驱动业务?其实这是一个一直在争论的话题,但是很多人不这么认为,我觉得就是大家不愿意承认罢了。我来和大家大概分析一下作为一个Java程序员,我们应该如何判断自己所处于的位置。
  业务驱动技术:如果你所在的项目是一个收益很小或者甚至没有收益的项目,请不要搞其他创新的东西,不要驱动业务要如何如何做,而是要熟知业务现在的痛点是什么?如何才能帮助业务盈利或者让项目更好,更顺利的进行。
  技术驱动业务:如果你所在的项目是一个很牛的项目,比如淘宝这类的项目,我可以在满足业务需求的情况下,和业务沟通,使用什么样的技术能更好的帮助业务创造收益,比如说下单的时候要进队列,可能几分钟之后订单状态才能处理完成,但是会让用户有更流畅的体验,赚取更多的访问流量,那么我相信业务愿意被技术驱动,会同意订单的延迟问题,这样便是技术驱动业务。
  我相信大部分人还都处于业务驱动技术的方向吧。
  所以你既然不能驱动业务,那就请拥抱业务变化吧。
  代码设计
  一直在做Java后端的项目,经常会有一些变动,我相信大家也都遇到过。
  比如当我们写一段代码的时候,我们考虑将需求映射成代码的状态模式,突然有一天,状态模式里边又添加了很多行为变化的东西,这时候你就挠头了,你硬生生的将状态模式中添加过多行为和变化。
  慢慢的你会发现这些状态模式,其实更像是一簇算法,应该使用策略模式,这时你应该已经晕头转向了。
  说了这么多,我的意思是,只要你觉得合理,就请将状态模式改为策略模式吧,所有的模式并不是凭空想象出来的,都是基于重构。
  Java编程中没有银弹,请拥抱业务变化,一直思考重构,你就有一个更好的代码设计!
  你真的优秀吗?
  真不好意思,我取了一个这么无聊的标题。
  国外流行一种编程方式,叫做结对编程,我相信国内很多公司都没有这么做,我就不在讲述结对编程带来的好处了,其实就是一边codereview,一边互相提高的一个过程。既然做不到这个,那如何让自己活在自己的世界中不断提高呢?
  平时开发的时候,做出的代码总认为是正确的,而且写法是完美的。,我相信这是大部分人的心声,还回到刚刚的问题,如何在自己的世界中不断提高呢?
  答案就是:
  多看成熟框架的源码
  多回头看自己的代码
  勤于重构
  你真的优秀吗?如果你每周都完成了学习源码,回头看自己代码,然后勤于重构,我认为你就真的很优秀了。
  即使也许你只是刚刚入门,但是一直坚持,你就是一个真的会写java代码的程序员了。
  技能
  UML
  不想多讨论UML相关的知识,但是我觉得你如果真的会写Java,请先学会表达自己,UML就是你说话的语言,做一名优秀的Java程序员,请至少学会这两种UML图:
  类图
  时序图
  cleancode
  我认为保持代码的简洁和可读性是代码的最基本保证,如果有一天为了程序的效率而降低了这两点,我认为是可以谅解的,除此之外,没有任何理由可以让你任意挥霍你的代码。
  读者可以看一下RobertC。Martin出版的《CleanCode》(代码整洁之道)这本书
  可以参考美团文章聊聊cleancode(http:tech。meituan。comcleancode。html);
  也可以看一下阿里的Java编码规范(https:yq。aliyun。comarticles69327?spm5176。100239。topwz。1。om5dRN)。
  无论如何,请保持你的代码的整洁。
  Linux基础命令
  这点其实和会写Java没有关系,但是Linux很多时候确实承载运行Java的容器,请学好Linux的基础命令。
  参考鸟哥的《Linux私房菜》
  总结
  Java是一个大体系,今天讨论并未涉及框架和架构相关知识,只是讨论如何写好代码。
  本文从写Java程序的小方面一直写到大方面,来阐述了如何才能写好Java程序,并告诉读者们如何才能提高自身的编码水平。
  我希望看到这篇文章的各位都能做一个优秀的Java程序员。
  作者:Lrwin
投诉 评论 转载

补齐短板王者归来?华为最新4G新机5G手机壳鸿蒙3。0齐发作为曾经国产手机一哥的华为,现在早已不能和小米OV等其它三家相提并论,无论是国内还是在全球市场,华为手机的销量已经被边缘化了。但好消息是华为一直没有放弃,加上以前建立起来的口碑……推荐从入门到放弃,2022最值得推荐的14款播放机没有什么比聆听一张精彩的CD专辑,让人感到更惬意的。当然,好的CD播放机能够帮助你最大限度地享受这场音乐之旅。购买CD播放机前应该考虑什么?比如功能、易用性和控制,当然还……飞猪启动组织改革回归创业者心态,将勒紧裤腰带过日子记者程璐3月31日,界面新闻从飞猪内部获悉,飞猪旅行总裁庄卓然发出全员信,宣布启动飞猪组织改革,为未来独立面对市场做好充分准备。庄卓然表示,飞猪已经成立工作小组,将……复杂的物理公式,科学家是怎么记忆的?说说我的经验,我是做空间物理的,公式很多,尤其是在等离子体里面,公式非常多,从简洁到复杂都有。相对比较简洁的磁流体描述基本方程:但要注意,这个方程是非线性的,做几阶……现实中如果遇到王多鱼那种情况,一个月能花完十个亿吗?如果我是王多鱼的话,我很有可能完不成这项任务,因为王多鱼之所以能够把这笔钱花出去,是因为时机得到极度巧妙地运用。你不会真的以为普通人花掉10亿是一件很容易的事情吧?……你见过哪些目瞪口呆的Java代码技巧?本文不是一个吹嘘的文章,不会讲很多高深的架构,相反,会讲解很多基础的问题和写法问题,如果读者自认为基础问题和写法问题都是不是问题,那请忽略这篇文章,节省出时间去做一些有意义的事……杜恩新品ProVisionSolo上手试玩折腾是开心,分享是快乐,大家好我是成都刘老烧,时隔一年我再次评测杜恩的产品,这次杜恩也紧跟芝杜的步伐升级为了Realtek1619DR可谓是铩羽而归,同时也能支持到12bit4……新购电脑,选AMD板U套装就对了上周隶属于锐龙5000G系列的锐龙75700G和锐龙55600G正式上市,标志着消费市场上的锐龙APU在时隔3年之后终于迎来了换代。对于饱受缺卡困扰的玩家来说,锐龙75700G……中国电信2020年CN2DCI网络扩容工程华为中兴新华三中标来自中国电信官网消息,中国电信2020年CN2DCI网络扩容工程核心路由器(P)、常规业务路由器(PEA)、云业务路由器(PEB)、接入交换机等设备招标,已完成对各投标人递交的……快递信息遭泄露,推广隐私面单不该这么难据媒体报道,2021年9月下旬至10月中旬,印有姓名、电话、地址等个人信息的快递面单遭明码标价批量售卖,涉及申通快递、百世快递、中通快递、圆通速递等企业。这些快递面单被分类成历……微软收购暴雪索尼股价暴跌市值一日蒸发200亿美元!丨一只热股微软宣布以687亿美元的价格收购游戏开发商动视暴雪后,市场出现了剧烈震动。微软在游戏界的主要竞争对手索尼尤其受挫。1月19日,索尼股价暴跌13,市值一天之内减少约200亿美元。……轻薄本该如何挑选?认准这三款EVO认证,暑期换机不后悔时光如梭,一转眼2021年暑期已经接近尾声,学生党们迎来了开学季。而开学高峰遇上换季狂潮,各家数码品牌纷纷推出活动,许多数码好物变得更为亲民,此时入手香味更浓郁,因此不少朋友也……
黑鲨5系列冲破引力,3月30日让我们一起来体验SQL经典面试题行列转换鲎活了4亿年,蓝色血液比黄金还贵!被人绑着抽血还吃到濒危?科大讯飞刷新机器常识推理世界纪录格力最近又实行了双休,为什么今年那么多大厂都在宣布双休制?KotlinCoroutine在Android中的使用和封装微信会不会被新软件取代?小鹏飞行汽车测试飞行曝光华为真的没有那么好!在国外还不如小米。为什么?华为2021SoundX帝瓦雷,带来无与伦比的视听盛宴弹窗广告霸屏直播消费维权难互联网广告新办法提出硬核规定比特币再现曙光初现,以太坊旭日东升,这波下跌行情已经结束?

友情链接:中准网聚热点快百科快传网快生活快软网快好知文好找七猫云易事利