Java知识点笔记(四):《Java 开发手册》的学习(二)

手册下载链接:https://pan.baidu.com/s/1kNYcboI-KwDuTbuW086YwQ    提取码:3271

1、集合处理

只要覆写 equals,就必须覆写 hashCode

因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须覆写这两个方法

如果自定义对象作为 Map 的键,那么必须覆写 hashCode 和 equals

ArrayList 的 subList 结果不可强转成 ArrayList,否则会抛出异常

使用 Map 的方法 keySet()/values()/entrySet() 返回集合对象时,不可以对其进行添加元素操作,否则会抛出异常

Collections 类返回的对象,如:emptyList()/singletonList() 等都是 immutable list,不可对其进行添加或者删除元素的操作

在 subList 场景中,高度注意对原集合元素的增加或删除,均会导致子列表的遍历、增加、删除产生异常

使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一致、长度为 0 的空数组

在使用 Collection 接口任何实现类的 addAll() 方法时,都要对输入的集合参数进行 NPE 判断

使用工具类 Arrays.asList() 把数组转换成集合时,不能使用其修改集合相关的方法,它的 add/remove/clear 方法会抛出异常

泛型通配符 <? extends T> 来接收返回的数据,不能使用 add 方法,<? super T> 不能使用 get 方法,作为接口调用赋值易出错 

PECS原则:第一、频繁往外读取内容的,适合用 <? extends T> ,第二、经常往里插入的,适合用  <? super T>

在无泛型限制定义的集合赋值给泛型限制的集合时,在使用集合元素时,需要进行 instanceof 判断,避免抛出异常

不要在 foreach 循环里进行元素的 remove/add 操作,remove 元素使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁

高度注意 Map 类集合 K/V 能不能存储 null 值的情况,如下表格:

2、并发处理

获取单例对象需要保证线程安全,其中的方法也要保证线程安全

创建线程或线程池时请指定有意义的线程名称,方便出错时回溯

线程资源必须通过线程池提供,不允许在应用中自行显式创建线程

线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式

SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为 static,必须加锁,或者使用 DateUtils 工具类

必须回收自定义的ThreadLocal变量,尤其在线程池场景下,线程经常会被复用

高并发时,同步调用应该去考量锁的性能损耗

能用无锁数据结构,就不要用锁; 能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁

对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁

在使用阻塞等待获取锁的方式中,必须在 try 代码块之外,并且在加锁方法与 try 代码块之间没有任何可能抛出异常的方法调用

在使用尝试机制来获取锁的方式中,进入业务代码块之前,必须先判断当前线程是否持有锁

锁的释放规则与锁的阻塞等待方式相同 

并发修改同一记录时,避免更新丢失,需要加锁

要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,使用 version 作为更新依据

多线程并行处理定时任务时,Timer 运行多个 TimeTask 时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行

如果在处理定时任务时使用 ScheduledExecutorService 则没有这个问题

3、异常处理

Java 类库中定义的可以通过预检查方式规避的 RuntimeException 异常不应该通 过 catch 的方式来处理

比如:NullPointerException,IndexOutOfBoundsException 等等

异常不要用来做流程控制,条件控制

catch时请分清稳定代码和非稳定代码,稳定代码指的是无论如何不会出错的代码

对于非稳定代码的 catch尽可能进行区分异常类型,再做对应的异常处理

捕获异常是为了处理它,不要捕获了却什么都不处理而抛弃之,如果不想处理它, 请将该异常抛给它的调用者

最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容

有 try 块放到了事务代码中,catch 异常后,如果需要回滚事务,一定要注意手动回滚事务

finally块必须对资源对象、流对象进行关闭,有异常也要做try-catch,不要在 finally 块中使用 return

捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类

在调用 RPC、二方包、或动态生成类的相关方法时,捕捉异常必须使用 Throwable 类来进行拦截

4、日志规约

应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架 SLF4J 中的 API

使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一

所有日志文件至少保存15天,因为有些异常具备以“周”为频次发生的特点

网络运行状态、安全相关信息、系统监测、管理后台操作、用户敏感操作需要留存相关的网络日志不少于6个月

应用中的扩展日志(如打点、临时监控、访问日志等)命名方式: appName_logType_logName.log

logType:日志类型,如 stats/monitor/access 等;logName:日志描述

这种命名的好处:通过文件名就可知道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找

在日志输出时,字符串变量之间的拼接使用占位符的方式

