Java开发手册(alibaba黄山版)-2022重点汇总

强制

1.所有对象的命名均不能以下划线或美元符号$开始以及结束
2.所有对象命名严禁使用拼音与英文混合方式,更不能使用中文的方式
3.类名使用 UpperCamelCase风格,以下情形例外:DO/PO/DTO/BO/VO/UID 等,方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase风格
4.常量命名应该全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长
5.抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾,测试类命名以它要测试的类的名称开始,以 Test 结尾
6.类型与中括号紧挨相连来定义数组,比如String[] args,int[] arrayDemo
7.POJO 类中的任何布尔类型的变量,都不要加 is 前缀,否则部分框架解析会引起序列化错误;
    说明:本文 MySQL 规约中的建表约定第 1 条,表达是与否的变量采用 is_xxx 的命名方式,所以需要在<resultMap>设置从 is_xxx 到 xxx 的映射关系
8.包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式
9.避免在子父类的成员变量之间、或者不同代码块的局部变量之间采用完全相同的命名,使可理解性降低

2.1. long 或 Long 赋值时,数值后使用大写 L,不能是小写 l;浮点数类型的数值后缀统一为大写的 D 或 F

3.1 代码格式
    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");
        // 在右大括号后直接结束,则必须换行
        }
    }
3.2 注释的双斜线与注释内容之间有且仅有一个空格
3.3 单行字符数限制不超过 120 个,超出需要换行,如下示例
    StringBuilder builder = new StringBuilder();
    // 超过 120 个字符的情况下,换行缩进 4 个空格,并且方法前的点号一起换行
    builder.append("yang").append("hao")
        .append("chen")
        .append("chen")
        .append("chen");
3.4 方法参数在定义和传入时,多个参数逗号后面必须加空格
3.5 IDE 的 text file encoding 设置为 UTF-8;IDE 中文件的换行符使用 Unix 格式,不要使用Windows 格式

4.1 不能使用过时的类或方法
4.2 所有整型包装类对象之间值的比较,全部使用 equals 方法比较
4.3 浮点数之间的等值判断,基本数据类型不能使用 == 进行比较,包装数据类型不能使用 equals进行判断。
    正例:
    (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.compareTo(y) == 0) {
        System.out.println("true");
    }
4.4 BigDecimal 的等值比较应使用 compareTo() 方法,而不是 equals() 方法
    说明:equals() 方法会比较值和精度(1.0 与 1.00 返回结果为 false),而 compareTo() 则会忽略精度。
4.5 禁止使用构造方法 BigDecimal(double) 的方式把 double 值转化为 BigDecimal 对象
4.6 .关于基本数据类型与包装数据类型的使用标准如下:
    1)【强制】所有的 POJO 类属性必须使用包装数据类型
    2)【强制】RPC 方法的返回值和参数必须使用包装数据类型。
    3)【推荐】所有的局部变量使用基本数据类型
4.7 构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中。
4.8 POJO 类必须写 toString 方法。使用 IDE 中的工具 source > generate toString 时,如果继承了另一个 POJO 类,注意在前面加一下 super.toString()。
    说明:在方法执行抛出异常时,可以直接调用 POJO 的 toString() 方法打印其属性值,便于排查问题。

5.1 日期格式化时,传入 pattern 中表示年份统一使用小写的 y
    说明:日期格式化时,yyyy 表示当天所在的年,而大写的 YYYY 代表是 week in which year(JDK7 之后引入的概念),意思是当天所在的周属于的年份,一周从周日开始,周六结束,只要本周跨年,返回的 YYYY 就是下一年。
5.2 在日期格式中分清楚大写的 M 和小写的 m,大写的 H 和小写的 h 分别指代的意义
    说明:日期格式中的这两对字母表意如下:
    1)表示月份是大写的 M
    2)表示分钟则是小写的 m
    3)24 小时制的是大写的 H
    4)12 小时制的则是小写的 h
5.3 获取当前毫秒数:System.currentTimeMillis();而不是 new Date().getTime()
    说明:获取纳秒级时间,则使用 System.nanoTime 的方式。在 JDK8 中,针对统计时间等场景,推荐使用 Instant 类
5.4 禁止在程序中写死一年为 365 天,避免在公历闰年时出现日期转换错误或程序逻辑错误。
    正例:
    // 获取今年的天数
    int daysOfThisYear = LocalDate.now().lengthOfYear();
    // 获取指定某年的天数
    LocalDate.of(2011, 1, 1).lengthOfYear();

