目录

前言

一.命名风格

1.规范变量的命名【强制】

2. 规范类的命名【强制】

3. 规范方法的命名【强制】

二. 代码风格

1.提升代码的可读性【推荐】

2.尽量避免魔法值【推荐】

3.避免大段代码【推荐】

三. 控制语句

1.控制语句的大括号【强制】

2.善于使用卫语句【推荐】

3.循环内避免重复性操作【强制】

4.循环内的try catch【参考】

四. 注释规范

1.类、属性、方法的注释【强制】

2.方法内的注释【推荐】

3.注释同样需要维护【强制】

4.谨慎注释代码【强制】

五. OOP规范

1.善于使用@Override【强制】

2.安全的调用equals方法【强制】

3.注意包装类型的比较【强制】

4.注意包装类型与基本类型的选择【强制】

5.高效的拼接字符串【强制】

6.类如何禁止被new【参考】

7.控制工具类的构造方法【参考】

六. 集合处理

1.集合的初始化【强制】

2.Map类集合能不能存null值【强制】

3.Java8中对集合的优雅操作【参考】

七. 并发处理

1.善于使用线程池【强制】

2.线程安全的数据结构【强制】

八. 异常处理

1.RuntimeException的处理【强制】

2.防止NPE【强制】

3.区分unchecked(Runtime)/checked异常【强制】

4.注意finally的使用【强制】

5.注意catch中的异常类型【强制】

九. 日志规范

1.注意使用占位符【强制】

2.精简日志内容【推荐】

3.规范error的使用【强制】


前言

高质量的代码应该同时具备以下特性:

  • 准确性:能够准确无误的返回业务需要的结果;
  • 健壮性:健壮的程序应该对于数据异常、网络异常、系统故障等特殊场景有良好的适应能力;
  • 高效性:高效的程序才会有良好的用户体验;
  • 可读性:代码除了自己看就是给同事或同行看,可读性与规范程度直接决定你在对方心中的地位。你的垃圾代码有可能会成为反面教材,在你不知道的地方被展览;
  • 可扩展性:扩展别人代码的时候有没有吐槽过?扩展还不如重新写?懂得都懂。

以下内容参考于《阿里巴巴Java开发手册》等权威资料。

一.命名风格

1.规范变量的命名【强制】

所有变量必须使用驼峰式风格,不能以特殊字符开头。常量命名全部大写,单词下划线隔开,语义尽量完整,不要嫌名字长。

正例:lowerCamelCase

反例:ObjId、_name、WFROLE_TYPE

2. 规范类的命名【强制】

类名首字母大写,遵循驼峰式,命名要体现类的业务用途。

正例:WorkFlowService、WorkFlowUtils、WorkFlowTest、WorkFlowException

反例:Test、Common

  • 领域模型命名:数据对象:xxxDO(xxxEntity),xxx为数据库表名;
  • 数据传输对象:xxxDTO;
  • 展示对象:xxxVO;
  • POJO是DO/DTO/VO的统称,禁止命名成xxxPOJO。

3. 规范方法的命名【强制】

方法名首字母小写,遵循驼峰式,命名要体现方法的业务用途(方法突出动作,一般都是动词)。

  • 获取单个对象的方法用get作为前缀;
  • 获取多个对象的方法用list作为前缀;
  • 获取统计值的方法用count作为前缀;
  • 插入方法用save/insert作为前缀;
  • 删除方法用remove/delete作为前缀。

二. 代码风格

1.提升代码的可读性【推荐】

  1. if/for/while等关键字与括号之间加一个空格;
  2. =、逻辑运算符、加减乘除等符号左右两边加一个空格;
  3. 双斜线注释与内容之间有且仅有一个空格;
  4. 单行字符数不超过120个,超出需要换行;
  5. 方法参数在定义和传入时,多个参数逗号后面必须加空格;
  6. 不同逻辑、不同语义、不同业务的代码之间加一个空行分隔,提升可读性;
  7. 删除类中无用的jar包。 

2.尽量避免魔法值【推荐】

方法内应尽量避免魔法值,用常量代替。

3.避免大段代码【推荐】

不要把一个功能的所有内容都集中在一个方法中。

最外层的入口方法往往用来体现整体的业务流程,尽量让人一目了然,具体实现细节可以抽到单独的方法来实现,同时要有明确的注释,这样的代码可以大大提升可读性与可扩展性。

正例:

