Java编程规范
一、命名风格——视情况大小写+尽量采用惯例命名
1.类名、方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格,其中类名首字母大写,其余首字母小写;
2.常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长;
3.杜绝完全不规范的缩写,避免望文不知义;
二、代码格式——什么时候该用空格,什么时候不应该
1. if/for/while/switch/do 等保留字与括号之间都必须加空格;
正例:
public static void main(String[] args) {
// 缩进 4 个空格
String say = "hello";
// 运算符的左右必须有一个空格
int flag = 0;
// 关键词 if 与括号之间必须有一个空格,括号内的 f 与左括号,0 与右括号不需要空格
if (flag == 0) {
System.out.println(say);
}
// 左大括号前加空格且不换行;左大括号后换行
if (flag == 1) {
System.out.println("world");
// 右大括号前换行,右大括号后有 else,不用换行
} else {
System.out.println("ok");
// 在右大括号后直接结束,则必须换行
}
}
2. 注释的双斜线与注释内容之间有且仅有一个空格;
3. 在进行类型强制转换时,右括号与强制转换值之间不需要任何空格隔开;
3. 方法参数在定义和传入时,多个参数逗号后边必须加空格;
4. IDE 的 text file encoding 设置为 UTF-8; IDE 中文件的换行符使用 Unix 格式,不要使用 Windows 格式
三、常量定义
1. 不允许任何魔法值(即未经预先定义的常量)直接出现在代码中;
2. 在 long 或者 Long 赋值时,数值后使用大写的 L,不能是小写的 l,小写容易跟数字1混淆,造成误解;
四、OOP 规约
1. Object 的 equals 方法容易抛空指针异常,推荐使用 java.util.Objects#equals(JDK7 引入的工具类);
2. 所有整型包装类对象之间值的比较,全部使用 equals 方法比较;
3. 浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals来判断,
反例:
float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
if (a == b) {
// 预期进入此代码快,执行其它业务逻辑
// 但事实上 a==b 的结果为 false
}
Float x = Float.valueOf(a);
Float y = Float.valueOf(b);
if (x.equals(y)) {
// 预期进入此代码快,执行其它业务逻辑
// 但事实上 equals 的结果为 false
}
正例:
(1) 指定一个误差范围,两个浮点数的差值在此范围之内,则认为是相等的。
float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
float diff = 1e-6f;
if (Math.abs(a - b) < diff) {
System.out.println("true");
}
(2) 使用 BigDecimal 来定义值,再进行浮点数的运算操作。
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
BigDecimal c = new BigDecimal("0.8");
BigDecimal x = a.subtract(b);
BigDecimal y = b.subtract(c);
if (x.equals(y)) {
System.out.println("true");
}
4. 禁止使用构造方法 BigDecimal(double)的方式把 double 值转化为 BigDecimal 对象。
说明:BigDecimal(double)存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常;
如:BigDecimal g = new BigDecimal(0.1f); 实际的存储值为:0.10000000149
正例:优先推荐入参为 String 的构造方法,或使用 BigDecimal 的 valueOf 方法,此方法内部其实执行了
Double 的 toString,而 Double 的 toString 按 double 的实际能表达的精度对尾数进行了截断。
BigDecimal recommend1 = new BigDecimal("0.1");
BigDecimal recommend2 = BigDecimal.valueOf(0.1);
5. 关于基本数据类型与包装数据类型的使用标准如下:
1) 【强制】所有的 POJO 类属性必须使用包装数据类型;
2) 【强制】RPC 方法的返回值和参数必须使用包装数据类型。
3) 【推荐】所有的局部变量使用基本数据类型。
6. 序列化类新增属性时,请不要修改 serialVersionUID 字段,避免反序列失败;如果完全不兼容升级,避免反序列化混乱,那么请修改 serialVersionUID 值。
说明:注意 serialVersionUID 不一致会抛出序列化运行时异常
7. 构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中;
8. 禁止在 POJO 类中,同时存在对应属性 xxx 的 isXxx()和 getXxx()方法。
说明:框架在调用属性 xxx 的提取方法时,并不能确定哪个方法一定是被优先调用到,神坑之一。
五、日期时间
1. 日期格式化时,传入 pattern 中表示年份统一使用小写的 y。
说明:日期格式化时,yyyy 表示当天所在的年,而大写的 YYYY 代表是 week in which year(JDK7 之后引入的概念),意思是当天所在的周属于的年份,一周从周日开始,周六结束,只要本周跨年,返回的 YYYY就是下一年。
正例:表示日期和时间的格式如下所示:
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
2. 在日期格式中分清楚大写的 M 和小写的 m,大写的 H 和小写的 h 分别指代的意义。
说明:日期格式中的这两对字母表意如下:
1) 表示月份是大写的 M;
2) 表示分钟则是小写的 m;
3) 24 小时制的是大写的 H;
4) 12 小时制的则是小写的 h。
3. 获取当前毫秒数:System.currentTimeMillis(); 而不是 new Date().getTime();
4. 不允许在程序任何地方中使用:1)java.sql.Date 2)java.sql.Time 3)java.sql.Timestamp。
说明:第 1 个不记录时间,getHours()抛出异常;第 2 个不记录日期,getYear()抛出异常;第 3 个在构造方法 super((time/1000)*1000),fastTime 和 nanos 分开存储秒和纳秒信息。
反例: java.util.Date.after(Date)进行时间比较时,当入参是 java.sql.Timestamp 时,会触发 JDKBUG(JDK9 已修复),可能导致比较时的意外结果
5. 不要在程序中写死一年为 365 天,避免在公历闰年时出现日期转换错误或程序逻辑错误;
正例:
// 获取今年的天数
int daysOfThisYear = LocalDate.now().lengthOfYear();
// 获取指定某年的天数
LocalDate.of(2011, 1, 1).lengthOfYear();
反例:
// 第一种情况:在闰年 366 天时,出现数组越界异常
int[] dayArray = new int[365];
// 第二种情况:一年有效期的会员制,今年 1 月 26 日注册,硬编码 365 返回的却是 1 月 25 日
Calendar calendar = Calendar.getInstance();
calendar.set(2020, 1, 26);
calendar.add(Calendar.DATE, 365);
六、集合处理
1. 关于 hashCode 和 equals 的处理,遵循如下规则:
1) 只要重写 equals,就必须重写 hashCode。
2) 因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须重写这两个方法。
3) 如果自定义对象作为 Map 的键,那么必须覆写 hashCode 和 equals。
说明:String 因为重写了 hashCode 和 equals 方法,所以我们可以愉快地使用 String 对象作为 key 来使用。
2. 判断所有集合内部的元素是否为空,使用 isEmpty()方法,而不是 size()==0 的方式;
说明:前者的时间复杂度为 O(1),而且可读性更好;
3. 在使用 java.util.stream.Collectors 类的 toMap()方法转为 Map 集合时,一定要使用含有参数类型为 BinaryOperator,参数名为 mergeFunction 的方法,
否则当出现相同 key值时会抛出 IllegalStateException 异常;
说明:参数 mergeFunction 的作用是当出现 key 重复时,自定义对 value 的处理策略;
正例:
public static void main(String[] args) {
List<Pair<String, Double>> pairArrayList = new ArrayList<>(3);
pairArrayList.add(new Pair<>("version", 6.19));
pairArrayList.add(new Pair<>("version", 10.24));
pairArrayList.add(new Pair<>("version", 13.14));
Map<String, Double> collect = pairArrayList.stream().collect(Collectors.toMap(Pair::getKey, Pair::getValue, new BinaryOperator<Double>() {
// BinaryOperator():若key值相同,保存哪一个value,是先存进去的aDouble,还是尚未存进去的aDouble2
@Override
public Double apply(Double aDouble, Double aDouble2) {
return aDouble2;
}
}));
// lambda表达式版本:
// Map<String, Double> collect = pairArrayList.stream().collect(Collectors.toMap(Pair::getKey, Pair::getValue, (aDouble, aDouble2) -> aDouble2));
// 如果是return aDouble2,那么结果为————key:version,value:13.14
// 如果是return aDouble,那么结果为————key:version,value:13.14
collect.forEach((s, aDouble) -> System.out.println("key:" + s + "," + "value:" + aDouble));
}
反例:
public static void main(String[] args) {
List<Pair<String, Double>> pairArrayList = new ArrayList<>(3);
pairArrayList.add(new Pair<>("version", 6.19));
pairArrayList.add(new Pair<>("version", 10.24));
pairArrayList.add(new Pair<>("version", 13.14));
// 报错:Exception in thread "main" java.lang.IllegalStateException: Duplicate key 6.19
Map<String, Double> collect = pairArrayList.stream().collect(Collectors.toMap(Pair::getKey, Pair::getValue));
collect.forEach((s, aDouble) -> System.out.println("key:" + s + "," + "value:" + aDouble));
}
4. 在使用 java.util.stream.Collectors 类的 toMap()方法转为 Map 集合时,一定要注意当 value 为 null 时会抛 NPE 异常;
说明:在 java.util.HashMap 的 merge 方法里会进行如下的判断:
if (value == null || remappingFunction == null)
throw new NullPointerException();
反例:
List<Pair<String, Double>> pairArrayList = new ArrayList<>(2);
pairArrayList.add(new Pair<>("version1", 4.22));
pairArrayList.add(new Pair<>("version2", null));
Map<String, Double> map = pairArrayList.stream().collect(
// 抛出 NullPointerException 异常
Collectors.toMap(Pair::getKey, Pair::getValue, (v1, v2) -> v2));
5. ArrayList 的 subList 结果不可强转成 ArrayList,否则会抛出 ClassCastException 异常:java.util.RandomAccessSubList cannot be cast to java.util.ArrayList。
说明:subList 返回的是 ArrayList 的内部类 SubList,并不是 ArrayList 而是 ArrayList 的一个视图,对于 SubList 子列表的所有操作最终会反映到原列表上
6. 使用 Map 的方法 keySet()/values()/entrySet()返回集合对象时,不可以对其进行添加元素操作,否则会抛出 UnsupportedOperationException 异常;
7. Collections 类返回的对象,如:emptyList()/singletonList()等都是 immutable list,不可对其进行添加或者删除元素的操作。
反例:如果查询无结果,返回 Collections.emptyList()空集合对象,调用方一旦进行了添加元素的操作,就会触发 UnsupportedOperationException 异常。
8.
七、并发处理
八、控制语句
1. 在一个 switch 块内,每个 case 要么通过 continue/break/return 等来终止,要么注释说明程序将继续执行到哪一个 case 为止;在一个 switch 块内,
都必须包含一个 default语句并且放在最后,即使它什么代码也没有。
说明:注意 break 是退出 switch 语句块,而 return 是退出方法体。
2. 当 switch 括号内的变量类型为 String 并且此变量为外部参数时,必须先进行 null判断;
反例:如下的代码输出是什么?
public class SwitchString {
public static void main(String[] args) {
method(null);
}
public static void method(String param) {
switch (param) {
// 肯定不是进入这里
case "sth":
System.out.println("it's sth");
break;
// 也不是进入这里
case "null":
System.out.println("it's null");
break;
// 也不是进入这里
default:
System.out.println("default");
}
}
}
3. 在 if/else/for/while/do 语句中必须使用大括号。
说明:即使只有一行代码,禁止不采用大括号的编码方式:if (condition) statements;
4. 三目运算符 condition? 表达式 1 : 表达式 2 中,高度注意表达式 1 和 2 在类型对齐时,可能抛出因自动拆箱导致的 NPE 异常;
说明:以下两种场景会触发类型对齐的拆箱操作:
1) 表达式 1 或表达式 2 的值只要有一个是原始类型。
2) 表达式 1 或表达式 2 的值的类型不一致,会强制拆箱升级成表示范围更大的那个类型。
反例:
Integer a = 1;
Integer b = 2;
Integer c = null;
Boolean flag = false;
// a*b 的结果是 int 类型,那么 c 会强制拆箱成 int 类型,抛出 NPE 异常
Integer result=(flag? a*b : c);
5. 在高并发场景中,避免使用”等于”判断作为中断或退出的条件。
说明:如果并发控制没有处理好,容易产生等值判断被“击穿”的情况,使用大于或小于的区间判断条件来代替;
反例:判断剩余奖品数量等于 0 时,终止发放奖品,但因为并发处理错误导致奖品数量瞬间变成了负数,这样的话,活动无法终止;
九、注释规约
1. 类、类属性、类方法的注释必须使用 Javadoc 规范,使用/**内容*/格式,不得使用// xxx 方式。
说明:在 IDE 编辑窗口中,Javadoc 方式会提示相关注释,生成 Javadoc 可以正确输出相应注释;
在 IDE中,工程调用方法时,不进入方法即可悬浮提示方法、参数、返回值的意义,提高阅读效率;
2. 所有的抽象方法(包括接口中的方法)必须要用 Javadoc 注释、除了返回值、参数、异常说明外,还必须指出该方法做什么事情,实现什么功能。
说明:对子类的实现要求,或者调用注意事项,请一并说明
3. 所有的类都必须添加创建者和创建日期。
说明:在设置模板时,注意 IDEA 的@author 为`${USER}`,而 eclipse 的@author 为`${user}`,大小写有区别,而日期的设置统一为 yyyy/MM/dd 的格式。
正例:
/**
* @author yangguanbao
* @date 2016/10/31
*/
4. 方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释使用/* */注释,注意与代码对齐;
5. 所有的枚举类型字段必须要有注释,说明每个数据项的用途;