6.1 关于 hashCode 和 equals 的处理,遵循如下规则:
    1)只要覆写 equals,就必须覆写 hashCode。
    2)因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须覆写这两种方法。
    3)如果自定义对象作为 Map 的键,那么必须覆写 hashCode 和 equals。
    说明:String 因为覆写了 hashCode 和 equals 方法,所以可以愉快地将 String 对象作为 key 来使用。
6.2 判断所有集合内部的元素是否为空,使用 isEmpty() 方法,而不是 size() == 0 的方式
    说明:在某些集合中,前者的时间复杂度为 O(1),而且可读性更好。
    正例:
    Map<String, Object> map = new HashMap<>(16);
    if (map.isEmpty()) {
        System.out.println("no element in this map.");
    }
6.3 使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一致、长度为0 的空数组。
    正例:
    List<String> list = new ArrayList<>(2);
    list.add("guan");
    list.add("bao");
    String[] array = list.toArray(new String[0]);
    说明:使用 toArray 带参方法,数组空间大小的 length:
    1)等于 0,动态创建与 size 相同的数组,性能最好。
    2)大于 0 但小于 size,重新创建大小等于 size 的数组,增加 GC 负担。
    3)等于 size,在高并发情况下,数组创建完成之后,size 正在变大的情况下,负面影响与 2 相同。
    4)大于 size,空间浪费,且在 size 处插入 null 值,存在 NPE 隐患。
6.4 不要在 foreach 循环里进行元素的 remove / add 操作。remove 元素请使用 iterator 方式,如果并发操作,需要对 iterator 对象加锁。
    正例:
    List<String> list = new ArrayList<>();
    list.add("1");
    list.add("2");
    Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()) {
        String item = iterator.next();
        if (删除元素的条件) {
            iterator.remove();
        }
    }
    反例:
    for (String item : list) {
        if ("1".equals(item)) {
            list.remove(item);
        }
    }
    说明:反例中的执行结果肯定会出乎大家的意料,那么试一下把“1”换成“2”会是同样的结果吗?(会报同样的错:java.util.ConcurrentModificationException)
7.1 线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
    说明:Executors 返回的线程池对象的弊端如下:
    1)FixedThreadPool 和 SingleThreadPool:
    允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
    2)CachedThreadPool:
    允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
    3)ScheduledThreadPool:
    允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
7.2 SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为 static,必须加锁,或者使用 DateUtils 工具类。
    正例:注意线程安全,使用 DateUtils。亦推荐如下处理:
    private static final ThreadLocal<DateFormat> dateStyle = new ThreadLocal<DateFormat>() {
        @Override
        protected DateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd");
        }
    }
    说明:如果是JDK8的应用,可以使用 Instant代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替SimpleDateFormat,官方给出的解释:simple beautiful strong immutable thread-safe
8.1 在一个 switch 块内,每个 case 要么通过 continue / break / return 等来终止,要么注释说明程序将继续执行到哪一个 case 为止;在一个 switch 块内,都必须包含一个 default 语句并且放在最后,即使它什么代码也没有。
    说明:注意 break 是退出 switch 语句块,而 return 是退出方法体。
8.2 当 switch 括号内的变量类型为 String 并且此变量为外部参数时,必须先进行 null 判断(否则会报错java.lang.NullPointerException)    
8.3 在 if / else / for / while / do 语句中必须使用大括号。
    反例: if (condition) statements;
    说明:即使只有一行代码,也要采用大括号的编码方式。
9.1 类、类属性、类方法的注释必须使用 Javadoc 规范,使用 /** 内容 */ 格式,不得使用 // xxx方式
9.2 方法内部单行注释,在被注释语句上方另起一行,使用 // 注释。方法内部多行注释使用 /* */注释,注意与代码对齐。
9.3 所有的枚举类型字段必须要有注释,说明每个数据项的用途。
11.1 Java 类库中定义的可以通过预检查方式规避的 RuntimeException 异常不应该通过 catch 的方式来处理,比如:NullPointerException,IndexOutOfBoundsException 等等。
    说明:无法通过预检查的异常除外,比如,在解析字符串形式的数字时,可能存在数字格式错误,不得不通过 catch
    NumberFormatException 来实现。
    正例:if (obj != null) {...}
    反例:try { obj.method(); } catch (NullPointerException e) {…}
11.2 不要在 finally 块中使用 return
    说明:try 块中的 return 语句执行成功后,并不马上返回,而是继续执行 finally 块中的语句,如果此处存在 return 语句,则会在此直接返回,无情丢弃掉 try 块中的返回点。