@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// 1、调用容器准备刷新的方法,获取容器的当时时间,同时给容器设置同步标识
			prepareRefresh();
			// 2、告诉子类启动refreshBeanFactory()方法,启动内部的bean factory然后对bean进行定位、加载、注册,最后bean会被组装成BeanDifinition对象保存在beanDefinitionMap中。
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
			// 3、为BeanFactory 配置容器特性,例如类加载器、事件处理器等
			prepareBeanFactory(beanFactory);
			try {
				// 4、允许子类做beanFactory的后置处理
				postProcessBeanFactory(beanFactory);
				// 5、在容器中调用注册为bean的工厂处理器
				invokeBeanFactoryPostProcessors(beanFactory);
				// 6、注册Bean的后置处理器,用于监听容器触发的事件
				registerBeanPostProcessors(beanFactory);
				// 7、初始化信息源,和国际化相关
				initMessageSource();
				// 8、初始化容器事件传播器
				initApplicationEventMulticaster();
				// 9、调用子类的某些特殊Bean初始化方法
				onRefresh();
				// 10、检查并注册实践监听器
				registerListeners();
				// 11、初始化所以剩余(非懒加载)的单例bean,根据beanName循环递归调用getBean方法,包括对循环引用的解决
				finishBeanFactoryInitialization(beanFactory);
				// 12、初始化容器的生命周期事件处理器,并发布容器的生命周期事件
				finishRefresh();
			}
			catch (BeansException ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}
				// 13、销毁已创建的Bean
				destroyBeans();
				// 14、取消refresh 操作,重置容器的同步标识
				cancelRefresh(ex);
				throw ex;
			}
			finally {
				// 15、重置公共缓存
				resetCommonCaches();
			}
		}
	}

三. 控制语句

1.控制语句的大括号【强制】

在if/else/for/while语句中,必须使用大括号,即使只有一行,使作用范围更清晰。

2.善于使用卫语句【推荐】

考虑使用卫语句代替if else。

正例:

if (todoOrderPastDTO == null) {
    throw new BizException(-1, "参数不能为空");
}
if (StringUtils.isBlank(todoOrderPastDTO.getOneLevelCode())) {
    throw new BizException(-1, "一级编码不能为空");
}
if (StringUtils.isBlank(todoOrderPastDTO.getTwoLevelCode())) {
    throw new BizException(-1, "二级编码不能为空");
}
if (StringUtils.isBlank(todoOrderPastDTO.getBizCode())) {
    throw new BizException(-1, "业务单号不能为空");
}
if (StringUtils.isBlank(todoOrderPastDTO.getTodoHandlerCode())) {
    throw new BizException(-1, "处理人系统号不能为空");
}

反例:

if (...) {
    if (...) {
        if (...) {
    	...
        } else {
    	...
        }
    } else {
        ...
    }
}

3.循环内避免重复性操作【强制】

Map<String, String> map = new HashMap<>(2);
map.put("a", "ok");

// String a = map.get("a");
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++) {
    // 这里要执行100次map的get方法
    sb.append(map.get("a"));
}

4.循环内的try catch【参考】

try catch对程序性能有一定影响,请根据业务场景考虑try catch是否可以提到循环外。

四. 注释规范

1.类、属性、方法的注释【强制】

必须使用Javadoc规范,/** xxx **/,不得使用 // xxx,尤其是利用工具生成的类,请补充注释说明。

2.方法内的注释【推荐】

方法内部单行注释使用// xxx,多行使用/* xxx */

3.注释同样需要维护【强制】

修改代码的同时,要对注释进行相应的修改,尤其是参数、返回值、异常、核心逻辑等。

4.谨慎注释代码【强制】

注释代码要说明理由,而不是简单的注释掉,如果后续可能恢复,统一使用三个斜杠 ///,并注明理由。如果不恢复,则直接删除,历史记录可以在git上找到。

五. OOP规范

1.善于使用@Override【强制】

所有覆写的方法必须加@Override,例如getObject()与get0bject(),通过@Override可以准确判断是否覆盖成功。

2.安全的调用equals方法【强制】

用常量或确定的值来调用equals方法。

3.注意包装类型的比较【强制】

所有相同类型的包装类对象之间的比较全部使用equals方法。

注:jdk1.7之后推荐使用Objects工具类。

