朱晔《Java业务开发常见错误100例》课程学习整理
一、序列化
(1)序列化和反序列化需要确保算法一致,因为不同序列化算法输出必定不同,要正确处理序列化后的数据就要使用相同的反序列化算法
例:Redis的Redis Template和StringRedisTemplate两种方式存取的数据完全无法通用,因为默认情况下RedisTemplate针对Key和Value使用了JDK序列化处理,StringRedisTemplate对于Key和Value,使用String序列化方式。
自定义RedisTemplate的key使用RedisSerializer.string()实现字符串序列化,value使用RedisSerializer.json()
(2)注意Jackson JSON反序列化对额外字段的处理,Jackson有大量的序列化和反序列化特性,可以用来微调序列化和反序列化的细节。需要注意如果自定义ObjectMapper的Bean,不要和Spring Boot自动配置的Bean冲突
(3)调试序列化反序列化问题时,我们要注意三点:是哪个组件在做序列化反序列化、整个过程有几次序列化反序列化,以及目前是序列化还是反序列化
(4)反序列化时要小心类的构造方法,默认情况下框架调用的是无参构造方法,对于需要序列化的POJO尽量不要自定义构造方法
(5)枚举作为API接口参数或返回值的两大坑,第一个坑客户端和服务端的枚举定义不一致时会出现异常,第二个坑枚举序列化反序列化实现自定义的字段非常麻烦,会涉及Jackson的Bug,因此建议尽量在程序内部使用,而不是作为API接口的参数或返回值
二、Java8时间类
Java 8推出了新的时间日期类ZoneId、ZoneOffset、LocalDateTime、ZonedDateTime和DateTimeFormatter
时区以及格式化
//一个时间表示
String stringDate = "2020-01-02 22:00:00";
//初始化三个时区
ZoneId timeZoneSH = ZoneId.of("Asia/Shanghai");
ZoneId timeZoneNY = ZoneId.of("America/New_York");
ZoneId timeZoneJST = ZoneOffset.ofHours(9);
//格式化器
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
ZonedDateTime date = ZonedDateTime.of(LocalDateTime.parse(stringDate, dateTimeFormatter), timeZoneJST);
//使用DateTimeFormatter格式化时间,可以通过withZone方法直接设置格式化使用的时区
DateTimeFormatter outputFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss Z");
System.out.println(timeZoneSH.getId() + outputFormat.withZone(timeZoneSH).format(date));
System.out.println(timeZoneNY.getId() + outputFormat.withZone(timeZoneNY).format(date));
System.out.println(timeZoneJST.getId() + outputFormat.withZone(timeZoneJST).format(date));
//使用刚才定义的DateTimeFormatterBuilder构建的DateTimeFormatter来解析这个时间
LocalDateTime localDateTime = LocalDateTime.parse("2020/1/2 12:34:56.789", dateTimeFormatter);
//解析成功
System.out.println(localDateTime.format(dateTimeFormatter));
//使用yyyyMM格式解析20160901是否可以成功呢?
String dt = "20160901";
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMM");
System.out.println("result:" + dateTimeFormatter.parse(dt));
日期时间计算
System.out.println("//测试操作日期");
System.out.println(LocalDate.now()
.minus(Period.ofDays(1))
.plus(1, ChronoUnit.DAYS)
.minusMonths(1)
.plus(Period.ofMonths(1)));
System.out.println("//本月的第一天");
System.out.println(LocalDate.now().with(TemporalAdjusters.firstDayOfMonth()));
System.out.println("//今年的程序员日");
System.out.println(LocalDate.now().with(TemporalAdjusters.firstDayOfYear()).plusDays(255));
System.out.println("//今天之前的一个周六");
System.out.println(LocalDate.now().with(TemporalAdjusters.previous(DayOfWeek.SATURDAY)));
System.out.println("//本月最后一个工作日");
System.out.println(LocalDate.now().with(TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY)));
System.out.println("//计算日期差");
LocalDate today = LocalDate.of(2019, 12, 12);
LocalDate specifyDate = LocalDate.of(2019, 10, 1);
System.out.println(Period.between(specifyDate, today).getDays());
System.out.println(Period.between(specifyDate, today));
System.out.println(ChronoUnit.DAYS.between(specifyDate, today));
Date与LocalDateTime转换
Date in = new Date();
LocalDateTime ldt = LocalDateTime.ofInstant(in.toInstant(), ZoneId.systemDefault());
Date out = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());
SimpleDateFormat使用过程中的问题
(1)定义的static的SimpleDateFormat可能出现线程安全问题
如果多线程调用parse方法,意味着多线程在并发操作一个Calendar,可能会出现一个线程还没来得及处理Calendar就被另一个线程清空的情况
(2)当需要解析的字符串和格式不匹配时,SimpleDateFormat不会抛异常