12.1 应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架(SLF4J、JCL—Jakarta Commons Logging)中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
    说明:日志框架(SLF4J、JCL--Jakarta Commons Logging)的使用方式(推荐使用 SLF4J)


推荐

1.在常量与变量命名时,表示类型的名词放在词尾,以提升辨识度,如:startTime、workQueue
2.如果模块、接口、类、方法使用了设计模式,在命名时要体现出具体模式
3.接口类中的方法和属性不要加任何修饰符号(public 也不要加)

2.1. 不要使用一个常量类维护所有常量,要按常量功能进行归类,分开维护,比如:缓存相关常量放在类 CacheConsts 下;系统配置相关常量放在类 SystemConfigConsts下

3.1 单个方法的总行数不超过80行(除注释之外)
3.2 没有必要增加若干空格来使变量的赋值等号与上一行对应位置的等号对齐
3.3 不同逻辑、不同语义、不同业务的代码之间插入一个空行,分隔开来以提升可读性

4.1 循环体内,字符串的连接方式,使用 StringBuilder 的 append 方法进行扩展
4.2 慎用 Object 的 clone 方法来拷贝对象

6.1 高度注意 Map 类集合 K / V 能不能存储 null 值的情况,如下表格:
    集合类 Key Value Super 说明
    Hashtable 不允许为 null 不允许为 null Dictionary 线程安全
    TreeMap 不允许为 null 允许为 null AbstractMap 线程不安全
    ConcurrentHashMap 不允许为 null 不允许为 null AbstractMap 锁分段技术(JDK8:CAS)
    HashMap 允许为 null 允许为 null AbstractMap 线程不安全
    反例:由于 HashMap 的干扰,很多人认为 ConcurrentHashMap 是可以置入 null 值,而事实上,存储 null 值时会抛出 NPE 异常。
7.1 资金相关的金融敏感信息,使用悲观锁策略
    说明:乐观锁在获得锁的同时已经完成了更新操作,校验逻辑容易出现漏洞,另外,乐观锁对冲突的解决策略有较复杂的要求,处理不当容易造成系统压力或数据异常,所以资金相关的金融敏感信息不建议使用乐观锁更新。
    正例:悲观锁遵循一锁二判三更新四释放的原则
8.1 当方法的代码总行数超过 10 行时,return / throw 等中断逻辑的右大括号后需要加一个空行
    说明:这样做逻辑清晰,有利于代码阅读时重点关注。
8.2 表达异常的分支时,少用 if-else 方式,这种方式可以改写成:
    if (condition) {
        ...
        return;
    }
    // 接着写 else 的业务逻辑代码;
    说明:如果非使用 if()...else if()...else...方式表达逻辑,避免后续代码维护困难,请勿超过 3 层。
9.1 在类中删除未使用的任何字段和方法、内部类;在方法中删除未使用的参数声明与内部变量
10.1 前后端交互的 API,需要明确协议、域名、路径、请求方法、请求内容、状态码、响应体。
    说明:
    1)协议:生产环境必须使用 HTTPS。
    2)路径:每一个 API 需对应一个路径,表示 API 具体的请求地址:
        a)代表一种资源,只能为名词,推荐使用复数,不能为动词,请求方法已经表达动作意义。
        b)URL 路径不能使用大写,单词如果需要分隔,统一使用下划线。
        c)路径禁止携带表示请求内容类型的后缀,比如".json",".xml",通过 accept 头表达即可。
    3)请求方法:对具体操作的定义,常见的请求方法如下:
        a)GET:从服务器取出资源。
        b)POST:在服务器新建一个资源。
        c)PUT:在服务器更新资源。
        d)DELETE:从服务器删除资源。
10.2 前后端数据列表相关的接口返回,如果为空,则返回空数组[]或空集合{}
     说明:此条约定有利于数据层面上的协作更加高效,减少前端很多琐碎的 null 判断。