Integer a1 = 1;
Integer a2 = 1;
Integer b1 = 128;
Integer b2 = 128;
System.out.println(a1 == a2); // -128 ~ 127 true
System.out.println(b1 == b2); // false
System.out.println(b1.equals(b2)); // true

Java编码流程 java编码规范有哪些?_List

4.注意包装类型与基本类型的选择【强制】

所有实体类必须使用包装数据类型,所有局部变量使用基本数据类型。

例如数据库的返回值可能是null,如果用基本数据类型接收,自动拆箱有NPE风险。

5.高效的拼接字符串【强制】

涉及多个字符串拼接的场景使用StringBuilder。

String str = "";
for (int i = 0; i < 100; i++) {
    // 编译后编译器会对代码优化,实际上每次循环都会new出一个StringBuilder,浪费资源
    str += "a";
}
System.out.println(str);

6.类如何禁止被new【参考】

如果类不允许外界直接new对象,构造方法必须是private,参考单例模式。

7.控制工具类的构造方法【参考】

工具类的构造方法建议是private或protected(可被继承)。

六. 集合处理

1.集合的初始化【强制】

集合初始化时,如果元素数量确定或可以预估,为了避免没必要的扩容,请指定集合初始值。

正例:

// 初始化一个需要承载20个元素的ArrayList(默认容量10,每次扩容1.5倍)
List<Object> list = new ArrayList<>(20);
 
// 初始化一个需要承载20个元素的HashMap(默认容量16,扩容因子0.75,每次扩容2倍)
Map<String, Object> map = new HashMap<>(32);

2.Map类集合能不能存null值【强制】

Java编码流程 java编码规范有哪些?_List_02

由于 HashMap 的干扰,很多人认为 ConcurrentHashMap 是可以置入 null 值,而事实上, 存储 null 值时会抛出 NPE 异常。

3.Java8中对集合的优雅操作【参考】

Demo:

  1. List按某属性分组;
  2. List转Map;
  3. List按条件过滤;
  4. List去重;
  5. List升序;
  6. List降序;
  7. List先升序再降序;
  8. List求和;
  9. List求最大值;
  10. List求最小值。
import lombok.Data;
 
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
 
/**
 * Java8 集合操作
 *
 * @Author hujy
 * @Date Created in 2019-07-16 11:38
 * @Version 1.0
 */
@Data
public class Fruit {
    private Integer id;
    private String name;
    private BigDecimal money;
    private Integer num;
 
    public Fruit(Integer id, String name, BigDecimal money, Integer num) {
        this.id = id;
        this.name = name;
        this.money = money;
        this.num = num;
    }
 
    /**
     * 分组
     * @author hujy
     * @date 2019-07-18 15:03
     * @param fruitList
     * @return void
     */
    private static void groupBy(List<Fruit> fruitList) {
        Map<Integer, List<Fruit>> groupBy =     
            fruitList.stream().collect(Collectors.groupingBy(Fruit::getId));
        System.out.println("groupBy:" + groupBy);
    }
 
    /**
     * List转Map
     * @author hujy
     * @date 2019-07-18 15:04
     * @param fruitList
     * @return void
     */
    private static void listToMap(List<Fruit> fruitList) {
        // 注:如果集合对象有重复的key,会报Duplicate key错误,可以用 (k1, k2) -> k1 来设置,如果有重复的key,则保留key1,舍弃key2
        Map<Integer, Fruit> fruitMap =
            fruitList.stream().collect(Collectors.toMap(Fruit::getId, a -> a, (k1, k2) -> k1));
        System.out.println("toMap:" + fruitMap);
    }
 
    /**
     * 按条件过滤
     * @author hujy
     * @date 2019-07-18 15:07
     * @param fruitList
     * @return void
     */
    private static void filter(List<Fruit> fruitList) {
        List<Fruit> filterList = 
            fruitList.stream().filter(a -> a.getName().equals("香蕉")).collect(Collectors.toList());
        System.out.println("filter:" + filterList);
    }
 
