1. 不要迷信静态代码扫描工具检测的结果
例如 SonarLint 部分检测项不准确,需要甄别。
其中一条,‘String 方法单个字符使用''比""效率高’,该条目有问题,用''和""效率差距不大,随便使用哪个,参考:
https://stackoverflow.com/questions/33646781/java-performance-string-indexofchar-vs-string-indexofsingle-string
2. 考虑使用阿里 p3c 插件
该插件和 SonarLint 不冲突,可同时使用。
3. switch 必须有 default
[阿里手册] 在一个 switch 块内,都必须包含一个 default 语句并且放在最后,即使空代码。
原因:1. 捕获意想不到的值 2. 处理默认情况 3. 告诉阅读代码的人你已经考虑了那种情况
4. 不允许魔法值
[阿里手册] 不允许任何魔法值 ( magic number,即未经预先定义的常量 ) 直接出现在代码中。
原因:1. 数值的意义难以理解 2. 数值需要变动时,可能要改不只一个地方
5. 工具类要有私有构造器
工具类是一些静态成员的集合,不希望被初始化,实例化对它没有任何意义。可以在私有构造器内部添加 throw new AssertionError(), 防止其被内部调用。
public class ClassExtraUtil { private ClassExtraUtil (){ } //..... }
参考:Effective Java 第二版 第四条 通过私有构造器强化不可实例化的能力
7. 不要使用同步的类:Vector、Hashtable、Stack、StringBuffer
说明:建议使用->不推荐使用
10. 日志取代 System.out.println 和 printStackTrace
11. 遵守普遍接受的命名惯例
[阿里手册] 常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
[Effective Java] 把标准的命名惯例当作一种内在的机制来看待,并且学者用它们作为第二特性。
Effective Java 第二版 第 56 条 遵守普遍接受的命名惯例
12. BigDecimal.valueOf(double val) 取 代 new BigDecimal(double val)
说明:
new BigDecimal(double x)会导致精度丢失。用 BigDecimal(String val)也可以。
BigDecimal.valueOf()底层实现:
public static BigDecimal valueOf(double val) { return new BigDecimal(Double.toString(val)); }
13. 不要忽略没有副作用的函数返回值
没有副作用的函数返回值被忽略,这种情况要么方法调用是没意义的,应该删除,要么源码的行为和预期不符。
if (content.length() > 1024) { content.substring(0,1024); }
14. 不要使用 catch 块中的“instanceof”测试异常类型
15. 集合操作前要判 null
说明:此处 payApplyEntities 传入方法中,可能会被设为 null,要防止 NPE。
16. 避免不必要的初始化
List<Object> lists;//不用 List<Object> lists = new ArrayList<>(); lists= xxxMapper.selectList(params);
17. 了解和使用类库
if (Collectoins.isEmpty(result)) { //不使用 result == null || result.iEmpty() throw new XxxException("xxxx"); }
参考:Effective Java 第二版 第 47 条 了解和使用类库
18. 提炼分解过长的方法
[阿里手册]
单个方法的总行数不超过 80 行。 说明:包括方法签名、结束右大括号、方法内代码、注释、
空行、回车及任何不可见字符的总行数不超过 80 行。
[重构]
我们要遵守这样一条原则:每当感觉需要以注释说明点什么的时候,我们就把需要说明的东
西写进一个独立的方法中,并以其用途命名。
[代码整洁之道]
函数的第一规则是短小,第二条规则还要更短小。函数应该做一件事。做好这件事。只做这一件事。
19. 移除不用的导入
20. 考虑“public static final”取代"public static" 说明:
因为被声明为"public static"的成员变量可以被任何对象修改,应该用 final 使其不可变。
21. 可合并的 if
说明:合并可折叠的 if 可提高代码可读性
22. 坚持使用 Override 注解
[阿里手册]
所有的覆写方法,必须加@Override 注解。
反例:getObject()与 get0bject()的问题。一个是字母的 O,一个是数字的 0,加@Override 可
以准确判断是否覆盖成功。另外,如果在抽象类中对方法签名进行修改,其实现类会马上编
译报错。
参考:Effective Java 第二版 第 36 条 坚持使用 Override 注解
23. Map 的 key 是枚举时用 EnumMap
说明:根据 Java Doc:Implementation note: All basic operations execute in constant time. They are likely (though not guaranteed) to be faster than their HashMap counterparts. EnumMap 可能比 HashMap 快。
24. 不要声明局部变量然后返回或抛出
return userService.selectList<params>; //List<UserEntity> lists = userService.selectList<params>; //return lists;
25. “<>”取代”<...>”
Map<String,Object> maps = new HashMap<>(); //Map<String,Object> maps = new HashMap<String,Objec>();
26. Boolean 值不要冗余
return string.length()<1024; //return string.length()<1024 ? true:false;
27. 考 虑 用 @GetMapping, @PostMapping 等 取 代 @RequestMapping
//@RequestMapping(value ="/getUser",method=RequestMethod.GET) @GetMapping("/getuser")
@ResponseBody public Response() getUserList(){ }
28. 不要在同一行声明多个变量
说明:在同一行声明多个变量不方便注释。
参考:https://www.oracle.com/technetwork/java/javase/documentation/codeconventions-141270.html#2991
29. 避免用对象引用访问静态成员或静态方法
Calendar date = Calendar.getInstance(); date.set(Calendar.DATE,date.getActualMaxinum(Calendar.DATE)); //date.set(Calendar.DATE,date.getActualMaxinum(date.DATE));
[阿里手册]
避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直
接用类名来访问即可。
30. 按正确顺序声明修饰符
说明:
Java 语言规范推荐按以下顺序声明修饰符:
1. Annotations 2. public 3. protected 4. private 5. abstract 6. static
7. final 8. transient 9. volatile 10. synchronized 11. native 12. strictfp
31. 需要同时遍历 Map 的 Key 和 Value 时用 entrySet()
//修改前 for (Object key : header.keySet()) { method.setHeader(String.valueOf(key),String.valueOf(header.get(key))) } //修改后 for (Map.Entry<String,Object> entry : header.entrySet()) { method.setHeader(entry.getKey(),String.valueOf(entry.getValue())) }
32. 不要忽略异常
参考:Effective Java 第二版 第 65 条 不要忽略异常
33. 不要将只包含一条语句的 lambda 嵌套在一个块中
34. 不要在循环中使用“+”连接字符串
[阿里手册] 循环体内,字符串的联接方式,使用 StringBuilder 的 append 方法进行扩展。
说明:反编译出的字节码文件显示每次循环都会 new 出一个 StringBuilder 对象,然后进行
append 操作,最后通过 toString 方法返回 String 对象,造成内存资源浪费。
36. 不抛出原生异常
说明:不抛出原生异常,包括 Error, RuntimeException, Throwable, and Exception,抛出单独的异常类型可以让调用代码时控制如何处理每个异常。此处应该抛出我们自定义的异常
BusinessException。
37. 谨慎注释掉代码
说明:
[代码整洁之道]
直接把代码注释掉是讨厌的做法。别这么干!
其他人不敢注释掉的代码。他们会想,代码依然放在那儿,一定有其原因,而且这段代码很
重要,不能删除。注释掉的代码堆积在一起,就像破酒瓶底的渣滓一般。
我们已经拥有优良的源代码控制系统如此之久,这些系统可以为我们记住不要的代码。我们
无需用注释来标记,删掉即可,它们丢不了。
[阿里手册]
谨慎注释掉代码。在上方详细说明,而不是简单地注释掉。如果无用,则删除。代码被注释
掉有两种可能性:
(1) 后续会恢复此段代码逻辑。
(2) 永久不用。前者如果没有备注信息,难以知晓注释动机。后者建议直接删掉 ( 代码
仓库保存了历史代码 ) 。
38. long 或者 Long 初始赋值时必须用大写的 L
[阿里手册] long 或者 Long 初始赋值时,必须使用大写的 L,不能是小写的 l,小写容易跟
数字 1 混淆,造成误解。
39. 在 if/else/for/while/do 语句中必须使用大括号
[阿里手册] 在 if/else/for/while/do 语句中必须使用大括号,即使只有一行代码,避免使用下
面的形式:if (condition) statements;
40. 使用常量或确定有值的对象来调用 equals
[阿里手册] Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用
equals。
41. 不要使用行尾注释
[阿里手册] 方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注
释使用/* */注释。注意与代码对齐。
42. 简化 stream API 调用链
说明:
stream API 调用链可以被简化,这样可以在遍历集合时避免创建重复的临时对象。
以下调用链可以被替代:
collection.stream().forEach() → collection.forEach()
collection.stream().collect(toList/toSet/toCollection()) → new CollectionType<>(collection)
collection.stream().toArray() → collection.toArray()
Arrays.asList().stream() → Arrays.stream() or Stream.of()
IntStream.range(0, array.length).mapToObj(idx -> array[idx]) → Arrays.stream(array)
IntStream.range(0, list.size()).mapToObj(idx -> list.get(idx)) → list.stream()
Collections.singleton().stream() → Stream.of()
Collections.emptyList().stream() → Stream.empty()
stream.filter().findFirst().isPresent() → stream.anyMatch()
stream.collect(counting()) → stream.count()
stream.collect(maxBy()) → stream.max()
stream.collect(mapping()) → stream.map().collect()
stream.collect(reducing()) → stream.reduce()
stream.collect(summingInt()) → stream.mapToInt().sum()
stream.mapToObj(x -> x) → stream.boxed()
stream.map(x -> {...; return x;}) → stream.peek(x -> ...)
!stream.anyMatch() → stream.noneMatch()
!stream.anyMatch(x -> !(...)) → stream.allMatch()
stream.map().anyMatch(Boolean::booleanValue) -> stream.anyMatch()
IntStream.range(expr1, expr2).mapToObj(x -> array[x]) -> Arrays.stream(array, expr1, expr2)
Collection.nCopies(count, ...) -> Stream.generate().limit(count)
stream.sorted(comparator).findFirst() -> Stream.min(comparator)
43. 类、类属性、类方法的注释必须使用 javadoc 规范
[阿里手册] 类、类属性、类方法的注释必须使用 javadoc 规范,使用/**内容*/格式,不得使
用//xxx 方式和/*xxx*/方式。 在 IDE 编辑窗口中,javadoc 方式会提示相关注释,生成 javadoc
可以正确输出相应注释;在 IDE 中,工程调用方法时,不进入方法即可悬浮提示方法、参数、
返回值的意义,提高阅读效率。
44. 集合初始化时指定集合初始值大小
说明:HashMap 使用 new HashMap(int initialCapacity)构造方法进行初始化,如果暂时无法确
定集合大小,那么指定默认值(16)即可。
45. sql.xml 配置参数使用#{}
[阿里手册] sql. xml 配置参数使用:#{},# param # 不要使用${} 此种方式容易出现 SQL 注
入。
46. SQL 模糊查询参数绑定考虑使用 bind 标签
模糊查询一般有三种方式:
1. Java 代码里拼接匹配符: 代码和 SQL 耦合度高;查看 xml 不能直接看出查询条件,降低开
发效率;有可能在 service 和 facade 层多次加%_
2. SQL 里用 concat 拼接匹配符:增加数据库运算
3. 使用<bind>:Java 代码做连接,推荐使用
47. 使用 Optional 取代 null
48. 考虑使用不可变集合
说明:
不可变对象有很多优点,包括:
1.当对象被不可信的库调用时,不可变形式是安全的;
2.不可变对象被多个线程调用时,不存在竞态条件问题
3.不可变集合不需要考虑变化,因此可以节省时间和空间。所有不可变的集合都比它们的可
变形式有更好的内存利用率(分析和测试细节);
4.不可变对象因为有固定不变,可以作为常量来安全使用。
创建对象的不可变拷贝是一项很好的防御性编程技巧。Guava 为所有 JDK 标准集合类型和
Guava 新集合类型都提供了简单易用的不可变版本。
JDK 也提供了 Collections.unmodifiableXXX 方法把集合包装为不可变形式,但我们认为不够
好:
1.笨重而且累赘:不能舒适地用在所有想做防御性拷贝的场景;
2.不安全:要保证没人通过原集合的引用进行修改,返回的集合才是事实上不可变的;
3.低效:包装过的集合仍然保有可变集合的开销,比如并发修改的检查、散列表的额外空间,
等等。
如果你没有修改某个集合的需求,或者希望某个集合保持不变时,把它防御性地拷贝到
不可变集合是个很好的实践。
关于防御式编程,《代码大全》里“防御式编程”这一章有详细的介绍。防御式编程的
概念来自于防御式驾驶,在防御式驾驶中要建立这样一种思维,那就是你永远不能确定另一
位司机要做什么。这样才能确保其他人做出危险动作时你也不会受到伤害。对于不可变集合,
当作为参数传递到其他方法时,不用担心该集合会被改变,在当前方法可以放心继续使用该
集合。防御式编程的核心思想是承认程序都会有问题,都需要被修改。
参考:
https://github.com/google/guava/wiki/ImmutableCollectionsExplained
《代码大全》
49. 避免采用取反逻辑运算符
[阿里手册] 取反逻辑不利于快速理解,并且取反逻辑写法必然存在对应的正向逻辑写法。
50. 所有枚举类型字段必须要有注释
[阿里手册] 所有的枚举类型字段必须要有注释,说明每个数据项的用途
51. 遇到多个构造器参数时要考虑用构建器
说明:
[Effective Java] 如果类的构造器或者静态工厂中具有多个参数,设计这种类时,Builder 模式
就是种不错的选择,特别是当大多数参数都是可选的时候。与使用传统的重叠构造器模式相
比,使用 Builder 模式的客户端代码将更易于阅读和编写,构建器也比 JavaBeans 更加安全。
参考:
Effective Java 第二版 第 2 条 遇到多个构造器参数时要考虑用构建器
52. try-with-resources 优于 try-finally
说明:
在处理必须关闭的资源时,使用 try-with-resources 语句替代 try-finally 语句。 生成的代码更
简洁,更清晰,并且生成的异常更有用。try-with-resources 语句在编写必须关闭资源的代码
时会更容易,也不会出错,而使用 try-finally 可能会出错。
53. lambda 表达式优于匿名类
说明:
从 Java8 开始,lambda 表达式是表示小函数对象的最佳方式。除非必须创建非函数式接
口类型的实例,否则不要使用匿名类作为函数对象。另外,lambda 表达式使表示小函数对
象变得如此容易,可以使用函数式编程。
lambda 表达式没有名称和文档; 如果计算不是显而易见的,或者超过几行,则不要将其
放入 lambda 表达式中。一行代码对于 lambda 说是理想的,三行代码是合理的最大值。如
果违反这一规定,可能会严重损害程序的可读性。
参考:Effective Java 3rd Item 42: Prefer lambdas to anonymous classes
54. 方法引用优先于 Lambda 表达式
说明:方法引用通常为 lambda 提供一个更简洁的选择。 如果方法引用看起来更简短更清晰,请
使用它们;否则,还是坚持 lambda
参考:Effective Java 3rdItem 43: Prefer method references to lambdas
55. 优先使用标准的函数式接口
现在 Java 已经有了 lambda 表达式,因此必须考虑 lambda 表达式来设计你的 API。在输
入 上 接 受 函 数 式 接 口 类 型 并 在 输 出 中 返 回 它 们 。 一 般 来 说 , 最 好 使 用
java.util.function.Function 中提供的标准接口,但请注意,在相对罕见的情况下需要编写自己
的函数式接口。
在 java.util.Function 中有 43 个接口。不能指望全部记住它们,但是如果记住了六个基本
接口,就可以在需要它们时派生出其余的接口。六种基本函数式接口概述如下:
接口 方法 示例
UnaryOperator T apply(T t) String::toLowerCase
BinaryOperator T apply(T t1, T t2) BigInteger::add
Predicate boolean test(T t) Collection::isEmpty
Function<T,R> R apply(T t) Arrays::asList
Supplier T get() Instant::now
Consumer void accept(T t) System.out::println
参考:Effective Java 3rdItem 44: Favor the use of standard functional interfaces
56. 谨慎地使用 streams
Stream API 具有足够的通用性,实际上任何计算都可以使用 Stream 执行,但仅仅是可
以,并不意味着应该这样做。如果使用得当,流可以使程序更短更清晰;如果使用不当,过
度使用流会使程序难以阅读且难以维护。过度使用流使程序难于阅读和维护。
有些任务最好使用流来完成,有些任务最好使用迭代来完成。将这两种方法结合起来,
可以最好地完成许多任务。对于选择使用哪种方法进行任务,没有硬性规定,但是有一些有
用的启发式方法。在许多情况下,使用哪种方法将是清楚的;在某些情况下,则不会很清楚。
如果不确定一个任务是通过流还是迭代更好地完成,那么尝试这两种方法,看看哪一种效果
更好。
参考:Effective Java 3rdItem 45: Use streams judiciously
57. 优先考虑流中无副作用的函数
管道流编程的本质是无副作用的函数对象。这适用于传递给流和相关对象的所有许多函
数对象。终结操作 forEach 仅应用于报告流执行的计算结果,而不是用于执行计算。 为了
正确使用流,必须了解收集器。最重要的收集器工厂是 toList,toSet,toMap,groupingBy
和 join。
参考:Effective Java 3rdItem 46: Prefer side-effect-free functions in streams
58. 优先使用集合而不是流作为返回类型
在编写返回元素序列的方法时,请记住,某些用户可能希望将它们作为流处理,而其他
用户可能希望以迭代的方式来处理。尽量适应这两类人。如果返回集合是可行的,请执行此
操作。Collection 接口是 Iterable 的子类型,并且具有 stream 方法,因此它提供迭代和流访
问。因此,Collection 或适当的子类型通常是公共序列返回方法的最佳返回类型。如果返回
的元素是基本类型或有严格的性能要求,则使用数组,数组还使用 Arrays.asList 和 Stream.of
方法提供简单的迭代和流访问。如果返回集合是不可行的,则返回流或可迭代的,无论哪个
看起来更自然。
参考:Effective Java 3rdItem 47: Prefer Collection to Stream as a return type
59. 谨慎使用并行流
通常,并行性带来的性能优势在 ArrayList、HashMap、HashSet 和 ConcurrentHashMap
实例、数组、int 类型范围和 long 类型的范围的流上最好。这些数据结构的共同之处在于,
它们都可以精确而廉价地分割成任意大小的子程序,这使得在并行线程之间划分工作变得很
容易。
并行化一个流不仅会导致糟糕的性能,包括活性失败,还会导致不正确的结果和不可预
知的行为(安全故障)。使用映射器(mappers),过滤器(filters)和其他程序员提供的不符
合其规范的功能对象的管道并行化可能会导致安全故障。
总之,甚至不要尝试并行化管道流,除非你有充分的理由相信它将保持计算的正确性并
提高其速度。不恰当地并行化流的代价可能是程序失败或性能灾难。如果您认为并行性是合
理的,那么请确保您的代码在并行运行时保持正确,并在实际情况下进行仔细的性能度量。
如果您的代码是正确的,并且这些实验证实了您对性能提高的怀疑,那么只有这样才能在生
产环境代码中并行化流。
参考:Effective Java 3rdItem 48: Use caution when making streams parallel
60. 谨慎地返回 optionals
如果你发现在写一个不会总是返回一个值的方法,并且你认为重要的是,使用该方法的
人每次调用该方法时都要考虑到这种可能性,那么你就应该返回一个 Optional。但是,你应
该意识到返回 Optional 会影响性能,Optional 是一个需要被分配空间和初始化的对象,并且
从 Optional 中读取值需要额外的间接操作,所以对于性能敏感的方法返回 Optional 是不合适
的,最好是返回 null 或者抛出异常。最后,除了作为返回值以外,几乎不应该以任何其他方
式使用 Optional。
参考:Effective Java 3rdItem 55: Return optionals judiciously