- 代码既是给机器执行的,也是给人看的。
- 简洁、优雅的代码也许在性能上是等价的(有时也不绝对),但在可读性上会有很大的提升。对于理解业务逻辑,降低寻找已知或潜在bug的成本,都有很大的帮助。
- 阿里出了一篇长文描述了常用的11种方法来精简代码,此处对这些方法进行了重拍和分析。(原作地址:https://mp.weixin.qq.com/s/Icn5_RZzFHB9WsKip2ZZ6g)
- 该文档为指南/指引,而非规范。就好像生活中有些行为不违规,但体现了一个人的素质。
概览
- 初级:【11】删除代码,【9】程序结构
- 初中级:【1】语法,【2】注解,【7】Optional
- 中级:【3】泛型,【8】Stream,【5】工具方法,【4】自身方法,【6】数据结构
- 中高级:【10】设计模式(Lambda表达式)
---- 初级 ----
1. 利用删除代码
删除已废弃的代码
删除项目中的已废弃的包、类、字段、方法、变量、常量、导入、注解、注释、已注释代码、Maven包导入、MyBatis的SQL语句、属性配置字段等,可以精简项目代码便于维护。
删除接口方法的public
对于接口(interface),所有的字段和方法都是 public 的,可以不用显式声明为 public 。Idea对public都会置灰处理。
删除枚举构造方法的private
枚举的构造方法无需增加private关键字
删除final方法的final
对于 final 类,不能被子类继承,所以其方法不会被覆盖,没有必要添加 final 修饰。
删除基类implements的接口
如果基类已 implements 某接口,子类没有必要再 implements 该接口,只需要直接实现接口方法即可,除非是想明确该具体类实现了某接口。
删除不必要的变量
不必要的变量,只会让代码看起来更繁琐。
2. 利用程序结构
返回条件表达式
条件表达式判断返回布尔值,条件表达式本身就是结果。
最小化条件作用域
最小化条件作用域,尽量提出公共处理代码。
调整表达式位置
调整表达式位置,在逻辑不变的前提下,让代码变得更简洁。【不推荐】如果过于破坏可读性,例如在一行执行读取信息并判断结果。
利用非空对象
在比较对象时,交换对象位置,利用非空对象,可以避免空指针判断。
---- 初中级 ----
3. 利用语法
利用三元表达式
简单的if-else,特别是一句的条件代码块,要用三元表达式简化。
但要特别注意返回类型不一致和自动拆装箱特性引入的NPE问题
for-each语句
用新的for(Object o : collection)
替换老式的下标循环(如不需要下标)或迭代器while。IDEA可以通过collection.for/fori快速生成循环代码块。
此处要注意,对于ArrayList,fori和for-each方式差别不大,尤其是在需要下标时,推荐使用前者;而在LinkedList实现中,后者对前者优势巨大,必须使用后者进行遍历。
利用try-with-resource语句
所有实现 Closeable 接口的“资源”,均可采用 try-with-resource 进行简化。甚至可以支持在try的括号内有多个资源,在try块正常/异常结束后,会按声明相反的顺序关闭资源。
利用return关键字
利用 return 关键字,可以提前函数返回,避免定义中间变量。
利用static关键字
利用 static 关键字,可以把字段变成静态字段,也可以把函数变为静态函数,调用时就无需初始化类对象。工具类使用该方法简化操作,仅有方法,没有“状态”。
利用Lambda表达式
lambda 表达式大量替代匿名内部类的使用,在简化了代码的同时,更突出了原有匿名内部类中真正有用的那部分代码。IDEA会给出普通匿名类到Lambda表达式的修改建议。
利用方法引用
方法引用(::),可以简化 lambda 表达式,省略变量声明和函数调用。不熟悉的话只需要正常写Lambda表达式,IDEA会给出改进建议。不是所有类中的方法都可以在Lambda表达式中转换为方法引用,例如对方法的反向(!)使用
利用静态导入
静态导入(import static),当程序中大量使用同一静态常量和函数时,可以简化静态常量和函数的引用。
利用unchecked异常
Java 的异常分为两类:Checked 异常和 Unchecked 异常。Unchecked 异常继承了RuntimeException ,特点是代码不需要处理它们也能通过编译,所以它们称作 Unchecked 异常。利用 Unchecked 异常,可以避免不必要的 try-catch 和 throws 异常处理。
如果要使用unchecked异常,必须让自定义异常继承自RuntimeException
。由于这种异常不被编译器check,则必须有处理这种异常的意识,不论在哪个层级。
4. 利用注解
利用Lombok注解
Lombok 提供了一组有用的注解,可以用来消除Java类中的大量样板代码。但要注意Lombok的@Data注解内容过多的问题。
利用Validation注解
Valiation注解是Java原生提供的一组Bean校验注解,它是一个JSR380(Bean Validation 2.0)的规范定义的实现,基于Java8。
而Hibernate有个Hibernate Validator是对其规范的实现和扩展(即Java和Hibernate都实现了JSR380)。
正常情况下,springboot程序用starter-web可以引入,具体关系如下
利用Valiation可以大大简化对参数的校验,有一套自己的方法,可用于controller接口和普通方法bean的校验。
利用@NonNull注解
这条不推荐使用,Spring使用该注解实现的是JSR305,该规范已经被废弃。尽量使用JSR380的实现。
利用注解特性
注解有以下特性可用于精简注解声明:
1、当注解属性值跟默认值一致时,可以删除该属性赋值;
2、当注解只有value属性时,可以去掉value进行简写;
3、当注解属性组合等于另一个特定注解时,直接采用该特定注解。
5. 利用Optional
保证值存在
Optional的正确使用方法,以及orElse和orElseGet的区别
保证值合法
filter的使用
避免空判断
map的使用
---- 中级 ----
6. 利用泛型
泛型接口
利用泛型限定接口的方式,明确接口内方法的对象类型,减少类型转换以及随之而来的ClassCastException
泛型类
同泛型接口,作用于具体类。一方面是对字段的类型限定,另一方面可以提高类的复用性
泛型方法
用法比较稀少,主要在入参和出参上保持一个灵活性。
7. 利用Steam
匹配集合数据
anyMatch,allMatch
过滤集合数据
filter
汇总集合数据
sum,average,reduce
转化集合数据
collect.toMap, toList, toSet
分组集合数据
groupingBy
分组汇总集合
groupingBy + reduce
生成范围集合
range,limit
8. 利用数据结构
利用数组简化
对于固定上下限范围的 if-else 语句,可以用数组+循环来简化。其中数组可以直接使用下标,也可维护一个下标的函数f(i)获取对应值
利用Map转化
对于映射关系的 if-else 语句,可以用Map来简化。此外,此规则同样适用于简化映射关系的 switch 语句。
利用容器类简化
Java 不像 Python 和 Go ,方法不支持返回多个对象。如果需要返回多个对象,就必须自定义类,或者利用容器类。常见的容器类有 Apache 的 Pair 类和 Triple 类, Pair 类支持返回 2 个对象, Triple 类支持返回 3 个对象。
利用ThreadLocal简化
ThreadLocal 提供了线程专有对象,可以在整个线程生命周期中随时取用,极大地方便了一些逻辑的实现。用 ThreadLocal 保存线程上下文对象,可以避免不必要的参数传递。
注意:除非对ThreadLocal非常了解,在生产环境一定要慎用,以免引起不可控的问题。
9. 利用自身方法
利用构造方法
构造方法,可以简化对象的初始化(new一个对象)和设置属性操作(一堆set)。对于属性字段较少的类,可以自定义构造方法。
利用Set的add方法
利用 Set 的 add 方法的返回值,可以直接知道该值是否已经存在,可以避免调用 contains 方法判断存在。如果该集合在add前不存在该值,则返回true,即插入到集合的动作是成功的。这也就是为什么可以通过add方法返回值替代contains。
利用Map的computeIfAbsent方法
利用 Map 的 computeIfAbsent 方法,可以保证获取到的对象非空,从而避免了不必要的空判断和重新设置值。
典型应用是一个Map<K,List<V>>的结构,为了构造后面的list(map的value),就需要利用该函数,在K指示的List不存在时,new出一个对象再插入V即可。如果这个List已经存在,则直接加入,不会new出新对象。
利用链式编程
链式编程,也叫级联式编程,调用对象的函数时返回一个this对象指向对象本身,达到链式效果,可以级联调用。链式编程的优点是:编程性强、可读性强、代码简洁。
- StringBuilder的append方法
- Stream流的中间操作
-
Lombok的Builder注解
10. 利用工具方法
引入第三方库时的一些考量
- 不要重新发明轮子
- 能不引入就不引入第三方库(Java8可以替代JodaTime)
- 用大不用小,尽量选择开源社区流行的,Github上活跃的第三方库
- 注意平衡,不要为了一个小功能,引入一个庞大的库
避免空值判断
通过工具类的等值方法,可以避免现式判空和NPE隐患,Optional本质上也是一种工具方法
- Optional
- CollectionUtils
- StringUtils
避免条件判断
工具类提供的集合方法,根据方法名描述的功能,避免了条件判断
- Stream流的anyMatch,allMatch等
- Math.max/min
- StringUtils
- ArrayUtils
简化赋值语句
各种of,as等类似方法
-
Arrays.asList,注意返回list的坑的问题(只读没问题)
简化数据拷贝
- 利用反射
- BeanUtils.copyProperties(userDO, userVO);
简化异常断言
用断言替代判断+抛出异常的代码方式
- assert关键字
- Spring-core中的Assert
- Junit中的Assert/Assertions
简化测试用例
把测试用例数据以 JSON 格式存入文件中,通过 JSON 的 parseObject 和 parseArray 方法解析成对象。虽然执行效率上有所下降,但可以减少大量的赋值语句,从而精简了测试代码。
简化算法实现
不要重复发明轮子,先在系统中找是否有类似的,实在没有再自己实现
封装工具方法
- 工具类Utils,Helper的使用
- 独立于业务
---- 中高级 ----
11. 利用设计模式
模版方法模式
模板方法模式(Template Method Pattern)定义一个固定的算法框架,而将算法的一些步骤放到子类中实现,使得子类可以在不改变算法框架的情况下重定义该算法的某些步骤。
建造者模式
组合优于继承,用另外一个类描述抽象类中的模版方法,就是建造者。
代理模式
Spring 中最重要的代理模式就是 AOP (Aspect-Oriented Programming,面向切面的编程),是使用 JDK 动态代理和 CGLIB 动态代理技术来实现的。