    /**
     * 去重
     * @author hujy
     * @date 2019-07-18 15:08
     * @param fruitList
     * @return void
     */
    private static void toUnique(List<Fruit> fruitList) {
        // 根据id去重
        List<Fruit> uniqueList = 
            fruitList.stream().collect(
                Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(o -> o.getId()))),
                        ArrayList::new));
        System.out.println("unique:" + uniqueList);
    }
 
    /**
     * 升序
     * @author hujy
     * @date 2019-07-18 15:09
     * @param fruitList
     * @return void
     */
    private static void sortAsc(List<Fruit> fruitList) {
        // 按id升序排序
        List<Fruit> sortAsc=
   fruitList.stream().sorted(Comparator.comparing(Fruit::getId)).collect(Collectors.toList());
        System.out.println("sortAsc:" + sortAsc);
    }
 
    /**
     * 降序
     * @author hujy
     * @date 2019-07-18 15:09
     * @param fruitList
     * @return void
     */
    private static void sortDesc(List<Fruit> fruitList) {
        // 按id降序排序
        List<Fruit> sortDesc =
                fruitList.stream().sorted(Comparator.comparing(Fruit::getId).reversed()).collect(Collectors.toList());
        System.out.println("sortDesc:" + sortDesc);
    }
 
    /**
     * 先升序再降序
     * @author hujy
     * @date 2019-07-18 15:09
     * @param fruitList
     * @return void
     */
    private static void sortAscDesc(List<Fruit> fruitList) {
        // 按id升序排序,id一样的按num降序
        List<Fruit> sortAscDesc = fruitList.stream().sorted(Comparator.comparing(Fruit::getId).reversed()
                .thenComparing(Fruit::getNum).reversed()).collect(Collectors.toList());
        System.out.println("sortAscDesc:" + sortAscDesc);
    }
 
    /**
     * 求和
     * @author hujy
     * @date 2019-07-18 15:10
     * @param fruitList
     * @return void
     */
    private static void sum(List<Fruit> fruitList) {
        // 按money求和
        BigDecimal totalMoney = fruitList.stream().map(Fruit::getMoney).reduce(BigDecimal.ZERO, BigDecimal::add);
        System.out.println("totalMoney:" + totalMoney);
    }
 
    /**
     * 求最大值
     * @author hujy
     * @date 2019-07-18 15:10
     * @param fruitList
     * @return void
     */
    private static void getMax(List<Fruit> fruitList) {
        // 求money最大值
        Optional<BigDecimal> max = fruitList.stream().map(Fruit::getMoney).max(BigDecimal::compareTo);
        System.out.println("max:" + max.get());
    }
 
    /**
     * 求最小值
     * @author hujy
     * @date 2019-07-18 15:10
     * @param fruitList
     * @return void
     */
    private static void getMin(List<Fruit> fruitList) {
        // 求money最小值
        Optional<BigDecimal> min = fruitList.stream().map(Fruit::getMoney).min(BigDecimal::compareTo);
        System.out.println("min:" + min.get());
    }
 
    public static void main(String[] args) {
        Fruit fruit1 = new Fruit(1, "苹果1", new BigDecimal("2.25"), 10);
        Fruit fruit2 = new Fruit(1, "苹果2", new BigDecimal("1.85"), 20);
        Fruit fruit3 = new Fruit(2, "香蕉", new BigDecimal("1.89"), 30);
        Fruit fruit4 = new Fruit(3, "樱桃", new BigDecimal("8.99"), 40);
 
        List<Fruit> fruitList = Arrays.asList(fruit1, fruit2, fruit3, fruit4);
 
        // 1.分组
        groupBy(fruitList);
        // groupBy:{1=[Fruit(id=1, name=苹果1, money=2.25, num=10), Fruit(id=1, name=苹果2, money=1.85, num=20)], 2=[Fruit(id=2, name=香蕉, money=1.89, num=30)], 3=[Fruit(id=3, name=樱桃, money=8.99, num=40)]}
 
        // 2.List转Map
        listToMap(fruitList);
        // toMap:{1=Fruit(id=1, name=苹果1, money=2.25, num=10), 2=Fruit(id=2, name=香蕉, money=1.89, num=30), 3=Fruit(id=3, name=樱桃, money=8.99, num=40)}
 
        // 3.按条件过滤
        filter(fruitList);
        // filter:[Fruit(id=2, name=香蕉, money=1.89, num=30)]
 
        // 4.根据id去重
        toUnique(fruitList);
        // unique:[Fruit(id=1, name=苹果1, money=2.25, num=10), Fruit(id=2, name=香蕉, money=1.89, num=30), Fruit(id=3, name=樱桃, money=8.99, num=40)]
 
        // 5.按id升序
        sortAsc(fruitList);
        // sortAsc:[Fruit(id=1, name=苹果1, money=2.25, num=10), Fruit(id=1, name=苹果2, money=1.85, num=20), Fruit(id=2, name=香蕉, money=1.89, num=30), Fruit(id=3, name=樱桃, money=8.99, num=40)]
 
        // 6.按id降序
        sortDesc(fruitList);
        // sortDesc:[Fruit(id=3, name=樱桃, money=8.99, num=40), Fruit(id=2, name=香蕉, money=1.89, num=30), Fruit(id=1, name=苹果1, money=2.25, num=10), Fruit(id=1, name=苹果2, money=1.85, num=20)]
 
        // 7.按id升序排序,id一样的按num降序
        sortAscDesc(fruitList);
        // sortAscDesc:[Fruit(id=1, name=苹果2, money=1.85, num=20), Fruit(id=1, name=苹果1, money=2.25, num=10), Fruit(id=2, name=香蕉, money=1.89, num=30), Fruit(id=3, name=樱桃, money=8.99, num=40)]
 
        // 8.money求和
        sum(fruitList);
        // totalMoney:14.98
 
        // 9.求money最大值
        getMax(fruitList);
        // max:8.99
 
        // 10.求money最小值
        getMin(fruitList);
        // min:1.85
    }
}

