Java开发手册归纳知识点

Java开发手册(泰山版)

开发手册注意事项
1. POJO 类必须写 toString 方法。使用 IDE 中的工具:source> generate toString
时,如果继承了另一个 POJO 类,注意在前面加一下 super.toString。
    说明:在方法执行抛出异常时,可以直接调用 POJO 的 toString()方法打印其属性值,便于排查问题。

2. 禁止在 POJO 类中,同时存在对应属性 xxx 的 isXxx()和 getXxx()方法。
说明:框架在调用属性 xxx 的提取方法时,并不能确定哪个方法一定是被优先调用到,神坑之一。

3. 浅拷贝和深拷贝的区别,浅拷贝会把对象拷贝进来,修改一个属性,原变量也会修改,是对地址的拷贝
   深拷贝会将原对象完整拷贝一份,将其复制一个对象
   
4. 关于日期的格式引用
    日期格式化时,传入 pattern 中表示年份统一使用小写的 y。
    说明:日期格式化时,yyyy 表示当天所在的年,而大写的 YYYY 代表是 week in which year(JDK7 之后
    引入的概念),意思是当天所在的周属于的年份,一周从周日开始,周六结束,只要本周跨年,返回的 YYYY
    就是下一年。
    正例:表示日期和时间的格式如下所示:
    new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
ps:24小时制度是用HH,12小时制度使用hh,大写MM表示月,小写mm表示分钟

5.     获取当前毫秒数:System.currentTimeMillis(); 而不是 new Date().getTime()。
说明:如果想获取更加精确的纳秒级时间值,使用 System.nanoTime 的方式。在 JDK8 中,针对统计时间
等场景,推荐使用 Instant 类。

6. 对于年份的天数需要进行动态写入,不能定死为365天,可以使用
    // 获取今年的天数
    int daysOfThisYear = LocalDate.now().lengthOfYear();
    // 获取指定某年的天数
    LocalDate.of(2011, 1, 1).lengthOfYear()

7. 使用 Map 的方法 keySet()/values()/entrySet()返回集合对象时,不可以对其进行添
加元素操作,否则会抛出 UnsupportedOperationException 异常。
    Collections 类返回的对象,如:emptyList()/singletonList()等都是 immutable list,
不可对其进行添加或者删除元素的操作。
反例:如果查询无结果,返回 Collections.emptyList()空集合对象,调用方一旦进行了添加元素的操作,就
会触发 UnsupportedOperationException 异常。
    在 subList 场景中,高度注意对父集合元素的增加或删除,均会导致子列表的遍历、
增加、删除产生 ConcurrentModificationException 异常。

8. 使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一
致、长度为 0 的空数组。
反例:直接使用 toArray 无参方法存在问题,此方法返回值只能是 Object[]类,若强转其它类型数组将出现
ClassCastException 错误。
正例:
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 隐患。


9. 使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历。
说明:keySet 其实是遍历了 2 次,一次是转为 Iterator 对象,另一次是从 hashMap 中取出 key 所对应的
value。而 entrySet 只是遍历了一次就把 key 和 value 都放到了 entry 中,效率更高。如果是 JDK8,使用
Map.forEach 方法。
正例:values()返回的是 V 值集合,是一个 list 集合对象;keySet()返回的是 K 值集合,是一个 Set 集合对
象;entrySet()返回的是 K-V 值组合集合。

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

集合类                Key            Value          Super         说明
Hashtable         不允许为 null   不允许为 null   Dictionary    线程安全
ConcurrentHashMap 不允许为 null   不允许为 null   AbstractMap  锁分段技术(JDK8:CAS)
TreeMap           不允许为 null   允许为 null    AbstractMap   线程不安全
HashMap           允许为 null     允许为 null    AbstractMap   线程不安全

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

11. 合理利用好集合的有序性(sort)和稳定性(order),避免集合的无序性(unsort)和不稳
定性(unorder)带来的负面影响。
说明:有序性是指遍历的结果是按某种比较规则依次排列的。稳定性指集合每次遍历的元素次序是一定的。
如:ArrayList 是 order/unsort;HashMap 是 unorder/unsort;TreeSet 是 order/sort。


