坏习惯一:调用低效的构造器,创建包装类型的对象。
反例:
正解:
解惑:使用 Long.valueOf(long) 代替 new Long(long),可以提高性能。
如 Long 源码所示,如果当传入的值介于 -128~127 时,会优先从缓存中返回缓存的值,而不是进行 new,充分利用空间换取时间,所以当值介于 -128~127 时,采取 Long.valueOf(long) 的效率要比 new Long(long) 快很多。
建议:
a)凡是涉及到 Long, Integer, Short, Character 以及 Byte 创建对象时,优先采用高效的 valueOf() 方法,而不是直接用低效构造器创建实例。
b)享元设计模式在这儿用到了,什么是享元模式?
坏习惯二:使用 keySet 迭代器迭代 Map,获取对应的 value。
反例:
正解:
解惑:keySet 方式遍历 Map 的性能不如 entrySet 性能好。
如果采用 keySet 的方式获取 Map 中 key,然后通过 key 获取 Map 对应的 value,如上图 HashMap 源码所示,每次都需要通过 key 去计算对应的 hash 值,然后再通过 hash 值获取对应的 value,效率会低不少。
建议:
a)如果想获取 Map 对应的 key 和 value,则推荐使用 entrySet。
b)如果只是单纯获取 Map 对应的 key,则推荐使用 keySet。
坏习惯三:使用 new Date().getTime() 获取当前时间戳。
反例:
正解:
解惑:如下图 Date 源码所示,Date 构造方法中最终还是调用了 System.currentTimeMillis() 方法来获取时间戳。
建议:
a)获取当前毫秒数采用 System.currentTimeMillis(),而不是new Date().getTime();
b)获取更加精确的纳秒级时间值,采用 System.nanoTime;
c)在 JDK8 中,针对统计时间等场景,建议使用 Instant 类。
坏习惯四:循环中使用 ” “ 号拼接字符串。
反例:
正解:推荐使用 StringBuilder/StringBuffer 进行字符串拼接。
解惑:参考「Java 程序该怎么优化?技巧篇」本次不赘述。
编码时易犯的一些小毛病
毛病一:变量作为 equals() 方法的调用方。
反例:
正解:
解惑:totalCount 应该作为方法 equals() 的调用方,而不是参数 作为调用方,因为参数作为调用方会出现空指针异常。
建议:
a)字符串的比较,常量建议当做 equals() 方法的调用方;
b)字符串判断空,建议用项目中的工具类。
毛病二:对象为 null 的检查滞后。
反例1:
正解:请在使用 data 对象前,做好是否为 null 的判断。
反例2:
正解:请提前检查对象 fos、fis 是否为 null,应该在第一次使用前就做空值检查。
解惑:后置对象为空的检查,可能会导致空指针异常的发生。
毛病三:要求传入非空的方法,传入空值。
反例:
正解:signInfo 变量的值可能存在为空的情形,导致发生空指针异常。
建议:发生异常的时候,方法该终止就终止;尽量做好防御性编程,该校验的参数进行必要的校验。
寄语写最后
常在河边站哪有不湿鞋,再牛逼的码农,编码也会有失误的时候,很有必要借助一款代码检查工具,做最后一道防线。
在这里,推荐 FindBugs、Checkstyle、SonarQube 三款代码检查工具,不过我用的最多的当属 FindBugs,可以拿去一试,使用门槛几乎为零。
该如何写出优雅的代码?
编码时:少一点不行
坏习惯一:记录日志时,缺失参数。
反例:
正解:
- 日志打印时,占位符 {} 要严格与参数相对应,如果对应不上,按照截图示意,日志输出则不会打印 queryString 的参数,会直接输出 {},但是某些版本下会出现空指针异常。
2.说一句废话:图中的 isVarfiy 是什么鬼?莫非是 isVerify,单词好好拼,千万别拼错,不然易被后人拍砖。
坏习惯二:记录日志时,缺失占位符 {}。
反例:
正解:类似的这种问题,多数程序员都犯过。记录日志时占位符少,而参数值多,日志输出时想打印的参数,日志中却没有打印。
如上面截图中代码所示,想输出请求的 queryString,但是由于缺失对应的占位符 {},则不会打印到日志中。
坏习惯三:使用 switch 时,缺失 default。
正解:
- 采用 switch 时,当所有 case 都不匹配时,会走 default 逻辑。为了程序更完成、更优雅,在一个 switch 块内,都必须包含一个 default 语句并且放在最后,即使它什么代码也没有。
- 说一句废话:截图中的代码格式,尤其是 break 前的分号,你能忍受吗?
坏习惯四:使用 switch 时,缺失 break。
反例:
正解:
- 在一个 switch 块内,每个 case 要么通过 break/return 等来终止,要么注释说明程序将继续执行到哪一个 case 为止;
- 注意 break 是退出 switch 语句块,而 return 是退出方法体。
编码时:多一点不行
毛病一:看似判 null 很严谨,实则多余。
反例:
正解:这应该是吃过空指针的亏,刚 new 出来的对象,二话不说又判断对象是否为 null,真是多余的判断。
这么写并不能彰显代码很 new B(),反而使代码有失大雅。
毛病二:担心对象使用出现空指针,就疯狂 new。
反例:
正解:创建对象而没有使用,除了白白的浪费内存空间,如果在高并发情况下,效率、内存占用可想而知。
难道上面代码是为了 new B(),百思不得其真解。
毛病三:多分支对应功能却是一模一样。
反例:
正解:
解惑:功能相同的分支进行合并到一起,代码确实能简化不少,优雅不少。
毛病四:闲置不用的对象,到处都是。
反例 1:
反例 2:
正解:闲置不用的对象,到处都是,若留着就是耗内存,而且影响雅观,不用的变量、代码段建议删除。
寄语写最后
常在河边站哪有不湿鞋,金无足赤人无完人,再牛逼的团队,编码都会有出 Bug 的时候。近期微信公众号推出了一个专辑功能,而我迫不及待的想体验。
谁成想,当我点击创建专辑时,输入专辑名称「码农心声」等信息,然后点击保存,却发现列表页面出现了多个「码农心声」,而且赶紧截了个图,不知道是不是个 Bug?
But who cares?多出来的直接删除就行啦,又不影响使用。
书接上篇,本次一起继续探讨一下,该如何写出优雅的代码?
编码时:搞的复杂并不好
坏习惯一:多余的 if/else。
反例:
类似上面这种写法,if/else 就显得有点高射炮打蚊子,有的同学就会按照下面方式进行简化。
代码语言:javascript复制addBool = (i == 0) ? true : false;
这种方式简化当然跑起来没问题,代码确实简化了不少,但是还是略显冗余啊。
正解:
代码语言:javascript复制addBool = (i == 0);
坏习惯二:多余的 else。
反例:
仅以上图为例,每次看到类似截图中的代码,心里都发毛,完全可以提前 return,进而干掉 else 分支。
正解:
心声:
- 简单就是美,代码写的越少,犯错的几率就越小。
2. 提前终止程序,绝大多数情况下,会节省很多不必要的开销(会减少很多无效的判断,减少无效变量、对象的创建)。
- 每种编程语言都离不开 if/else 进行条件判断,如果在编码时,存在过多的 if/else 嵌套,代码的可读性就会下降,后期维护难度就会大大提高。
编码时:不善于用轮子
毛病一:随处可见的判空逻辑。
反例:
代码语言:javascript复制if(merId == null || "".equals(merId)) { //do something}
程序为了避免 NPE,很多时候都需要做非空检查,当然上面这种检查方式很有效,只是项目中有太多的属性字段等待去校验,如果到处都是类似的判断,确实有点不太雅观。
很多同学会想着,自己封装 StringUtils 工具类,其实更推荐大家使用三方的轮子。
推荐1:Apache commons-lang 工具包
代码语言:javascript复制if(StringUtils.isBlank(merId)) { //do something}
推荐2:谷歌的 Guava 工具包
代码语言:javascript复制if(Strings.isNullOrEmpty(merId)) { //do something}
心声:
1. Apache Commons 下面的工具包,用熟了,确实很香。
- 谷歌的 Guava 工具包也不错,该类库经过高度的优化,方便我们快速编码,能规避不少编码错误。
毛病二:完成对象间的属性 Copy,编写冗长的代码。
反例:
代码语言:javascript复制... ...batchEntity.setNotifyType(batchEntityOld.getNotifyType());batchEntity.setUpdatedTime(batchEntityOld.getUpdatedTime());batchEntity.setBizType(batchEntityOld.getBizType());batchEntity.setMerchId(batchEntityOld.getMerchId());... ...
正解:
方式 1:采用 Apache BeanUtils 完成属性赋值。
代码语言:javascript复制BeanUtils.copyProperties(batchEntityOld,batchEntity);
方式 2:采用 Spring BeanUtils 完成属性赋值。
代码语言:javascript复制BeanUtils.copyProperties(batchEntity,batchEntityOld);
对的,你没看错,方法名称、参数都一样,但是 target、source要注意(稍有不慎,就入坑啊!)
不过,这里更推荐使用 Spring BeanUtils,而且在阿里开发规约中也明确强制使用 Spring BeanUtils 完成属性的 copy。
另外,为什么不建议使用 Apache BeanUtils 呢?看看源码就知道啦。
性能问题,估计跟日志输出、类型判断、用 号进行字符串拼接等脱不了关系。
寄语写最后
精妙的代码简洁明了,如果将这个代码给其他程序员看,他们会说:“哇,这代码写得真好。”那感觉很像在写一首诗。
我等采石之人当心怀大教堂之愿景——《程序员修炼之道》。
在一个项目的整体结构之内,总有空间展示个性和匠心……百年之后,我们的技艺或许如今日的土建工程师看待中世纪大教堂建造者使用的技法一样陈旧,但是我们的匠心却会得到尊重——匠人精神。
本次继续探讨一下,该如何写出健壮的代码?
编码时:看似顺眼,实则不然。
举个栗子