七. 并发处理

1.善于使用线程池【强制】

创建线程要使用线程池,建议不要自行显式创建。

// 定义固定数量线程池
ThreadPoolExecutor pool = new ThreadPoolExecutor(
        threadNum,
        threadNum,
        0L,
        TimeUnit.MILLISECONDS,
        new ArrayBlockingQueue<>(500),
        new DefaultThreadFactory("dataMove-pool"));

2.线程安全的数据结构【强制】

HashMap是非线程安全的,在并发写操作情况可能导致数据丢失,极端情况下产生死循环,占满CPU,因此禁止将HashMap等非线程安全的数据结构当做成员变量使用,特别是在Spring管理的单例中。

可使用ConcurrentHashMap代替HashMap来满足多线程并发操作的场景,ConcurrentHashMap在Java8中做了大量优化,建议了解。

ArrayList → CopyOnWriteArrayList

StringBuilder → StringBuffer

SimpleDateFormat是非线程安全的,禁止将SimpleDateFormat对象作为成员变量或公共静态工具使用,不建议加锁使用,会有效率问题。

八. 异常处理

1.RuntimeException的处理【强制】

可以通过预检查方式规避的RuntimeException不应该通过try/catch方式处理,例如NullPointerException(NPE)、IndexOutOfBoundsException。

2.防止NPE【强制】

防止NPE是程序员的基本素养,以下场景需要注意NPE校验:

  1. 当使用基本数据类型接收包装数据类型对象时,自动拆箱可能产生NPE,例如数据库查询返回null;
  2. 远程调用返回对象时,如果不进行非空校验,易产生NPE;
  3. 集合即使是isNotEmpty,取出的元素也可能是null,若不对元素进行非空校验直接使用,易产生NPE;
  4. 级联调用obj.getA().getB().getC()易产生NPE。

3.区分unchecked(Runtime)/checked异常【强制】

例如使用Spring进行事务管理时,默认会对运行时异常与error进行回滚,如果还想对非运行时异常回滚,需要通过rollbackFor具体指定回滚异常类型,这样会对该类型以及其子类异常进行回滚。

基于AOP动态代理的实现原理,如果在方法内部通过try/catch捕获异常,事务不会回滚。

4.注意finally的使用【强制】

在使用try/catch/finally时,不能在finally进行return,这样会覆盖try或catch中的return的结果,finally中一般用来执行对于资源释放的操作,如锁释放、流关闭等。

5.注意catch中的异常类型【强制】

在使用catch捕获异常时,应该注意捕获的异常与抛出的异常完全匹配,或者捕获的异常是抛出异常的父类。

九. 日志规范

1.注意使用占位符【强制】

debug/info级别的日志输出必须使用占位符方式,避免字符串拼接产生无用对象。

2.精简日志内容【推荐】

日志内容应该是一些唯一性强、辨识度高的关键字,方便快速定位分析问题,例如业务主键、流水号、接口入参、返回值、函数计算结果等。

日志并不是越详细越好,应该尽量做到精简,日志过多会影响系统性能。

3.规范error的使用【强制】

正例:

catch (Exception e) {
    logger.error("xxx接口执行异常," + e.getMessage(), e);
}

反例:

catch (Exception e) {
    logger.error("保存失败");
}