12. Set集合去掉List集合中重复元素
public static void main(String[] args) {
    
    //利用set集合 去除ArrayList集合中的重复元素
    ArrayList<String> list = new ArrayList<>();
    list.add("1");
    list.add("1");
    list.add("2");
    list.add("2");
    list.add("3");
    list.add("3");
    list.add("4");
    list.add("4");
    System.out.println("去重前的List集合:"+list);
    
    Set<String> set = new HashSet<>();
    set.addAll(list);
    System.out.println("Set集合:"+set);
    
    list.clear();            // 清空原有元素 放入被list去重后的元素
    list.addAll(set);
    System.out.println("去重后的List集合:"+list);
}
运行结果:

去重前的List集合:[1, 1, 2, 2, 3, 3, 4, 4]
Set集合:[1, 2, 3, 4]
去重后的List集合:[1, 2, 3, 4]

13. OOM(java.lang.OutOfMemoryError)内存用完,NPE(java.lang.NullPointerException)空指针

14. SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为 static,
必须加锁,或者使用 DateUtils 工具类。
正例:注意线程安全,使用 DateUtils。亦推荐如下处理:
private static final ThreadLocal<DateFormat> df = 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。

15. 必须回收自定义的 ThreadLocal 变量,尤其在线程池场景下,线程经常会被复用,
如果不清理自定义的 ThreadLocal 变量,可能会影响后续业务逻辑和造成内存泄露等问题。
尽量在代理中使用 try-finally 块进行回收。
正例:
objectThreadLocal.set(userInfo);
try {
 // ...
} finally {
 objectThreadLocal.remove();
}

16. 高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能
锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。
说明:尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用 RPC 方法。 
RPC的英文全程是Remote Procedure Call,也就是远程过程调用,这里可以把过程理解为方法,也就是远程方法调用。
在分布式系统中,因为每一个服务的边界都很小,很有可能调用别的服务提供的方法,这就会出现服务A调用服务B中一个方法的需求,也就是远程过程调用。

