示例引入
====
那么,我们既然要记录每次保存前后的字段值变化,则肯定需要进行字段值的比较。
比如,我们下面有一个 User 对象:
@Data
public class User {
private String name;
private int age;
}
我们如果要记录每次 User 对象保存的前后变化,可能大家最容易想到的,就是对每个字段依次进行比较,获取结果,具体操作如下:
@Data
public class User {
private String name;
private int age;
/**
* 获取比较结果
* tips: 这里我们做演示,先忽略比较对象和比较值为 null 的情况
*
* @param u 待比较对象
* @return 比较结果
*/
public String compare(User u) {
StringBuilder sb = new StringBuilder();
if (!u.getName().equals(this.name)) {
sb.append(String.format("[姓名:%s -> %s], ", u.getName(), this.name));
}
if (u.getAge() != this.age) {
sb.append(String.format("[年龄:%d -> %d], ", u.getAge(), this.age));
}
return sb.toString();
}
}
我们不妨来写个示例测试一下:
public class Test {
public static void main(String[] args) {
// 模拟一个保存前的数据对象
User u1 = new User();
u1.setName("大乔");
u1.setAge(24);
// 模拟一个保存后的数据对象
User u2 = new User();
u2.setName("小乔");
u2.setAge(22);
// 获取保存前后的字段变化情况
String result = u2.compare(u1);
System.out.println(result);
}
}
我们运行程序,可以发现,已经成功获取到了每个字段的数据变化情况:
设想下,如果随着业务的拓展,当我们的 2 个字段增加到多个字段的时候。
如果这个时候,我们再依次去比较每个字段,是不是就显得有点繁琐了。并且逻辑性也高度重合,这时的代码就会显得十分的臃肿,那么,这个时候,我们就不妨考虑写一个通用的方法了。
工具文件
====
在写这个通用方法时,我们应该考虑到以下几点:
(1)可以接收任何对象的比较,但比较的对象应该是同个对象;
(2)可以给字段进行一个备注,因为我们看到的最终内容,应该是一个中文名称;
(3)一个对象中,可以忽略某些字段进行比较,只要我需要的字段进行比较。
于是,针对上面的需求,我写了一个通用的比较方法,具体包含下面三个文件:
CompareUtils
package com.zyqok.utils.compare;
import java.lang.reflect.Field;
import java.util.*;
/**
* 使用须知: <br>
* (1)该工具类主要用于两个同类对象的属性值比较; <br>
* (2)使用本工具类前,请将对应的类属性上打上 @Compare("xxx") 注解,其中xxx为字段的表达名称;<br>
* (3)为了比较灵活,只有打了该注解才会进行比较,不打的字段则不会进行比较 <br>
* (4)比较后,只会返回有变化的字段,无变化的字符则不返回 <br>
*
* @author zyqok
* @since 2021/05/05
*/
public class CompareUtils<T> {
private static final String COMMA = ",";
/**
* 属性比较
*
* @param source 源数据对象
* @param target 目标数据对象
* @return 对应属性值的比较变化
*/
public String compare(T source, T target) {
return compare(source, target, null);
}
/**
* 属性比较
*
* @param source 源数据对象
* @param target 目标数据对象
* @param ignoreCompareFields 忽略比较的字段
* @return 对应属性值的比较变化
*/
public String compare(T source, T target, List<String> ignoreCompareFields) {
if (Objects.isNull(source) && Objects.isNull(target)) {
return "";
}
Map<String, CompareNode> sourceMap = this.getFiledValueMap(source);
Map<String, CompareNode> targetMap = this.getFiledValueMap(target);
if (sourceMap.isEmpty() && targetMap.isEmpty()) {
return "";
}
// 如果源数据为空,则只显示目标数据,不显示属性变化情况
if (sourceMap.isEmpty()) {
return doEmpty(targetMap, ignoreCompareFields);
}
// 如果源数据为空,则显示属性变化情况
String s = doCompare(sourceMap, targetMap, ignoreCompareFields);
if (!s.endsWith(COMMA)) {
return s;
}
return s.substring(0, s.length() - 1);
}
private String doEmpty(Map<String, CompareNode> targetMap, List<String> ignoreCompareFields) {
StringBuilder sb = new StringBuilder();
Collection<CompareNode> values = targetMap.values();
int size = values.size();
int current = 0;
for (CompareNode node : values) {
current++;
Object o = Optional.ofNullable(node.getFieldValue()).orElse("");
if (Objects.nonNull(ignoreCompareFields) && ignoreCompareFields.contains(node.getFieldKey())) {
continue;
}
if (o.toString().length() > 0) {
sb.append("[" + node.getFieldName() + ":" + o + "]");
if (current < size) {
sb.append(COMMA);
}
}
}
return sb.toString();
}
private String doCompare(Map<String, CompareNode> sourceMap, Map<String, CompareNode> targetMap, List<String> ignoreCompareFields) {
StringBuilder sb = new StringBuilder();
Set<String> keys = sourceMap.keySet();
int size = keys.size();
int current = 0;
for (String key : keys) {
current++;
CompareNode sn = sourceMap.get(key);
CompareNode tn = targetMap.get(key);
if (Objects.nonNull(ignoreCompareFields) && ignoreCompareFields.contains(sn.getFieldKey())) {
continue;
}
String sv = Optional.ofNullable(sn.getFieldValue()).orElse("").toString();
String tv = Optional.ofNullable(tn.getFieldValue()).orElse("").toString();
// 只有两者属性值不一致时, 才显示变化情况
if (!sv.equals(tv)) {
sb.append(String.format("[%s:%s -> %s]", sn.getFieldName(), sv, tv));
if (current < size) {
sb.append(COMMA);
}
}
}
return sb.toString();
}
private Map<String, CompareNode> getFiledValueMap(T t) {
if (Objects.isNull(t)) {
return Collections.emptyMap();
}
Field[] fields = t.getClass().getDeclaredFields();
if (Objects.isNull(fields) || fields.length == 0) {
return Collections.emptyMap();
}
Map<String, CompareNode> map = new LinkedHashMap();
for (Field field : fields) {
Compare compareAnnotation = field.getAnnotation(Compare.class);
if (Objects.isNull(compareAnnotation)) {
continue;
}
field.setAccessible(true);
try {
String fieldKey = field.getName();
CompareNode node = new CompareNode();
node.setFieldKey(fieldKey);
node.setFieldValue(field.get(t));
node.setFieldName(compareAnnotation.value());
map.put(field.getName(), node);
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
}
return map;
}
}
# 写在最后
作为一名即将求职的程序员,面对一个可能跟近些年非常不同的 2019 年,你的就业机会和风口会出现在哪里?在这种新环境下,工作应该选择大厂还是小公司?已有几年工作经验的老兵,又应该如何保持和提升自身竞争力,转被动为主动?
**就目前大环境来看,跳槽成功的难度比往年高很多。一个明显的感受:今年的面试,无论一面还是二面,都很考验Java程序员的技术功底。**
最近我整理了一份复习用的面试题及面试高频的考点题及技术点梳理成一份“**Java经典面试问题(含答案解析).pdf**和一份网上搜集的“**Java程序员面试笔试真题库.pdf**”(实际上比预期多花了不少精力),**包含分布式架构、高可扩展、高性能、高并发、Jvm性能调优、Spring,MyBatis,Nginx源码分析,Redis,ActiveMQ、Mycat、Netty、Kafka、Mysql、Zookeeper、Tomcat、Docker、Dubbo、Nginx等多个知识点高级进阶干货!**
**由于篇幅有限,为了方便大家观看,这里以图片的形式给大家展示部分的目录和答案截图!**
![](https://img-blog.csdnimg.cn/img_convert/e36ec0fdf56dbbd38d148b15dae0e092.webp?x-oss-process=image/format,png)
### Java经典面试问题(含答案解析)
![](https://img-blog.csdnimg.cn/img_convert/3d33728e913f07c8d56b42fd924a4eda.webp?x-oss-process=image/format,png)
### 阿里巴巴技术笔试心得
![](https://img-blog.csdnimg.cn/img_convert/fa8bb7cb98b07bd6d5fee343bdf70776.webp?x-oss-process=image/format,png)
技术功底。**
最近我整理了一份复习用的面试题及面试高频的考点题及技术点梳理成一份“**Java经典面试问题(含答案解析).pdf**和一份网上搜集的“**Java程序员面试笔试真题库.pdf**”(实际上比预期多花了不少精力),**包含分布式架构、高可扩展、高性能、高并发、Jvm性能调优、Spring,MyBatis,Nginx源码分析,Redis,ActiveMQ、Mycat、Netty、Kafka、Mysql、Zookeeper、Tomcat、Docker、Dubbo、Nginx等多个知识点高级进阶干货!**
**由于篇幅有限,为了方便大家观看,这里以图片的形式给大家展示部分的目录和答案截图!**
[外链图片转存中...(img-nGx0UdVm-1714363541782)]
### Java经典面试问题(含答案解析)
[外链图片转存中...(img-8UqVZWSN-1714363541783)]
### 阿里巴巴技术笔试心得
[外链图片转存中...(img-98rvS4vw-1714363541783)]
> **本文已被[CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】](https://bbs.csdn.net/topics/618154847)收录**