Java代码精简指南

  • 代码既是给机器执行的,也是给人看的。
  • 简洁、优雅的代码也许在性能上是等价的(有时也不绝对),但在可读性上会有很大的提升。对于理解业务逻辑,降低寻找已知或潜在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 修饰。


【推荐】删除,注意Idea并没有置灰

删除基类implements的接口

如果基类已 implements 某接口,子类没有必要再 implements 该接口,只需要直接实现接口方法即可,除非是想明确该具体类实现了某接口。


【反例】可去掉不必要的声明

删除不必要的变量

不必要的变量,只会让代码看起来更繁琐。


【对比】不必要的变量声明

2. 利用程序结构

返回条件表达式

条件表达式判断返回布尔值,条件表达式本身就是结果。


【反例】可直接返回条件表达式

最小化条件作用域

最小化条件作用域,尽量提出公共处理代码。

【反例】需要最小化条件分支代码块

调整表达式位置

调整表达式位置,在逻辑不变的前提下,让代码变得更简洁。【不推荐】如果过于破坏可读性,例如在一行执行读取信息并判断结果。

利用非空对象

在比较对象时,交换对象位置,利用非空对象,可以避免空指针判断。


【推荐】避免value和result的NPE

---- 初中级 ----

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块正常/异常结束后,会按声明相反的顺序关闭资源。


【推荐】可直接写在try的后面

利用return关键字

利用 return 关键字,可以提前函数返回,避免定义中间变量。


【反例】没有特殊理由,可以直接return true,不需要hasSuper变量

利用static关键字

利用 static 关键字,可以把字段变成静态字段,也可以把函数变为静态函数,调用时就无需初始化类对象。工具类使用该方法简化操作,仅有方法,没有“状态”。

利用Lambda表达式

lambda 表达式大量替代匿名内部类的使用,在简化了代码的同时,更突出了原有匿名内部类中真正有用的那部分代码。IDEA会给出普通匿名类到Lambda表达式的修改建议。


【对比】注意精简编译器可以自己推断的部分

利用方法引用

方法引用(::),可以简化 lambda 表达式,省略变量声明和函数调用。不熟悉的话只需要正常写Lambda表达式,IDEA会给出改进建议。不是所有类中的方法都可以在Lambda表达式中转换为方法引用,例如对方法的反向(!)使用


【推荐】利用Idea的提示进行更新

利用静态导入

静态导入(import static),当程序中大量使用同一静态常量和函数时,可以简化静态常量和函数的引用。


【推荐】大量使用的常量可以通过静态引入减少代码量

利用unchecked异常

Java 的异常分为两类:Checked 异常和 Unchecked 异常。Unchecked 异常继承了RuntimeException ,特点是代码不需要处理它们也能通过编译,所以它们称作 Unchecked 异常。利用 Unchecked 异常,可以避免不必要的 try-catch 和 throws 异常处理。
如果要使用unchecked异常,必须让自定义异常继承自RuntimeException。由于这种异常不被编译器check,则必须有处理这种异常的意识,不论在哪个层级。

【推荐】createUser方法既不用try-catch,也不用throws异常

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可以引入,具体关系如下


starter-web中的validation包

利用Valiation可以大大简化对参数的校验,有一套自己的方法,可用于controller接口和普通方法bean的校验。


【推荐】普通类的validation方法

利用@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 语句。


【反例】用Map提前写好映射关系

利用容器类简化

Java 不像 Python 和 Go ,方法不支持返回多个对象。如果需要返回多个对象,就必须自定义类,或者利用容器类。常见的容器类有 Apache 的 Pair 类和 Triple 类, Pair 类支持返回 2 个对象, Triple 类支持返回 3 个对象。


【推荐】Commons lang3里的Pair实现

利用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注解


    【推荐】链式编程写入一个String

10. 利用工具方法

引入第三方库时的一些考量

  1. 不要重新发明轮子
  2. 能不引入就不引入第三方库(Java8可以替代JodaTime)
  3. 用大不用小,尽量选择开源社区流行的,Github上活跃的第三方库
  4. 注意平衡,不要为了一个小功能,引入一个庞大的库

避免空值判断

通过工具类的等值方法,可以避免现式判空和NPE隐患,Optional本质上也是一种工具方法

  • Optional
  • CollectionUtils
  • StringUtils

避免条件判断

工具类提供的集合方法,根据方法名描述的功能,避免了条件判断

  • Stream流的anyMatch,allMatch等
  • Math.max/min
  • StringUtils
  • ArrayUtils

简化赋值语句

各种of,as等类似方法

  • Arrays.asList,注意返回list的坑的问题(只读没问题)


    【特别注意】内部类ArrayList
【特别注意】实现了set方法,而没有实现add方法

简化数据拷贝

  • 利用反射
  • 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 动态代理技术来实现的。


【推荐】不在红框处catch异常
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,784评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,745评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,702评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,229评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,245评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,376评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,798评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,471评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,655评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,485评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,535评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,235评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,793评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,863评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,096评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,654评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,233评论 2 341