17. 悲观锁
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,
所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁
(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。
传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,
都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

乐观锁
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,
所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,
可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,
像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。
在Java中java.util.concurrent.atomic包下面的原子变量类
就是使用了乐观锁的一种实现方式CAS实现的。

两种锁的使用场景
从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,
像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,
这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,
一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,
所以一般多写的场景下用悲观锁就比较合适。

资金相关的金融敏感信息,使用悲观锁策略。
说明:乐观锁在获得锁的同时已经完成了更新操作,校验逻辑容易出现漏洞,另外,乐观锁对冲突的解决策
略有较复杂的要求,处理不当容易造成系统压力或数据异常,所以资金相关的金融敏感信息不建议使用乐观
锁更新。
正例:悲观锁遵循一锁二判三更新四释放的原则

18. HashMap 在容量不够进行 resize 时由于高并发可能出现死链,导致 CPU 飙升,在
开发过程中注意规避此风险。

19. 在一个 switch 块内,每个 case 要么通过 continue/break/return 等来终止,要么
注释说明程序将继续执行到哪一个 case 为止;在一个 switch 块内,
都必须包含一个 default语句并且放在最后,即使它什么代码也没有。
说明:注意 break 是退出 switch 语句块,而 return 是退出方法体。

20. 所有的类都必须添加创建者和创建日期。
说明:在设置模板时,注意 IDEA 的@author 为`${USER}`,而 eclipse 的@author 为`${user}`,大小写有
区别,而日期的设置统一为 yyyy/MM/dd 的格式。
 正例:
 /**
* @author yangguanbao
* @date 2016/10/31
*/

21. 所有的枚举类型字段必须要有注释,说明每个数据项的用途

用法一:常量定义
public enum ColorEnum {  
  RED, GREEN, BLANK, YELLOW  
}

用法二:switch语句
enum ColorEnum {
    GREEN, YELLOW, RED
}
public class ColorTest {
    ColorEnum color = ColorEnum.RED;
 
    public void change() {
        switch (color) {
            case RED:
                color = ColorEnum.GREEN;
                break;
            case YELLOW:
                color = ColorEnum.RED;
                break;
            case GREEN:
                color = ColorEnum.YELLOW;
                break;
        }
    }
}

用法三:枚举添加方法
public class EnumTest {
    public static void main(String[] args) {
        ErrorCodeEnum errorCode = ErrorCodeEnum.SUCCESS;
        System.out.println("状态码:" + errorCode.code() + 
                           " 状态信息:" + errorCode.msg());
    }
}
 
enum ErrorCodeEnum {
    SUCCESS(1000, "success"),
    PARAM_ERROR(1001, "parameter error"),
    SYS_ERROR(1003, "system error"),
    NAMESPACE_NOT_FOUND(2001, "namespace not found"),
    NODE_NOT_EXIST(3002, "node not exist"),
    NODE_ALREADY_EXIST(3003, "node already exist"),
    UNKNOWN_ERROR(9999, "unknown error");
 
    private int code;
    private String msg;
 
    ErrorCodeEnum(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }
 
    public int code() {
        return code;
    }
 
    public String msg() {
        return msg;
    }
 
    public static ErrorCodeEnum getErrorCode(int code) {
        for (ErrorCodeEnum it : ErrorCodeEnum.values()) {
            if (it.code() == code) {
                return it;
            }
        }
        return UNKNOWN_ERROR;
    }
}


22. 在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度。
说明:不要在方法体内定义:Pattern pattern = Pattern.compile(“规则”);

23. mybatis-plus中redis的使用
(1) 开启mybatis-plus的二级缓存,application.yml文件进行配置,加入pom文件的依赖
(2) mapper类中加入缓存注解@CacheNamespace
(implementation= MybatisRedisCache.class,eviction= MybatisRedisCache.class)
(3) 编写MybatisRedisCache实现Cache,定义读写锁等
(4) 编写RedisConfiguration 定义序列化规则和缓存方式和时间等自定义事项
(5) 编写Util类,进行调用

24. 后台输送给页面的变量必须加$!{var}——中间的感叹号。
说明:如果 var 等于 null 或者不存在,那么${var}会直接显示在页面上。

25. 任何数据结构的构造或初始化,都应指定大小,避免数据结构无限增长吃光内存

26. 事务场景中,抛出异常被 catch 后,如果需要回滚,一定要注意手动回滚事务。

27. finally 块必须对资源对象、流对象进行关闭,有异常也要做 try-catch。
 
28. 防止 NPE,是程序员的基本修养,注意 NPE 产生的场景:
    1) 返回类型为基本数据类型,return 包装数据类型的对象时,自动拆箱有可能产生 NPE。
     反例:public int f() { return Integer 对象}, 如果为 null,自动解箱抛 NPE。
    2) 数据库的查询结果可能为 null。
    3) 集合里的元素即使 isNotEmpty,取出的数据元素也可能为 null。
    4) 远程调用返回对象时,一律要求进行空指针判断,防止 NPE。
    5) 对于 Session 中获取的数据,建议进行 NPE 检查,避免空指针。
    6) 级联调用 obj.getA().getB().getC();一连串调用,易产生 NPE。
    正例:使用 JDK8 的 Optional 类来防止 NPE 问题
    
29. 在日志输出时,字符串变量之间的拼接使用占位符的方式。
说明:因为 String 字符串的拼接会使用 StringBuilder 的 append()方式,有一定的性能损耗。使用占位符仅
是替换动作,可以有效提升性能。
正例:logger.debug("Processing trade with id: {} and symbol: {}", id, symbol);

30. 好的单元测试必须遵守 AIR 原则。
说明:单元测试在线上运行时,感觉像空气(AIR)一样并不存在,但在测试质量的保障上,却是非常关键
的。好的单元测试宏观上来说,具有自动化、独立性、可重复执行的特点。
⚫ A:Automatic(自动化)
⚫ I:Independent(独立性)
⚫ R:Repeatable(可重复)

31. 单元测试应该是全自动执行的,并且非交互式的。测试用例通常是被定期执行的,执
行过程必须完全自动化才有意义。输出结果需要人工检查的测试不是一个好的单元测试。单元
测试中不准使用 System.out 来进行人肉验证,必须使用 assert 来验证。

32. 编写单元测试代码遵守 BCDE 原则,以保证被测试模块的交付质量。
⚫ B:Border,边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等。
⚫ C:Correct,正确的输入,并得到预期的结果。
⚫ D:Design,与设计文档相结合,来编写单元测试。
⚫ E:Error,强制错误信息输入(如:非法数据、异常流程、业务允许外等),并得到预期的结果。

