Java编程规范

参考手册:file:///C:/Users/%E6%B1%A4%E5%BF%97%E8%B6%85/Desktop/CS%E5%AD%A6%E4%B9%A0/%E9%98%BF%E9%87%8C%E5%B7%B4%E5%B7%B4-JAVA%E5%BC%80%E5%8F%91%E6%89%8B%E5%86%8C-%E6%B3%B0%E5%B1%B1%E7%89%88.pdf

 

 

一、命名风格——视情况大小写+尽量采用惯例命名

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. 所有的枚举类型字段必须要有注释,说明每个数据项的用途;

 

 

 

 

 


 

posted @ 2023-12-04 20:56  Avava_Ava  阅读(13)  评论(0编辑  收藏  举报