├── README.md └── src └── cn └── localhost01 ├── EqualsUtilTest.java └── EqualsUtil.java /README.md: -------------------------------------------------------------------------------- 1 | # EqualsUtil 2 | *用于比较两个对象是否内容相同* 3 | 4 | ## 支持: 5 | * 支持级联比较:除了比较两个对象的基本类型属性外,还支持迭代比较对象属性的子属性; 6 | * 定制比较字段:只会比较指定字段数组的字段,同时可和“级联比较”兼容,即给定的字段数组同样生效于对象属性的子属性; 7 | * 定制忽略字段:比较时会忽略指定的字段数组的字段,同时可和“级联比较”兼容,即给定的字段数组同样生效于对象属性的子属性; 8 | 9 | 技术: 10 | * 基于Java反射实现 11 | -------------------------------------------------------------------------------- /src/cn/localhost01/EqualsUtilTest.java: -------------------------------------------------------------------------------- 1 | package cn.localhost01; 2 | 3 | public class EqualsUtilTest { 4 | 5 | public static void main(String[] args) { 6 | B b = new B(); 7 | b.setNum(1); 8 | b.setChina(true); 9 | b.setPhone(12545L); 10 | b.setHeight(15.1f); 11 | b.setDepart("zs"); 12 | b.setData((byte) 20); 13 | b.setPower('z'); 14 | 15 | B b1 = new B(); 16 | b1.setNum(2); 17 | b1.setChina(true); 18 | b1.setPhone(1254L); 19 | b1.setHeight(15.1f); 20 | b1.setDepart("zs"); 21 | b1.setData((byte) 20); 22 | b1.setPower('z'); 23 | 24 | A a = new A(); 25 | a.setAge(1); 26 | a.setBoy(true); 27 | a.setIdCard(12545L); 28 | a.setInCome(15.1f); 29 | a.setName("zs"); 30 | a.setPass((byte) 20); 31 | a.setTag('a'); 32 | a.setB(b); 33 | 34 | A a2 = new A(); 35 | a2.setAge(1); 36 | a2.setBoy(true); 37 | a2.setIdCard(1254L); 38 | a2.setInCome(15.1f); 39 | a2.setName("zs"); 40 | a2.setPass((byte) 200); 41 | a2.setTag('a'); 42 | a2.setB(b1); 43 | System.out.println(EqualsUtil 44 | .assertEqualsWithFields(a, a2, new String[] { "tag","isBoy", "name", "b", "isChina" }, true)); 45 | } 46 | } 47 | 48 | class A { 49 | 50 | /** 51 | * @return the b 52 | */ 53 | public B getB() { 54 | return b; 55 | } 56 | 57 | /** 58 | * @param b 59 | * the b to set 60 | */ 61 | public void setB(B b) { 62 | this.b = b; 63 | } 64 | 65 | /** 66 | * @return the age 67 | */ 68 | public int getAge() { 69 | return age; 70 | } 71 | 72 | /** 73 | * @param age 74 | * the age to set 75 | */ 76 | public void setAge(int age) { 77 | this.age = age; 78 | } 79 | 80 | /** 81 | * @return the name 82 | */ 83 | public String getName() { 84 | return name; 85 | } 86 | 87 | /** 88 | * @param name 89 | * the name to set 90 | */ 91 | public void setName(String name) { 92 | this.name = name; 93 | } 94 | 95 | /** 96 | * @return the idCard 97 | */ 98 | public long getIdCard() { 99 | return IdCard; 100 | } 101 | 102 | /** 103 | * @param idCard 104 | * the idCard to set 105 | */ 106 | public void setIdCard(long idCard) { 107 | IdCard = idCard; 108 | } 109 | 110 | /** 111 | * @return the isBoy 112 | */ 113 | public boolean isBoy() { 114 | return isBoy; 115 | } 116 | 117 | /** 118 | * @param isBoy 119 | * the isBoy to set 120 | */ 121 | public void setBoy(boolean isBoy) { 122 | this.isBoy = isBoy; 123 | } 124 | 125 | /** 126 | * @return the inCome 127 | */ 128 | public float getInCome() { 129 | return inCome; 130 | } 131 | 132 | /** 133 | * @param inCome 134 | * the inCome to set 135 | */ 136 | public void setInCome(float inCome) { 137 | this.inCome = inCome; 138 | } 139 | 140 | /** 141 | * @return the pass 142 | */ 143 | public byte getPass() { 144 | return pass; 145 | } 146 | 147 | /** 148 | * @param pass 149 | * the pass to set 150 | */ 151 | public void setPass(byte pass) { 152 | this.pass = pass; 153 | } 154 | 155 | /** 156 | * @return the tag 157 | */ 158 | public char getTag() { 159 | return tag; 160 | } 161 | 162 | /** 163 | * @param tag 164 | * the tag to set 165 | */ 166 | public void setTag(char tag) { 167 | this.tag = tag; 168 | } 169 | 170 | private boolean isBoy; 171 | private String name; 172 | private B b; 173 | private int age; 174 | private long IdCard; 175 | private float inCome; 176 | private byte pass; 177 | private char tag; 178 | } 179 | 180 | class B { 181 | 182 | /** 183 | * @return the num 184 | */ 185 | public int getNum() { 186 | return num; 187 | } 188 | 189 | /** 190 | * @param num 191 | * the num to set 192 | */ 193 | public void setNum(int num) { 194 | this.num = num; 195 | } 196 | 197 | /** 198 | * @return the depart 199 | */ 200 | public String getDepart() { 201 | return depart; 202 | } 203 | 204 | /** 205 | * @param depart 206 | * the depart to set 207 | */ 208 | public void setDepart(String depart) { 209 | this.depart = depart; 210 | } 211 | 212 | /** 213 | * @return the phone 214 | */ 215 | public long getPhone() { 216 | return phone; 217 | } 218 | 219 | /** 220 | * @param phone 221 | * the phone to set 222 | */ 223 | public void setPhone(long phone) { 224 | this.phone = phone; 225 | } 226 | 227 | /** 228 | * @return the isChina 229 | */ 230 | public boolean isChina() { 231 | return isChina; 232 | } 233 | 234 | /** 235 | * @param isChina 236 | * the isChina to set 237 | */ 238 | public void setChina(boolean isChina) { 239 | this.isChina = isChina; 240 | } 241 | 242 | /** 243 | * @return the height 244 | */ 245 | public float getHeight() { 246 | return height; 247 | } 248 | 249 | /** 250 | * @param height 251 | * the height to set 252 | */ 253 | public void setHeight(float height) { 254 | this.height = height; 255 | } 256 | 257 | /** 258 | * @return the data 259 | */ 260 | public byte getData() { 261 | return data; 262 | } 263 | 264 | /** 265 | * @param data 266 | * the data to set 267 | */ 268 | public void setData(byte data) { 269 | this.data = data; 270 | } 271 | 272 | /** 273 | * @return the power 274 | */ 275 | public char getPower() { 276 | return power; 277 | } 278 | 279 | /** 280 | * @param power 281 | * the power to set 282 | */ 283 | public void setPower(char power) { 284 | this.power = power; 285 | } 286 | 287 | private int num; 288 | private String depart; 289 | private long phone; 290 | private boolean isChina; 291 | private float height; 292 | private byte data; 293 | private char power; 294 | } 295 | -------------------------------------------------------------------------------- /src/cn/localhost01/EqualsUtil.java: -------------------------------------------------------------------------------- 1 | package cn.localhost01; 2 | 3 | import java.lang.reflect.Field; 4 | import java.lang.reflect.Method; 5 | import java.util.Arrays; 6 | 7 | public abstract class EqualsUtil { 8 | 9 | // 两个对象比较时,assertEquals方法调用次数 10 | private static int assertCount; 11 | 12 | /** 13 | * 描述:比较两个对象所有字段内容是否相同(子对象也会进行比较) 14 | *
15 | * 16 | * @param _obj1 17 | * @param _obj2 18 | * 19 | * @return boolean 20 | * 21 | * @throws Exception 22 | * @author ran.chunlin 23 | * @date 2018/1/3 21:04 24 | * @version 1.0 25 | */ 26 | public static boolean assertEquals(Object _obj1, Object _obj2) { 27 | return assertEqualsIgnoreFields(_obj1, _obj2, null, true); 28 | } 29 | 30 | /** 31 | * 描述:比较两个对象指定字段内容是否相同(比较时只比较这些字段) 32 | *
33 | * 34 | * @param _obj1 35 | * @param _obj2 36 | * @param _Fields 要比较的字段数组,不区分大小写 37 | * @param _isCascade 是否级联(true:比较深度将扩大到子对象字段;false:比较深度只限于传入对象) 38 | * 39 | * @return boolean 40 | * 41 | * @throws Exception 42 | * @author ran.chunlin 43 | * @date 2018/1/3 21:03 44 | * @version 1.0 45 | */ 46 | public static boolean assertEqualsWithFields(Object _obj1, Object _obj2, String[] _Fields, boolean _isCascade) { 47 | return baseEquals(_obj1, _obj2, _Fields, _isCascade, EqualsMode.WITH_MODE); 48 | } 49 | 50 | /** 51 | * 描述:比较两个对象除指定字段外的其他字段内容是否相同(比较时将忽略这些字段) 52 | *
53 | * 约定:比较的字段需含有get方法
54 | *
55 | * @param _obj1
56 | * @param _obj2
57 | * @param _ignoreFields 要忽略的字段数组,不区分大小写
58 | * @param _isCascade 是否级联(true:比较深度将扩大到子对象字段;false:比较深度只限于传入对象)
59 | *
60 | * @return boolean
61 | *
62 | * @throws Exception
63 | * @author ran.chunlin
64 | * @date 2018/1/3 20:58
65 | * @version 1.0
66 | */
67 | public static boolean assertEqualsIgnoreFields(Object _obj1, Object _obj2, String[] _ignoreFields,
68 | boolean _isCascade) {
69 | return baseEquals(_obj1, _obj2, _ignoreFields, _isCascade, EqualsMode.IGNORE_MODE);
70 | }
71 |
72 | /**
73 | * 描述:比较两个对象内容是否相同
74 | *
75 | * @param _obj1
76 | * @param _obj2
77 | * @param _Fields 字段数组
78 | * @param _isCascade 是否级联
79 | * @param _equalsMode 比较模式(WITH_MODE:只比较字段数组的字段;IGNORE_MODE:忽略字段数组的字段)
80 | *
81 | * @return boolean
82 | *
83 | * @throws Exception
84 | * @author ran.chunlin
85 | * @date 2018/1/3 20:51
86 | * @version 1.0
87 | */
88 | private static boolean baseEquals(Object _obj1, Object _obj2, String[] _Fields, boolean _isCascade,
89 | EqualsMode _equalsMode) {
90 |
91 | if (_obj1 == _obj2)
92 | return true;
93 |
94 | // 解决两个对象的属性互相持有对方引用或自身引用导致死循环问题。默认比较次数达到100次时,返回true(该返回是不可信的)
95 | if (assertCount == 100)
96 | return true;
97 |
98 | if (assertCount == 0) {//只关心传入对象各自是否为空
99 | if (_obj1 == null || _obj2 == null)
100 | return false;
101 |
102 | if (_obj1.getClass() != _obj2.getClass())
103 | return false;
104 | }
105 |
106 | if (assertCount > 0) {
107 | if (_obj1 != null && _obj2 != null)
108 | ;//子对象都不为空,继续执行比较逻辑
109 | else if (_obj1 == null && _obj2 == null) // 两个子对象均为空,返回TRUE
110 | return true;
111 | else //其中一个为空
112 | return false;
113 | }
114 |
115 | assertCount++;
116 |
117 | Class cla1 = _obj1.getClass();
118 |
119 | if (cla1.isPrimitive() || _obj1 instanceof String) // 判断cla1是否属于基本数据类型或String类型
120 | return _obj1.equals(_obj2);
121 |
122 | if (isWrapperType(cla1)) {// 判断cla1是否属于基本数据类型的封装类型
123 |
124 | try {
125 | Class clazz = (Class) cla1.getField("TYPE").get(null);
126 | if (clazz.isPrimitive())
127 | return _obj1.equals(_obj2);
128 | } catch (Exception e) {// 忽略,继续向下执行
129 | }
130 | }
131 |
132 | // 调用类中所有字段相应的get方法进行比较
133 | return equalsContentByAllField(_obj1, _obj2, _Fields, _isCascade, _equalsMode);
134 | }
135 |
136 | /* 描述:比较两个对象仅内容是否相等(直接调用所有get方法进行比较) */
137 | @Deprecated private static boolean equalsContentByAllGet(Object _obj1, Object _obj2, String[] _Fields,
138 | boolean _isCascade, EqualsMode _equalsMode) {
139 |
140 | Method[] methods = _obj1.getClass().getMethods();// 获取类中所有方法
141 | Method[] methods2 = _obj2.getClass().getMethods();// 获取类中所有方法
142 |
143 | for (Method method : methods) {// 遍历一个对象方法数组
144 |
145 | if (!method.getName().startsWith("get") && !method.getName().startsWith("is")) // 非get或is方法不取
146 | continue;
147 |
148 | boolean isContain = isContainInArray(_Fields, method.getName().substring(3)) || isContainInArray(_Fields,
149 | method.getName());
150 |
151 | if (_equalsMode == EqualsMode.WITH_MODE && !isContain) // 指定字段模式,但不包含指定字段
152 | continue;
153 |
154 | if (_equalsMode == EqualsMode.IGNORE_MODE && isContain) // 忽略字段模式,但包含指定字段
155 | continue;
156 |
157 | for (Method method2 : methods2) {// 遍历一个对象方法数组
158 | if (!method2.getName().startsWith("get") && !method2.getName().startsWith("is")) // 非get或is方法不取
159 | continue;
160 |
161 | Object value = null;// 存放值
162 | Object value2 = null;// 存放值
163 | if (method.getName().equals(method2.getName())) {
164 | try {
165 | value = method.invoke(_obj1, new Object[] {});// 调用get方法
166 | } catch (Exception e) {
167 | value = null;
168 | }
169 | try {
170 | value2 = method2.invoke(_obj2, new Object[] {});// 调用get方法
171 | } catch (Exception e) {
172 | value2 = null;
173 | }
174 | // 比较两对象是否相等,不相等返回false,相等继续下一个比较
175 | if (_isCascade) { // 是否级联忽略/比较对象属性的字段
176 | if (!baseEquals(value, value2, _Fields, _isCascade, _equalsMode))
177 | return false;
178 | } else {
179 | if (!baseEquals(value, value2, null, false, EqualsMode.IGNORE_MODE))
180 | return false;
181 | }
182 | }
183 | }
184 |
185 | }
186 |
187 | return true;
188 | }
189 |
190 | /* 描述:比较两个对象仅内容是否相等(调用类中所有字段相应的get方法进行比较) */
191 | private static boolean equalsContentByAllField(Object _obj1, Object _obj2, String[] _Fields, boolean _isCascade,
192 | EqualsMode _equalsMode) {
193 | Class cla1 = _obj1.getClass();
194 | Class cla2 = _obj2.getClass();
195 |
196 | Field[] fields1 = cla1.getDeclaredFields();
197 | Field[] fields2 = cla2.getDeclaredFields();
198 |
199 | for (Field field1 : fields1) {// 遍历一个对象字段数组
200 |
201 | if (_equalsMode == EqualsMode.WITH_MODE && !isContainInArray(_Fields, field1.getName())) // 指定字段模式,但不包含指定字段
202 | continue;
203 |
204 | if (_equalsMode == EqualsMode.IGNORE_MODE && isContainInArray(_Fields, field1.getName())) // 忽略字段模式,但包含指定字段
205 | continue;
206 |
207 | if (!isContainInArray(fields2, field1)) // 如果另一个对象字段数组都不包含一个对象的某个字段
208 | return false;
209 |
210 | Object obj1 = getValueByField(_obj1, field1);// 获取值
211 | Object obj2 = getValueByField(_obj2, field1);// 获取值
212 |
213 | // 比较两对象是否相等,不相等返回false,相等继续下一个比较
214 | if (_isCascade) {// 是否级联忽略对象属性的字段比较
215 | if (!baseEquals(obj1, obj2, _Fields, _isCascade, _equalsMode))
216 | return false;
217 | } else {
218 | if (!baseEquals(obj1, obj2, null, false, EqualsMode.IGNORE_MODE))
219 | return false;
220 | }
221 | }
222 |
223 | return true;
224 | }
225 |
226 | /**
227 | * 描述: 根据字段名称从对象中取值
228 | *
229 | * @param _obj1
230 | * @param _field
231 | *
232 | * @return java.lang.Object
233 | *
234 | * @throws Exception
235 | * @author ran.chunlin
236 | * @date 2018/1/3 22:04
237 | * @version 1.0
238 | */
239 | private static Object getValueByField(Object _obj1, Field _field) {
240 |
241 | String sFieldName = _field.getName();// 获取字段名称
242 | Method[] methods = _obj1.getClass().getMethods();// 获取类中所有方法
243 | Object value = null;// 存放值
244 |
245 | for (Method method : methods) {
246 | if (!method.getName().startsWith("get") && !method.getName().startsWith("is")) // 非get或is方法不取
247 | continue;
248 |
249 | if (method.getName().substring(3).equalsIgnoreCase(sFieldName) || method.getName()
250 | .equalsIgnoreCase(sFieldName)) {// 找到了字段的get方法
251 | try {
252 | value = method.invoke(_obj1, new Object[] {});// 调用get方法
253 | return value;
254 | } catch (Exception e) {
255 | return null;
256 | }
257 | }
258 | }
259 | return null;
260 | }
261 |
262 | /**
263 | * 描述: 判断传入类型是否是基本数据类型的封装类型
264 | *
265 | * @param _cla
266 | *
267 | * @return boolean
268 | *
269 | * @throws Exception
270 | * @author ran.chunlin
271 | * @date 2018/1/3 21:19
272 | * @version 1.0
273 | */
274 | private static boolean isWrapperType(Class _cla) {
275 |
276 | Field[] fieldArray = _cla.getFields();
277 | int size = fieldArray.length;
278 |
279 | String[] strFieldArray = new String[size];
280 |
281 | for (int i = 0; i < size; i++)
282 | strFieldArray[i] = fieldArray[i].getName();
283 |
284 | if (strFieldArray == null || strFieldArray.length == 0) // 如果该字符串或数组本身就为空
285 | return false;
286 |
287 | for (String strField : strFieldArray)
288 | if ("TYPE".equalsIgnoreCase(strField)) // 存在于数组中
289 | return true;
290 |
291 | return false;
292 | }
293 |
294 | /**
295 | * 描述: 数组是否包含指定数据
296 | *
297 | * @param _strs
298 | * @param _str
299 | *
300 | * @return boolean
301 | *
302 | * @throws Exception
303 | * @author ran.chunlin
304 | * @date 2018/1/3 22:23
305 | * @version 1.0
306 | */
307 | private static boolean isContainInArray(String[] _strs, String _str) {
308 | return Arrays.asList(_strs).contains(_str);
309 | }
310 |
311 | /**
312 | * 描述: 数组是否包含指定数据
313 | *
314 | * @param _fields
315 | * @param _field
316 | *
317 | * @return boolean
318 | *
319 | * @throws Exception
320 | * @author ran.chunlin
321 | * @date 2018/1/3 22:22
322 | * @version 1.0
323 | */
324 | private static boolean isContainInArray(Field[] _fields, Field _field) {
325 | int size = _fields.length;
326 |
327 | String[] strFields = new String[size];
328 | for (int i = 0; i < size; i++)
329 | strFields[i] = _fields[i].getName();
330 |
331 | return isContainInArray(strFields, _field.getName());
332 | }
333 |
334 | }
335 |
336 | enum EqualsMode {
337 | /**
338 | * 指定比较字段数组
339 | */
340 | WITH_MODE, /**
341 | * 指定忽略字段数组
342 | */
343 | IGNORE_MODE
344 | }
345 |
--------------------------------------------------------------------------------