对于 trace/debug/info级别的日志输出,必须进行日志级别的开关判断

避免重复打印日志,浪费磁盘空间,务必在 log4j.xml中设置 additivity=false

国际化团队或海外部署的服务器由于字符集 问题,使用全英文来注释和描述日志错误信息

5、单元测试

好的单元测试必须遵守 AIR 原则,具有自动化、独立性、可重复执行的特点

单元测试应该是全自动执行的,并且非交互式的。测试用例通常是被定期执行的,执行过程必须完全自动化才有意义

输出结果需要人工检查的测试不是一个好的单元测试

单元测试中不准使用 System.out 来进行人肉验证,必须使用 assert 来验证

为了保证单元测试稳定可靠且便于维护,单元测试用例之间决不能互相调用,也不能依赖执行的先后次序

单元测试是可以重复执行的,不能受到外界环境的影响

对于单元测试,要保证测试粒度足够小,有助于精确定位问题。单测粒度至多是类级别,一般是方法级别

核心业务、核心应用、核心模块的增量代码确保单元测试通过

单元测试代码必须写在如下工程目录:src/test/java,不允许写在业务代码目录下

6、安全规约

隶属于用户个人的页面或者功能必须进行权限控制校验

用户敏感数据禁止直接展示,必须对展示数据进行脱敏

用户输入的 SQL 参数严格使用参数绑定或者 METADATA 字段值限定,防止 SQL 注入,禁止字符串拼接 SQL 访问数据库

用户请求传入的任何参数必须做有效性验证

禁止向 HTML 页面输出未经安全过滤或未正确转义的用户数据

表单、AJAX 提交必须执行 CSRF 安全验证

在使用平台资源,譬如短信、邮件、电话、下单、支付,必须实现正确的防重放的机制

如数量限制、疲劳度控制、验证码校验,避免被滥刷而导致资损

7、工程结构

DO(Data Object):此对象与数据库表结构一一对应,通过 DAO 层向上传输数据源对象

DTO(Data Transfer Object):数据传输对象,Service 或 Manager 向外传输的对象

BO(Business Object):业务对象,由 Service 层输出的封装业务逻辑的对象

AO(Application Object):应用对象,在 Web 层与 Service 层之间抽象的复用对象模型,极为贴近展示层,复用度不高

VO(View Object):显示层对象,通常是 Web 向模板渲染引擎层传输的对象

Query:数据查询对象,各层接收上层的查询请求。注意超过2个参数的查询封装,禁止使用 Map 类来传输

定义 GAV 遵从以下规则:

GroupID 格式:com.{公司/BU }.业务线 [.子业务线],最多4级

ArtifactID 格式:产品线名-模块名。语义不重复不遗漏,先到中央仓库去查证一下

Version:详细规定参考下方

二方库版本号命名方式:主版本号.次版本号.修订号

主版本号:产品方向改变,或者大规模 API 不兼容,或者架构不兼容升级

次版本号:保持相对兼容性,增加主要功能特性,影响范围极小的 API 不兼容修改

修订号:保持完全兼容性,修复BUG、新增次要功能特性等

线上应用不要依赖 SNAPSHOT 版本(安全包除外)

二方库的新增或升级,保持除功能点之外的其它 jar 包仲裁结果不变。如果有改变,必须明确评估和验证

二方库里可以定义枚举类型,参数可以使用枚举类型,但是接口返回值不允许使用枚举类型或者包含枚举类型的 POJO 对象

依赖于一个二方库群时,必须定义一个统一的版本变量,避免版本号不一致

禁止在子项目的 pom 依赖中出现相同的 GroupId,相同的 ArtifactId,但是不同的 Version

8、设计规约

存储方案和底层数据结构的设计获得评审一致通过,并沉淀成为文档

在需求分析阶段,如果与系统交互的 User 超过一类并且相关的 User Case 超过5个,使用用例图来表达更加清晰的结构化需求

如果某个业务对象的状态超过3个,使用状态图来表达并且明确状态变化的各个触发条件

如果系统中某个功能的调用链路上的涉及对象超过3个,使用时序图来表达并且明确各调用环节的输入与输出

如果系统中模型类超过5个,并且存在复杂的依赖关系,使用类图来表达并且明确类之间的关系

如果系统中超过2个对象之间存在协作关系,并且需要表示复杂的处理流程,使用活动图来表示