33. 用户输入的 SQL 参数严格使用参数绑定或者 METADATA 字段值限定,防止 SQL 注入,
禁止字符串拼接 SQL 访问数据库。
反例:某系统签名大量被恶意修改,即是因为对于危险字符 # --没有进行转义,导致数据库更新时,where
后边的信息被注释掉,对全库进行更新。

34. 表单、AJAX 提交必须执行 CSRF 安全验证。
说明:CSRF(Cross-site request forgery)跨站请求伪造是一类常见编程漏洞。对于存在 CSRF 漏洞的应用/
网站,攻击者可以事先构造好 URL,只要受害者用户一访问,后台便在用户不知情的情况下对数据库中用
户参数进行相应修改。

CSRF(Cross-site Request Forgery)跨站请求伪造(或者缩写为XSRF),
也被称为"One Click Attack"或"Session Riding"(
曾被列为互联网 20 大安全隐患之一),是一种借助社工对网站身份的恶意利用。
不大流行,但如果被成功利用,危害更大。

CSRF与XSS的区别:
XSS
通过盗取网站内的已有的用户的身份,然后再执行相关操作

CSRF
通过伪装(伪造、更改状态的请求)用户身份(即盗用身份),通过服务器身份认证后,然后发送恶意请求(服务器会认为请求是合法的),但是服务器给出响应肯定是给真实的那个用户,

原理:
在浏览器中cookie在一段时间内是不会过期(不关闭或者退出浏览器),再次访问都会默认登录,这个应该都有体验。如果在cookie存在期间,通过构造csrf脚本或包含csrf脚本的链接发送给用户,得到信息后,再伪造成用户身份,执行相关操作

基本流程:
用户在某网站A进行登录-------->身份验证成功,返回cookie给用户---------->攻击者构建一个网站F,诱使用户使用同一浏览器进入(前提:未退出网站A,一般都会有默认浏览器)------------->网站F收到用户请求后,返回恶意代码给用户,强制他访问网站A---------->用户浏览器在网站A上执行相关操作(以已经持有的cookie)

使用过程
(1) 获取用户使用的修改信息的url
(2) 伪造url,将传入的参数修改
(3) 将url诱骗用户点击
(4) 信息全部被更改

35. 表名、字段名必须使用小写字母或数字,禁止出现数字开头,禁止两个下划线中间只
出现数字。数据库字段名的修改代价很大,因为无法进行预发布,所以字段名称需要慎重考虑。
说明:MySQL 在 Windows 下不区分大小写,但在 Linux 下默认是区分大小写。因此,数据库名、表名、
字段名,都不允许出现任何大写字母,避免节外生枝。
正例:aliyun_admin,rdc_config,level3_name
反例:AliyunAdmin,rdcConfig,level_3_name

36. 主键索引名为 pk_字段名;唯一索引名为 uk_字段名;普通索引名则为 idx_字段名。
说明:pk_ 即 primary key;uk_ 即 unique key;idx_ 即 index 的简称。

37. 小数类型为 decimal,禁止使用 float 和 double。
说明:在存储的时候,float 和 double 都存在精度损失的问题,很可能在比较值的时候,得到不正确的
结果。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数并分开存储

38. 业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引。
说明:不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明显的;另外,
即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。

39. 不得使用外键与级联,一切外键概念必须在应用层解决。
说明:(概念解释)学生表中的 student_id 是主键,那么成绩表中的 student_id 则为外键。如果更新学
生表中的 student_id,同时触发成绩表中的 student_id 更新,即为级联更新。外键与级联更新适用于单机
低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库
的插入速度。

40. 分层领域模型规约:
• DO(Data Object):此对象与数据库表结构一一对应,通过 DAO 层向上传输数据源对象。
• DTO(Data Transfer Object):数据传输对象,Service 或 Manager 向外传输的对象。
• BO(Business Object):业务对象,可以由 Service 层输出的封装业务逻辑的对象。
• Query:数据查询对象,各层接收上层的查询请求。注意超过 2 个参数的查询封装,禁止使用 Map 类
来传输。
• VO(View Object):显示层对象,通常是 Web 向模板渲染引擎层传输的对象。

41. @EnableScheduling 开启消息任务    @EnableAsync 开启异步调用

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值