Java代码精简之道(推荐)
前言
古语有云:
道为术之灵,术为道之体;以道统术,以术得道。
其中:“道”指“规律、道理、理论”,“术”指“方法、技巧、技术”。意思是:“道”是“术”的灵魂,“术”是“道”的肉体;可以用“道”来统管“术”,也可以从“术”中获得“道”。
在拜读大佬“孤尽”的文章《CodeReview是苦涩但有意思的修行》时,感受最深的一句话就是:“优质的代码一定是少即是多的精兵原则”,这就是大佬的代码精简之“道”。
工匠追求“术”到极致,其实就是在寻“道”,且离悟“道”也就不远了,亦或是已经得道,这就是“工匠精神”——一种追求“以术得道”的精神。如果一个工匠只满足于“术”,不能追求“术”到极致去悟“道”,那只是一个靠“术”养家糊口的工匠而已。作者根据多年来的实践探索,总结了大量的Java代码精简之“术”,试图阐述出心中的Java代码精简之“道”。
1.利用语法
1.1.利用三元表达式
普通:
Stringtitle; if(isMember(phone)){ title="会员"; }else{ title="游客"; }
精简:
Stringtitle=isMember(phone)?"会员":"游客";
注意:对于包装类型的算术计算,需要注意避免拆包时的空指针问题。
1.2.利用for-each语句
从Java5起,提供了for-each循环,简化了数组和集合的循环遍历。for-each 循环允许你无需保持传统for循环中的索引就可以遍历数组,或在使用迭代器时无需在while循环中调用hasNext方法和next方法就可以遍历集合。
普通:
double[]values=...; for(inti=0;ivalueList=...; Iterator iterator=valueList.iterator(); while(iterator.hasNext()){ Doublevalue=iterator.next(); //TODO:处理value }
精简:
double[]values=...; for(doublevalue:values){ //TODO:处理value } ListvalueList=...; for(Doublevalue:valueList){ //TODO:处理value }
1.3.利用try-with-resource语句
所有实现Closeable接口的“资源”,均可采用try-with-resource进行简化。
普通:
BufferedReaderreader=null; try{ reader=newBufferedReader(newFileReader("cities.csv")); Stringline; while((line=reader.readLine())!=null){ //TODO:处理line } }catch(IOExceptione){ log.error("读取文件异常",e); }finally{ if(reader!=null){ try{ reader.close(); }catch(IOExceptione){ log.error("关闭文件异常",e); } } }
精简:
try(BufferedReaderreader=newBufferedReader(newFileReader("test.txt"))){ Stringline; while((line=reader.readLine())!=null){ //TODO:处理line } }catch(IOExceptione){ log.error("读取文件异常",e); }
1.4.利用return关键字
利用return关键字,可以提前函数返回,避免定义中间变量。
普通:
publicstaticbooleanhasSuper(@NonNullListuserList){ booleanhasSuper=false; for(UserDOuser:userList){ if(Boolean.TRUE.equals(user.getIsSuper())){ hasSuper=true; break; } } returnhasSuper; }
精简:
publicstaticbooleanhasSuper(@NonNullListuserList){ for(UserDOuser:userList){ if(Boolean.TRUE.equals(user.getIsSuper())){ returntrue; } } returnfalse; }
1.5.利用static关键字
利用static关键字,可以把字段变成静态字段,也可以把函数变为静态函数,调用时就无需初始化类对象。
普通:
publicfinalclassGisHelper{ publicdoubledistance(doublelng1,doublelat1,doublelng2,doublelat2){ //方法实现代码 } } GisHelpergisHelper=newGisHelper(); doubledistance=gisHelper.distance(116.178692D,39.967115D,116.410778D,39.899721D);
精简:
publicfinalclassGisHelper{ publicstaticdoubledistance(doublelng1,doublelat1,doublelng2,doublelat2){ //方法实现代码 } } doubledistance=GisHelper.distance(116.178692D,39.967115D,116.410778D,39.899721D);
1.6.利用lambda表达式
Java8发布以后,lambda表达式大量替代匿名内部类的使用,在简化了代码的同时,更突出了原有匿名内部类中真正有用的那部分代码。
普通:
newThread(newRunnable(){ publicvoidrun(){ //线程处理代码 } }).start();
精简:
newThread(()->{ //线程处理代码 }).start();
1.7.利用方法引用
方法引用(::),可以简化lambda表达式,省略变量声明和函数调用。
普通:
Arrays.sort(nameArray,(a,b)->a.compareToIgnoreCase(b)); ListuserIdList=userList.stream() .map(user->user.getId()) .collect(Collectors.toList());
精简:
Arrays.sort(nameArray,String::compareToIgnoreCase); ListuserIdList=userList.stream() .map(UserDO::getId) .collect(Collectors.toList());
1.8.利用静态导入
静态导入(importstatic),当程序中大量使用同一静态常量和函数时,可以简化静态常量和函数的引用。
普通:
ListareaList=radiusList.stream().map(r->Math.PI*Math.pow(r,2)).collect(Collectors.toList()); ...
精简:
importstaticjava.lang.Math.PI; importstaticjava.lang.Math.pow; importstaticjava.util.stream.Collectors.toList; ListareaList=radiusList.stream().map(r->PI*pow(r,2)).collect(toList()); ...
注意:静态引入容易造成代码阅读困难,所以在实际项目中应该警慎使用。
1.9.利用unchecked异常
Java的异常分为两类:Checked异常和Unchecked异常。Unchecked异常继承了RuntimeException,特点是代码不需要处理它们也能通过编译,所以它们称作 Unchecked异常。利用Unchecked异常,可以避免不必要的try-catch和throws异常处理。
普通:
@Service publicclassUserService{ publicvoidcreateUser(UserCreateVOcreate,OpUserVOuser)throwsBusinessException{ checkOperatorUser(user); ... } privatevoidcheckOperatorUser(OpUserVOuser)throwsBusinessException{ if(!hasPermission(user)){ thrownewBusinessException("用户无操作权限"); } ... } ... } @RestController @RequestMapping("/user") publicclassUserController{ @Autowired privateUserServiceuserService; @PostMapping("/createUser") publicResultcreateUser(@RequestBody@ValidUserCreateVOcreate,OpUserVOuser)throwsBusinessException{ userService.createUser(create,user); returnResult.success(); } ... }
精简:
@Service publicclassUserService{ publicvoidcreateUser(UserCreateVOcreate,OpUserVOuser){ checkOperatorUser(user); ... } privatevoidcheckOperatorUser(OpUserVOuser){ if(!hasPermission(user)){ thrownewBusinessRuntimeException("用户无操作权限"); } ... } ... } @RestController @RequestMapping("/user") publicclassUserController{ @Autowired privateUserServiceuserService; @PostMapping("/createUser") publicResultcreateUser(@RequestBody@ValidUserCreateVOcreate,OpUserVOuser){ userService.createUser(create,user); returnResult.success(); } ... }
2.利用注解
2.1.利用Lombok注解
Lombok提供了一组有用的注解,可以用来消除Java类中的大量样板代码。
普通:
publicclassUserVO{ privateLongid; privateStringname; publicLonggetId(){ returnthis.id; } publicvoidsetId(Longid){ this.id=id; } publicStringgetName(){ returnthis.name; } publicvoidsetName(Stringname){ this.name=name; } ... }
精简:
@Getter @Setter @ToString publicclassUserVO{ privateLongid; privateStringname; ... }
2.2.利用Validation注解
普通:
@Getter@Setter@ToStringpublicclassUserCreateVO{@NotBlank(message="用户名称不能为空")privateStringname;@NotNull(message="公司标识不能为空")privateLongcompanyId;...}@Service@ValidatedpublicclassUserService{publicLongcreateUser(@ValidUserCreateVOcreate){//TODO:创建用户returnnull;}}
精简:
@Getter @Setter @ToString publicclassUserCreateVO{ @NotBlank(message="用户名称不能为空") privateStringname; @NotNull(message="公司标识不能为空") privateLongcompanyId; ... } @Service @Validated publicclassUserService{ publicLongcreateUser(@ValidUserCreateVOcreate){ //TODO:创建用户 returnnull; } }
2.3.利用@NonNull注解
Spring的@NonNull注解,用于标注参数或返回值非空,适用于项目内部团队协作。只要实现方和调用方遵循规范,可以避免不必要的空值判断,这充分体现了阿里的“新六脉神剑”提倡的“因为信任,所以简单”。
普通:
publicListqueryCompanyUser(LongcompanyId){ //检查公司标识 if(companyId==null){ returnnull; } //查询返回用户 List userList=userDAO.queryByCompanyId(companyId); returnuserList.stream().map(this::transUser).collect(Collectors.toList()); } LongcompanyId=1L; List userList=queryCompanyUser(companyId); if(CollectionUtils.isNotEmpty(userList)){ for(UserVOuser:userList){ //TODO:处理公司用户 } }
精简:
public@NonNullListqueryCompanyUser(@NonNullLongcompanyId){ List userList=userDAO.queryByCompanyId(companyId); returnuserList.stream().map(this::transUser).collect(Collectors.toList()); } LongcompanyId=1L; List userList=queryCompanyUser(companyId); for(UserVOuser:userList){ //TODO:处理公司用户 }
2.4.利用注解特性
注解有以下特性可用于精简注解声明:
1、当注解属性值跟默认值一致时,可以删除该属性赋值;
2、当注解只有value属性时,可以去掉value进行简写;
3、当注解属性组合等于另一个特定注解时,直接采用该特定注解。
普通:
@Lazy(true); @Service(value="userService") @RequestMapping(path="/getUser",method=RequestMethod.GET)
精简:
@Lazy @Service("userService") @GetMapping("/getUser")
3.利用泛型
3.1.泛型接口
在Java没有引入泛型前,都是采用Object表示通用对象,最大的问题就是类型无法强校验并且需要强制类型转换。
普通:
publicinterfaceComparable{ publicintcompareTo(Objectother); } @Getter @Setter @ToString publicclassUserVOimplementsComparable{ privateLongid; @Override publicintcompareTo(Objectother){ UserVOuser=(UserVO)other; returnLong.compare(this.id,user.id); } }
精简:
publicinterfaceComparable{ publicintcompareTo(Tother); } @Getter @Setter @ToString publicclassUserVOimplementsComparable { privateLongid; @Override publicintcompareTo(UserVOother){ returnLong.compare(this.id,other.id); } }
3.2.泛型类
普通:
@Getter @Setter @ToString publicclassIntPoint{ privateIntegerx; privateIntegery; } @Getter @Setter @ToString publicclassDoublePoint{ privateDoublex; privateDoubley; }
精简:
@Getter @Setter @ToString publicclassPoint{ privateTx; privateTy; }
3.3.泛型方法
普通:
publicstaticMapnewHashMap(String[]keys,Integer[]values){ //检查参数非空 if(ArrayUtils.isEmpty(keys)||ArrayUtils.isEmpty(values)){ returnCollections.emptyMap(); } //转化哈希映射 Map map=newHashMap<>(); intlength=Math.min(keys.length,values.length); for(inti=0;i 精简:
publicstaticMap newHashMap(K[]keys,V[]values){ //检查参数非空 if(ArrayUtils.isEmpty(keys)||ArrayUtils.isEmpty(values)){ returnCollections.emptyMap(); } //转化哈希映射 Map map=newHashMap<>(); intlength=Math.min(keys.length,values.length); for(inti=0;i 4.利用自身方法
4.1.利用构造方法
构造方法,可以简化对象的初始化和设置属性操作。对于属性字段较少的类,可以自定义构造方法。
普通:
@Getter @Setter @ToString publicclassPageDataVO{ privateLongtotalCount; privateList dataList; } PageDataVO pageData=newPageDataVO<>(); pageData.setTotalCount(totalCount); pageData.setDataList(userList); returnpageData; 精简:
@Getter @Setter @ToString @NoArgsConstructor @AllArgsConstructor publicclassPageDataVO{ privateLongtotalCount; privateList dataList; } returnnewPageDataVO<>(totalCount,userList); 注意:如果属性字段被替换时,存在构造函数初始化赋值问题。比如把属性字段title替换为nickname,由于构造函数的参数个数和类型不变,原有构造函数初始化语句不会报错,导致把原title值赋值给nickname。如果采用Setter方法赋值,编译器会提示错误并要求修复。
4.2.利用Set的add方法
利用Set的add方法的返回值,可以直接知道该值是否已经存在,可以避免调用contains方法判断存在。
普通:
以下案例是进行用户去重转化操作,需要先调用contains方法判断存在,后调用add方法进行添加。
SetuserIdSet=newHashSet<>(); List userVOList=newArrayList<>(); for(UserDOuserDO:userDOList){ if(!userIdSet.contains(userDO.getId())){ userIdSet.add(userDO.getId()); userVOList.add(transUser(userDO)); } } 精简:
SSetuserIdSet=newHashSet<>(); List userVOList=newArrayList<>(); for(UserDOuserDO:userDOList){ if(userIdSet.add(userDO.getId())){ userVOList.add(transUser(userDO)); } } 4.3.利用Map的computeIfAbsent方法
利用Map的computeIfAbsent方法,可以保证获取到的对象非空,从而避免了不必要的空判断和重新设置值。
普通:
Map>roleUserMap=newHashMap<>(); for(UserDOuserDO:userDOList){ LongroleId=userDO.getRoleId(); List userList=roleUserMap.get(roleId); if(Objects.isNull(userList)){ userList=newArrayList<>(); roleUserMap.put(roleId,userList); } userList.add(userDO); } 精简:
Map>roleUserMap=newHashMap<>(); for(UserDOuserDO:userDOList){ roleUserMap.computeIfAbsent(userDO.getRoleId(),key->newArrayList<>()) .add(userDO); } 4.4.利用链式编程
链式编程,也叫级联式编程,调用对象的函数时返回一个this对象指向对象本身,达到链式效果,可以级联调用。链式编程的优点是:编程性强、可读性强、代码简洁。
普通:
StringBuilderbuilder=newStringBuilder(96); builder.append("selectid,namefrom"); builder.append(T_USER); builder.append("whereid="); builder.append(userId); builder.append(";");精简:
StringBuilderbuilder=newStringBuilder(96); builder.append("selectid,namefrom") .append(T_USER) .append("whereid=") .append(userId) .append(";");5.利用工具方法
5.1.避免空值判断
普通:
if(userList!=null&&!userList.isEmpty()){ //TODO:处理代码 }精简:
if(CollectionUtils.isNotEmpty(userList)){ //TODO:处理代码 }5.2.避免条件判断
普通:
doubleresult; if(value<=MIN_LIMIT){ result=MIN_LIMIT; }else{ result=value; }精简:
doubleresult=Math.max(MIN_LIMIT,value);5.3.简化赋值语句
普通:
publicstaticfinalListANIMAL_LIST; static{ List animalList=newArrayList<>(); animalList.add("dog"); animalList.add("cat"); animalList.add("tiger"); ANIMAL_LIST=Collections.unmodifiableList(animalList); } 精简:
//JDK流派 publicstaticfinalListANIMAL_LIST=Arrays.asList("dog","cat","tiger"); //Guava流派 publicstaticfinalList ANIMAL_LIST=ImmutableList.of("dog","cat","tiger"); 注意:Arrays.asList返回的List并不是ArrayList,不支持add等变更操作。
5.4.简化数据拷贝
普通:
UserVOuserVO=newUserVO(); userVO.setId(userDO.getId()); userVO.setName(userDO.getName()); ... userVO.setDescription(userDO.getDescription()); userVOList.add(userVO);精简:
UserVOuserVO=newUserVO(); BeanUtils.copyProperties(userDO,userVO); userVOList.add(userVO);反例:
ListuserVOList=JSON.parseArray(JSON.toJSONString(userDOList),UserVO.class); 精简代码,但不能以过大的性能损失为代价。例子是浅层拷贝,用不着JSON这样重量级的武器。
5.5.简化异常断言
普通:
if(Objects.isNull(userId)){ thrownewIllegalArgumentException("用户标识不能为空"); }精简:
Assert.notNull(userId,"用户标识不能为空");注意:可能有些插件不认同这种判断,导致使用该对象时会有空指针警告。
5.6.简化测试用例
把测试用例数据以JSON格式存入文件中,通过JSON的parseObject和parseArray方法解析成对象。虽然执行效率上有所下降,但可以减少大量的赋值语句,从而精简了测试代码。
普通:
@Test publicvoidtestCreateUser(){ UserCreateVOuserCreate=newUserCreateVO(); userCreate.setName("Changyi"); userCreate.setTitle("Developer"); userCreate.setCompany("AMAP"); ... LonguserId=userService.createUser(OPERATOR,userCreate); Assert.assertNotNull(userId,"创建用户失败"); }精简:
@Test publicvoidtestCreateUser(){ StringjsonText=ResourceHelper.getResourceAsString(getClass(),"createUser.json"); UserCreateVOuserCreate=JSON.parseObject(jsonText,UserCreateVO.class); LonguserId=userService.createUser(OPERATOR,userCreate); Assert.assertNotNull(userId,"创建用户失败"); }建议:JSON文件名最好以被测试的方法命名,如果有多个版本可以用数字后缀表示。
5.7.简化算法实现
一些常规算法,已有现成的工具方法,我们就没有必要自己实现了。
普通:
inttotalSize=valueList.size(); List>partitionList=newArrayList<>(); for(inti=0;i
精简:
List>partitionList=ListUtils.partition(valueList,PARTITION_SIZE);
5.8.封装工具方法
一些特殊算法,没有现成的工具方法,我们就只好自己亲自实现了。
普通:
比如,SQL设置参数值的方法就比较难用,setLong方法不能设置参数值为null。
//设置参数值 if(Objects.nonNull(user.getId())){ statement.setLong(1,user.getId()); }else{ statement.setNull(1,Types.BIGINT); } ...精简:
我们可以封装为一个工具类SqlHelper,简化设置参数值的代码。
/**SQL辅助类*/ publicfinalclassSqlHelper{ /**设置长整数值*/ publicstaticvoidsetLong(PreparedStatementstatement,intindex,Longvalue)throwsSQLException{ if(Objects.nonNull(value)){ statement.setLong(index,value.longValue()); }else{ statement.setNull(index,Types.BIGINT); } } ... } //设置参数值 SqlHelper.setLong(statement,1,user.getId());6.利用数据结构
6.1.利用数组简化
对于固定上下限范围的if-else语句,可以用数组+循环来简化。
普通:
publicstaticintgetGrade(doublescore){ if(score>=90.0D){ return1; } if(score>=80.0D){ return2; } if(score>=60.0D){ return3; } if(score>=30.0D){ return4; } return5; }精简:
privatestaticfinaldouble[]SCORE_RANGES=newdouble[]{90.0D,80.0D,60.0D,30.0D}; publicstaticintgetGrade(doublescore){ for(inti=0;i=SCORE_RANGES[i]){ returni+1; } } returnSCORE_RANGES.length+1; } 思考:上面的案例返回值是递增的,所以用数组简化是没有问题的。但是,如果返回值不是递增的,能否用数组进行简化呢?答案是可以的,请自行思考解决。
6.2.利用Map简化
对于映射关系的if-else语句,可以用Map来简化。此外,此规则同样适用于简化映射关系的switch语句。
普通:
publicstaticStringgetBiologyClass(Stringname){ switch(name){ case"dog": return"animal"; case"cat": return"animal"; case"lavender": return"plant"; ... default: returnnull; } }精简:
privatestaticfinalMapBIOLOGY_CLASS_MAP =ImmutableMap. builder() .put("dog","animal") .put("cat","animal") .put("lavender","plant") ... .build(); publicstaticStringgetBiologyClass(Stringname){ returnBIOLOGY_CLASS_MAP.get(name); } 已经把方法简化为一行代码,其实都没有封装方法的必要了。
6.3.利用容器类简化
Java不像Python和Go,方法不支持返回多个对象。如果需要返回多个对象,就必须自定义类,或者利用容器类。常见的容器类有Apache的Pair类和Triple类,Pair类支持返回2个对象,Triple类支持返回3个对象。
普通:
@Setter @Getter @ToString @AllArgsConstructor publicstaticclassPointAndDistance{ privatePointpoint; privateDoubledistance; } publicstaticPointAndDistancegetNearest(Pointpoint,Point[]points){ //计算最近点和距离 ... //返回最近点和距离 returnnewPointAndDistance(nearestPoint,nearestDistance); }精简:
publicstaticPairgetNearest(Pointpoint,Point[]points){ //计算最近点和距离 ... //返回最近点和距离 returnImmutablePair.of(nearestPoint,nearestDistance); } 6.4.利用ThreadLocal简化
ThreadLocal提供了线程专有对象,可以在整个线程生命周期中随时取用,极大地方便了一些逻辑的实现。用ThreadLocal保存线程上下文对象,可以避免不必要的参数传递。
普通:
由于DateFormat的format方法线程非安全(建议使用替代方法),在线程中频繁初始化DateFormat性能太低,如果考虑重用只能用参数传入DateFormat。例子如下:
publicstaticStringformatDate(Datedate,DateFormatformat){ returnformat.format(date); } publicstaticListgetDateList(DateminDate,DatemaxDate,DateFormatformat){ List dateList=newArrayList<>(); Calendarcalendar=Calendar.getInstance(); calendar.setTime(minDate); StringcurrDate=formatDate(calendar.getTime(),format); StringmaxsDate=formatDate(maxDate,format); while(currDate.compareTo(maxsDate)<=0){ dateList.add(currDate); calendar.add(Calendar.DATE,1); currDate=formatDate(calendar.getTime(),format); } returndateList; } 精简:
可能你会觉得以下的代码量反而多了,如果调用工具方法的地方比较多,就可以省下一大堆DateFormat初始化和传入参数的代码。
privatestaticfinalThreadLocalLOCAL_DATE_FORMAT=newThreadLocal (){ @Override protectedDateFormatinitialValue(){ returnnewSimpleDateFormat("yyyyMMdd"); } }; publicstaticStringformatDate(Datedate){ returnLOCAL_DATE_FORMAT.get().format(date); } publicstaticList getDateList(DateminDate,DatemaxDate){ List dateList=newArrayList<>(); Calendarcalendar=Calendar.getInstance(); calendar.setTime(minDate); StringcurrDate=formatDate(calendar.getTime()); StringmaxsDate=formatDate(maxDate); while(currDate.compareTo(maxsDate)<=0){ dateList.add(currDate); calendar.add(Calendar.DATE,1); currDate=formatDate(calendar.getTime()); } returndateList; } 注意:ThreadLocal有一定的内存泄露的风险,尽量在业务代码结束前调用remove方法进行数据清除。
7.利用Optional
在Java8里,引入了一个Optional类,该类是一个可以为null的容器对象。
7.1.保证值存在
普通:
IntegerthisValue; if(Objects.nonNull(value)){ thisValue=value; }else{ thisValue=DEFAULT_VALUE; }精简:
IntegerthisValue=Optional.ofNullable(value).orElse(DEFAULT_VALUE);7.2.保证值合法
普通:
IntegerthisValue; if(Objects.nonNull(value)&&value.compareTo(MAX_VALUE)<=0){ thisValue=value; }else{ thisValue=MAX_VALUE; }精简:
IntegerthisValue=Optional.ofNullable(value) .filter(tempValue->tempValue.compareTo(MAX_VALUE)<=0).orElse(MAX_VALUE);7.3.避免空判断
普通:
Stringzipcode=null; if(Objects.nonNull(user)){ Addressaddress=user.getAddress(); if(Objects.nonNull(address)){ Countrycountry=address.getCountry(); if(Objects.nonNull(country)){ zipcode=country.getZipcode(); } } }精简:
tringzipcode=Optional.ofNullable(user).map(User::getAddress) .map(Address::getCountry).map(Country::getZipcode).orElse(null);8.利用Stream
流(Stream)是Java8的新成员,允许你以声明式处理数据集合,可以看成为一个遍历数据集的高级迭代器。流主要有三部分构成:获取一个数据源→数据转换→执行操作获取想要的结果。每次转换原有Stream对象不改变,返回一个新的Stream对象,这就允许对其操作可以像链条一样排列,形成了一个管道。流(Stream)提供的功能非常有用,主要包括匹配、过滤、汇总、转化、分组、分组汇总等功能。
8.1.匹配集合数据
普通:
booleanisFound=false; for(UserDOuser:userList){ if(Objects.equals(user.getId(),userId)){ isFound=true; break; } }精简:
booleanisFound=userList.stream() .anyMatch(user->Objects.equals(user.getId(),userId));8.2.过滤集合数据
普通:
ListresultList=newArrayList<>(); for(UserDOuser:userList){ if(Boolean.TRUE.equals(user.getIsSuper())){ resultList.add(user); } } 精简:
ListresultList=userList.stream() .filter(user->Boolean.TRUE.equals(user.getIsSuper())) .collect(Collectors.toList()); 8.3.汇总集合数据
普通:
doubletotal=0.0D; for(Accountaccount:accountList){ total+=account.getBalance(); }精简:
doubletotal=accountList.stream().mapToDouble(Account::getBalance).sum();8.4.转化集合数据
普通:
ListuserVOList=newArrayList<>(); for(UserDOuserDO:userDOList){ userVOList.add(transUser(userDO)); } 精简:
ListuserVOList=userDOList.stream() .map(this::transUser).collect(Collectors.toList()); 8.5.分组集合数据
普通:
Map>roleUserMap=newHashMap<>(); for(UserDOuserDO:userDOList){ roleUserMap.computeIfAbsent(userDO.getRoleId(),key->newArrayList<>()) .add(userDO); } 精简:
Map>roleUserMap=userDOList.stream() .collect(Collectors.groupingBy(UserDO::getRoleId)); 8.6.分组汇总集合
普通:
MaproleTotalMap=newHashMap<>(); for(Accountaccount:accountList){ LongroleId=account.getRoleId(); Doubletotal=Optional.ofNullable(roleTotalMap.get(roleId)).orElse(0.0D); roleTotalMap.put(roleId,total+account.getBalance()); } 精简:
roleTotalMap=accountList.stream().collect(Collectors.groupingBy(Account::getRoleId,Collectors.summingDouble(Account::getBalance)));8.7.生成范围集合
Python的range非常方便,Stream也提供了类似的方法。
普通:
int[]array1=newint[N]; for(inti=0;i精简:
int[]array1=IntStream.rangeClosed(1,N).toArray(); int[]array2=IntStream.iterate(1,n->n*2).limit(N).toArray();9.利用程序结构
9.1.返回条件表达式
条件表达式判断返回布尔值,条件表达式本身就是结果。
普通:
publicbooleanisSuper(LonguserId) UserDOuser=userDAO.get(userId); if(Objects.nonNull(user)&&Boolean.TRUE.equals(user.getIsSuper())){ returntrue; } returnfalse; }精简:
publicbooleanisSuper(LonguserId) UserDOuser=userDAO.get(userId); returnObjects.nonNull(user)&&Boolean.TRUE.equals(user.getIsSuper()); }9.2.最小化条件作用域
最小化条件作用域,尽量提出公共处理代码。
普通:
Resultresult=summaryService.reportWorkDaily(workDaily); if(result.isSuccess()){ Stringmessage="上报工作日报成功"; dingtalkService.sendMessage(user.getPhone(),message); }else{ Stringmessage="上报工作日报失败:"+result.getMessage(); log.warn(message); dingtalkService.sendMessage(user.getPhone(),message); }精简:
Stringmessage; Resultresult=summaryService.reportWorkDaily(workDaily); if(result.isSuccess()){ message="上报工作日报成功"; }else{ message="上报工作日报失败:"+result.getMessage(); log.warn(message); } dingtalkService.sendMessage(user.getPhone(),message);9.3.调整表达式位置
调整表达式位置,在逻辑不变的前提下,让代码变得更简洁。
普通1:
Stringline=readLine(); while(Objects.nonNull(line)){ ...//处理逻辑代码 line=readLine(); }普通2:
for(Stringline=readLine();Objects.nonNull(line);line=readLine()){ ...//处理逻辑代码 }精简:
Stringline; while(Objects.nonNull(line=readLine())){ ...//处理逻辑代码 }注意:有些规范可能不建议这种精简写法。
9.4.利用非空对象
在比较对象时,交换对象位置,利用非空对象,可以避免空指针判断。
普通:
privatestaticfinalintMAX_VALUE=1000; booleanisMax=(value!=null&&value.equals(MAX_VALUE)); booleanisTrue=(result!=null&&result.equals(Boolean.TRUE));精简:
privatestaticfinalIntegerMAX_VALUE=1000; booleanisMax=MAX_VALUE.equals(value); booleanisTrue=Boolean.TRUE.equals(result);10.利用设计模式
10.1.模板方法模式
模板方法模式(TemplateMethodPattern)定义一个固定的算法框架,而将算法的一些步骤放到子类中实现,使得子类可以在不改变算法框架的情况下重定义该算法的某些步骤。
普通:
@Repository publicclassUserValue{ /**值操作*/ @Resource(name="stringRedisTemplate") privateValueOperationsvalueOperations; /**值模式*/ privatestaticfinalStringKEY_FORMAT="Value:User:%s"; /**设置值*/ publicvoidset(Longid,UserDOvalue){ Stringkey=String.format(KEY_FORMAT,id); valueOperations.set(key,JSON.toJSONString(value)); } /**获取值*/ publicUserDOget(Longid){ Stringkey=String.format(KEY_FORMAT,id); Stringvalue=valueOperations.get(key); returnJSON.parseObject(value,UserDO.class); } ... } @Repository publicclassRoleValue{ /**值操作*/ @Resource(name="stringRedisTemplate") privateValueOperations valueOperations; /**值模式*/ privatestaticfinalStringKEY_FORMAT="Value:Role:%s"; /**设置值*/ publicvoidset(Longid,RoleDOvalue){ Stringkey=String.format(KEY_FORMAT,id); valueOperations.set(key,JSON.toJSONString(value)); } /**获取值*/ publicRoleDOget(Longid){ Stringkey=String.format(KEY_FORMAT,id); Stringvalue=valueOperations.get(key); returnJSON.parseObject(value,RoleDO.class); } ... } 精简:
publicabstractclassAbstractDynamicValue{ /**值操作*/ @Resource(name="stringRedisTemplate") privateValueOperationsvalueOperations; /**设置值*/ publicvoidset(Iid,Vvalue){ valueOperations.set(getKey(id),JSON.toJSONString(value)); } /**获取值*/ publicVget(Iid){ returnJSON.parseObject(valueOperations.get(getKey(id)),getValueClass()); } ... /**获取主键*/ protectedabstractStringgetKey(Iid); /**获取值类*/ protectedabstractClass getValueClass(); } @Repository publicclassUserValueextendsAbstractValue { /**获取主键*/ @Override protectedStringgetKey(Longid){ returnString.format("Value:User:%s",id); } /**获取值类*/ @Override protectedClass getValueClass(){ returnUserDO.class; } } @Repository publicclassRoleValueextendsAbstractValue { /**获取主键*/ @Override protectedStringgetKey(Longid){ returnString.format("Value:Role:%s",id); } /**获取值类*/ @Override protectedClass getValueClass(){ returnRoleDO.class; } } 10.2.建造者模式
建造者模式(BuilderPattern)将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示,这样的设计模式被称为建造者模式。
普通:
publicinterfaceDataHandler{ /**解析数据*/ publicTparseData(Recordrecord); /**存储数据*/ publicbooleanstoreData(List dataList); } public longexecuteFetch(StringtableName,intbatchSize,DataHandler dataHandler)throwsException{ //构建下载会话 DownloadSessionsession=buildSession(tableName); //获取数据数量 longrecordCount=session.getRecordCount(); if(recordCount==0){ return0; } //进行数据读取 longfetchCount=0L; try(RecordReaderreader=session.openRecordReader(0L,recordCount,true)){ //依次读取数据 Recordrecord; List dataList=newArrayList<>(batchSize); while((record=reader.read())!=null){ //解析添加数据 Tdata=dataHandler.parseData(record); if(Objects.nonNull(data)){ dataList.add(data); } //批量存储数据 if(dataList.size()==batchSize){ booleanisContinue=dataHandler.storeData(dataList); fetchCount+=batchSize; dataList.clear(); if(!isContinue){ break; } } } //存储剩余数据 if(CollectionUtils.isNotEmpty(dataList)){ dataHandler.storeData(dataList); fetchCount+=dataList.size(); dataList.clear(); } } //返回获取数量 returnfetchCount; } //使用案例 longfetchCount=odpsService.executeFetch("user",5000,newDataHandler(){ /**解析数据*/ @Override publicTparseData(Recordrecord){ UserDOuser=newUserDO(); user.setId(record.getBigint("id")); user.setName(record.getString("name")); returnuser; } /**存储数据*/ @Override publicbooleanstoreData(List dataList){ userDAO.batchInsert(dataList); returntrue; } }); 精简:
publiclongexecuteFetch(StringtableName,intbatchSize,Function dataParser,Function ,Boolean>dataStorage)throwsException{ //构建下载会话 DownloadSessionsession=buildSession(tableName); //获取数据数量 longrecordCount=session.getRecordCount(); if(recordCount==0){ return0; } //进行数据读取 longfetchCount=0L; try(RecordReaderreader=session.openRecordReader(0L,recordCount,true)){ //依次读取数据 Recordrecord; List
dataList=newArrayList<>(batchSize); while((record=reader.read())!=null){ //解析添加数据 Tdata=dataParser.apply(record); if(Objects.nonNull(data)){ dataList.add(data); } //批量存储数据 if(dataList.size()==batchSize){ BooleanisContinue=dataStorage.apply(dataList); fetchCount+=batchSize; dataList.clear(); if(!Boolean.TRUE.equals(isContinue)){ break; } } } //存储剩余数据 if(CollectionUtils.isNotEmpty(dataList)){ dataStorage.apply(dataList); fetchCount+=dataList.size(); dataList.clear(); } } //返回获取数量 returnfetchCount; } //使用案例 longfetchCount=odpsService.executeFetch("user",5000,record->{ UserDOuser=newUserDO(); user.setId(record.getBigint("id")); user.setName(record.getString("name")); returnuser; },dataList->{ userDAO.batchInsert(dataList); returntrue; }); 普通的建造者模式,实现时需要定义DataHandler接口,调用时需要实现DataHandler匿名内部类,代码较多较繁琐。而精简后的建造者模式,充分利用了函数式编程,实现时无需定义接口,直接使用Function接口;调用时无需实现匿名内部类,直接采用lambda表达式,代码较少较简洁。
10.3.代理模式
Spring中最重要的代理模式就是AOP(Aspect-OrientedProgramming,面向切面的编程),是使用JDK动态代理和CGLIB动态代理技术来实现的。
普通:
@Slf4j @RestController @RequestMapping("/user") publicclassUserController{ /**用户服务*/ @Autowired privateUserServiceuserService; /**查询用户*/ @PostMapping("/queryUser") publicResult>queryUser(@RequestBody@ValidUserQueryVOquery){ try{ PageDataVOpageData=userService.queryUser(query); returnResult.success(pageData); }catch(Exceptione){ log.error(e.getMessage(),e); returnResult.failure(e.getMessage()); } } ... } 精简1:
基于 @ControllerAdvice的异常处理:
@RestController @RequestMapping("/user") publicclassUserController{ /**用户服务*/ @Autowired privateUserServiceuserService; /**查询用户*/ @PostMapping("/queryUser") publicResult>queryUser(@RequestBody@ValidUserQueryVOquery){ PageDataVO pageData=userService.queryUser(query); returnResult.success(pageData); } ... } @Slf4j @ControllerAdvice publicclassGlobalControllerAdvice{ /**处理异常*/ @ResponseBody @ExceptionHandler(Exception.class) publicResult handleException(Exceptione){ log.error(e.getMessage(),e); returnResult.failure(e.getMessage()); } } 精简2:
基于AOP的异常处理:
//UserController代码同"精简1" @Slf4j @Aspect publicclassWebExceptionAspect{ /**点切面*/ @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)") privatevoidwebPointcut(){} /**处理异常*/ @AfterThrowing(pointcut="webPointcut()",throwing="e") publicvoidhandleException(Exceptione){ Resultresult=Result.failure(e.getMessage()); writeContent(JSON.toJSONString(result)); } ... } 11.利用删除代码
“少即是多”,“少”不是空白而是精简,“多”不是拥挤而是完美。删除多余的代码,才能使代码更精简更完美。
11.1.删除已废弃的代码
删除项目中的已废弃的包、类、字段、方法、变量、常量、导入、注解、注释、已注释代码、Maven包导入、MyBatis的SQL语句、属性配置字段等,可以精简项目代码便于维护。
普通:
importlombok.extern.slf4j.Slf4j; @Slf4j @Service publicclassProductService{ @Value("discardRate") privatedoublediscardRate; ... privateProductVOtransProductDO(ProductDOproductDO){ ProductVOproductVO=newProductVO(); BeanUtils.copyProperties(productDO,productVO); //productVO.setPrice(getDiscardPrice(productDO.getPrice())); returnproductVO; } privateBigDecimalgetDiscardPrice(BigDecimaloriginalPrice){ ... } }精简:
@Service publicclassProductService{ ... privateProductVOtransProductDO(ProductDOproductDO){ ProductVOproductVO=newProductVO(); BeanUtils.copyProperties(productDO,productVO); returnproductVO; } }11.2.删除接口方法的public对于接口(interface),所有的字段和方法都是public的,可以不用显式声明为public。普通:
publicinterfaceUserDAO{ publicLongcountUser(@Param("query")UserQueryquery); publicListqueryUser(@Param("query")UserQueryquery); } 11.2.删除接口方法的public
对于接口(interface),所有的字段和方法都是public的,可以不用显式声明为public。
普通:
publicinterfaceUserDAO{ publicLongcountUser(@Param("query")UserQueryquery); publicListqueryUser(@Param("query")UserQueryquery); } 精简:
publicinterfaceUserDAO{ LongcountUser(@Param("query")UserQueryquery); ListqueryUser(@Param("query")UserQueryquery); } 11.3.删除枚举构造方法的private
对于枚举(menu),构造方法都是private的,可以不用显式声明为private。
普通:
publicenumUserStatus{ DISABLED(0,"禁用"), ENABLED(1,"启用"); privatefinalIntegervalue; privatefinalStringdesc; privateUserStatus(Integervalue,Stringdesc){ this.value=value; this.desc=desc; } ... }精简:
publicenumUserStatus{ DISABLED(0,"禁用"), ENABLED(1,"启用"); privatefinalIntegervalue; privatefinalStringdesc; UserStatus(Integervalue,Stringdesc){ this.value=value; this.desc=desc; } ... }11.4.删除final类方法的final
对于final类,不能被子类继承,所以其方法不会被覆盖,没有必要添加final修饰。
普通:
publicfinalRectangleimplementsShape{ ... @Override publicfinaldoublegetArea(){ returnwidth*height; } }精简:
publicfinalRectangleimplementsShape{ ... @Override publicdoublegetArea(){ returnwidth*height; } }11.5.删除基类implements的接口
如果基类已implements某接口,子类没有必要再implements该接口,只需要直接实现接口方法即可。
普通:
publicinterfaceShape{ ... doublegetArea(); } publicabstractAbstractShapeimplementsShape{ ... } publicfinalRectangleextendsAbstractShapeimplementsShape{ ... @Override publicdoublegetArea(){ returnwidth*height; } }精简:
... publicfinalRectangleextendsAbstractShape{ ... @Override publicdoublegetArea(){ returnwidth*height; } }11.6.删除不必要的变量
不必要的变量,只会让代码看起来更繁琐。
普通:
publicBooleanexistsUser(LonguserId){ Booleanexists=userDAO.exists(userId); returnexists; }精简:
publicBooleanexistsUser(LonguserId){ returnuserDAO.exists(userId); }后记
古语又云:
有道无术,术尚可求也;有术无道,止于术。
意思是:有“道”而无“术”,“术”还可以逐渐获得;有“术”而无“道”,就可能止步于“术”了。所以,我们不要仅满足于从实践中总结“术”,因为“道”的表现形式是多变的;而应该上升到“道”的高度,因为“术”背后的道理是相通的。当遇到新的事物时,我们可以从理论中找到“道”、从实践中找出“术”,尝试着去认知新的事物。
到此这篇关于Java代码精简之道的文章就介绍到这了,更多相关Java代码精简内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!