10.3 在前后端交互的 JSON 格式数据中,所有的 key 必须为小写字母开始的 lowerCamelCase风格,符合英文表达习惯,且表意完整。
10.4 及时清理不再使用的代码段或配置信息。
    说明:对于垃圾代码或过时配置,坚决清理干净,避免程序过度臃肿,代码冗余。
    正例:对于暂时被注释掉,后续可能恢复使用的代码片断,在注释代码上方,统一规定使用三个斜杠(///)来说明注释掉代码的理由
    public static void hello() {
        /// 业务方通知活动暂停
        // Business business = new Business();
        // business.active();
        System.out.println("it's finished");
    }
11.1 方法的返回值可以为 null,不强制返回空集合,或者空对象等,必须添加注释充分说明什么情况下会返回 null 值。
    说明:本规约明确防止 NPE 是调用者的责任。即使被调用方法返回空集合或者空对象,对调用者来说,也并非高枕无忧,必须考虑到远程调用失败,运行时异常等场景返回 null 的情况。
11.2 防止 NPE,是程序员的基本修养,注意 NPE 产生的场景
    1)返回类型为基本数据类型,return 包装数据类型的对象时,自动拆箱有可能产生 NPE
        反例:public int method() { return Integer 对象; },如果为 null,自动解箱抛 NPE。
    2)数据库的查询结果可能为 null。
    3)集合里的元素即使 isNotEmpty,取出的数据元素也可能为 null。
    4)远程调用返回对象时,一律要求进行空指针判断,防止 NPE。
    5)对于 Session 中获取的数据,建议进行 NPE 检查,避免空指针。
    6)级联调用 obj.getA().getB().getC();一连串调用,易产生 NPE。
    正例:使用 JDK8 的 Optional 类来防止 NPE 问题。


参考

1.1 Service / DAO 层方法命名规约:
    1)获取单个对象的方法用 get 做前缀。
    2)获取多个对象的方法用 list 做前缀,复数结尾,如:listObjects
    3)获取统计值的方法用 count 做前缀。
    4)插入的方法用 save / insert 做前缀。
    5)删除的方法用 remove / delete 做前缀。
    6)修改的方法用 update 做前缀
6.1 利用 Set 元素唯一的特性,可以快速对一个集合进行去重操作,避免使用 List 的contains() 进行遍历去重或者判断包含操作

专有名词解释

1. POJO(Plain Ordinary Java Object):在本规约中,POJO 专指只有 setter / getter / toString 的简单类,包括DO / DTO / BO / VO 等。
2. DO(Data Object):阿里巴巴专指数据库表一 一对应的 POJO 类。此对象与数据库表结构一 一对应,通过 DAO 层向上传输数据源对象。
3. PO(Persistent Object):也指数据库表一 一对应的 POJO 类。此对象与数据库表结构一 一对应,通过 DAO 层向上传输数据源对象。
4. DTO(Data Transfer Object ):数据传输对象,Service 或 Manager 向外传输的对象
5. BO(Business Object):业务对象,可以由 Service 层输出的封装业务逻辑的对象
6. Query:数据查询对象,各层接收上层的查询请求。注意超过 2 个参数的查询封装,禁止使用 Map 类来传输,,即我们公司内部常用的QO包
7. VO(View Object):显示层对象,通常是 Web 向模板渲染引擎层传输的对象。
8. CAS(Compare And Swap):解决多线程并行情况下使用锁造成性能损耗的一种机制,这是硬件实现的原子操作。CAS 操作包含三个操作数:内存位置、预期原值和新值。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。
9. GAV(GroupId、ArtifactId、Version):Maven 坐标,是用来唯一标识 jar 包。
10. OOP(Object Oriented Programming):本文泛指类、对象的编程处理方式。
11. AQS(AbstractQueuedSynchronizer):利用先进先出队列实现的底层同步工具类,它是很多上层同步实现类的基础,比如:ReentrantLock、CountDownLatch、Semaphore 等,它们通过继承 AQS 实现其模版方法,然后将 AQS子类作为同步组件的内部类,通常命名为 Sync。
12. ORM(Object Relation Mapping):对象关系映射,对象领域模型与底层数据之间的转换,本文泛指 iBATISmybatis 等框架。
13. NPE(java.lang.NullPointerException):空指针异常。
14. OOM(Out Of Memory):源于 java.lang.OutOfMemoryError,当 JVM 没有足够的内存来为对象分配空间并且垃圾回收器也无法回收空间时,系统出现的严重状况。
15. GMT(Greenwich Mean Time):指位于英国伦敦郊区的皇家格林尼治天文台的标准时间,因为本初子午线被定义在通过那里的经线。地球每天的自转是有些不规则的,而且正在缓慢减速,现在的标准时间是协调世界时(UTC),它由原子钟提供。
16. 一方库:本工程内部子项目模块依赖的库(jar 包)。
17. 二方库:公司内部发布到中央仓库,可供公司内部其它应用依赖的库(jar 包)
18. 三方库:公司之外的开源库(jar 包)。 

猜你喜欢

转载自blog.csdn.net/qq_34020761/article/details/128878606