假设现在有需求如下:比较2个相同类型的不同对象,找到这2个对象的不同的地方,并展示记录一下。当然说的是相互对应的属性啦。
带着这个需求,看下面的例子。(我写代码的都不嫌弃长,你看代码的就也别嫌弃咯。)
package com.lxk.test;
import com.google.common.collect.Lists;
import com.lxk.model.Car;
import com.lxk.model.Dog;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
/**
* Compare the difference between two objects
* <p>
* Created by lxk on 2017/3/12
*/
public class CompareObject {
public static void main(String[] args) {
Dog dog1 = new Dog("大师兄的dog", true, true);
Dog dog2 = new Dog("大师兄的dog", false, false);
List<Dog> dogs = Lists.newArrayList();
List<Dog> myDogs = Lists.newArrayList();
dogs.add(dog1);
myDogs.add(dog2);
List<String> boys = Lists.newArrayList("tom", "jerry", "jack");
//List<String> myBoys = Lists.newArrayList("tom", "jerry", "jack");//这行注释打开,下行代码注释掉,则boys属性就相同了
List<String> myBoys = Lists.newArrayList("tom hanks", "gery", "pul");
Car car1 = new Car("q7", 182, dogs, boys);
Car car2 = new Car("a6", 152, myDogs, myBoys);
contrastObj(car1, car2);
}
private static void contrastObj(Object obj1, Object obj2) {
if (obj1 instanceof Car && obj2 instanceof Car) {
Car pojo1 = (Car) obj1;
Car pojo2 = (Car) obj2;
List<String> textList = Lists.newArrayList();
try {
Class clazz = pojo1.getClass();
Field[] fields = pojo1.getClass().getDeclaredFields();
for (Field field : fields) {
PropertyDescriptor pd = new PropertyDescriptor(field.getName(), clazz);
Method getMethod = pd.getReadMethod();
Object o1 = getMethod.invoke(pojo1);
Object o2 = getMethod.invoke(pojo2);
String s1 = o1 == null ? "" : o1.toString();//避免空指针异常
String s2 = o2 == null ? "" : o2.toString();//避免空指针异常
//思考下面注释的这一行:会bug的,虽然被try catch了,程序没报错,但是结果不是我们想要的
//if (!o1.toString().equals(o2.toString())) {
if (!s1.equals(s2)) {
textList.add("不一样的属性:" + field.getName() + " 属性值:[" + s1 + "," + s2 + "]");
}
}
} catch (Exception e) {
System.out.println(e.getMessage());
}
for (Object object : textList) {
System.out.println(object);
}
}
}
}
代码里面使用的两个model,也是极为重要的。
Car的bean如下:
package com.lxk.model;
import java.util.List;
public class Car implements Comparable<Car> {
private String sign;
private int price;
private List<Dog> myDog;
private List<String> boys;
public Car(String sign, int price) {
this.sign = sign;
this.price = price;
}
public Car(String sign, int price, List<Dog> myDog) {
this.sign = sign;
this.price = price;
this.myDog = myDog;
}
public Car(String sign, int price, List<Dog> myDog, List<String> boys) {
this.sign = sign;
this.price = price;
this.myDog = myDog;
this.boys = boys;
}
//getter and setter
}
Dog的bean如下:
package com.lxk.model;
/**
* 测试boolean属性的getter和setter
* <p>
* Created by lxk on 2016/12/23
*/
public class Dog {
private String name;
private boolean isLoyal;//是忠诚的
private boolean alive;//活蹦乱跳的
public Dog(boolean isLoyal, boolean alive) {
this.isLoyal = isLoyal;
this.alive = alive;
}
public Dog(String name, boolean isLoyal, boolean alive) {
this.name = name;
this.isLoyal = isLoyal;
this.alive = alive;
}
//getter and setter
//@Override
//public boolean equals(Object o) {
// if (this == o) return true;
// if (!(o instanceof Dog)) return false;
// Dog dog = (Dog) o;
// return Objects.equal(getName(), dog.getName());
//}
//
//@Override
//public int hashCode() {
// return Objects.hashCode(getName());
//}
}
先不要在意,我为什么把Dog bean的hashcode和equal方法给注释啦先。
如上代码的执行结果 图,如下:
从代码的执行情况看,可以得到,我们要的不同的属性的名称,当然你是可以把这些sign price myDog boys这些属性,再次转化成你要的文字。
注意后面的对应的属性值。类型是String 或者int 或者List<String>这些类型,直接使用toString()方法就可以把他转化成字符串了,但是看到myDog属性,这个自定义对象的list的toString()的时候,却是两个地址,对,这个就是他们的内存堆地址吧。可以看到,2个集合所指向的引用是不一样的。
现在,看第二种情况,就是把Dog bean里面的注释,打开。其他地方的代码不动。
然后指向结果如下图:
可以看到,现在 myDog属性的值,竟然一样啦。但是,我们可以清楚的看到,我们的两个Car对象里面包含的Dog对象是不一样的。只是,这2个Car对象的两个Dog对象的name属性是一样的,都是:大师兄的dog。
因为,你在Dog bean里面已经重写了hashcode和equal两个方法,那么在new对象的时候,他就会只根据这个name属性去生成hashcode,他决定放在堆内存的什么位置。
那么就算,在我们看起来,那两个Car对象里面的两个Dog对象是不同的,但是计算机内存却认为是相同的。而且,toString()方法得到的结果也是跟他的内存地址有关系的。所以,比较结果,就是一样的。
这就是意外。
在实际开发中,我们也会有这种情况,可能因为某种需要,我们把某个bean的hashcode和equal方法给重写了。就像我这里 的这个Dog类,但是这个类又在其他地方被使用了。他的不同也决定另外一个对象是不是相同的,就比如我要判断两个Car对象是不是相同的。用如上方法的话,那么就判断失败啦。
怎么应对呢?一般,当你发现你判断失败了,多半是自己测试出来的,我表示我当时就是自己测试出来的,后来发现就是写入hashcode和equal方法的属性相同了,他才相同,就想到了这一点。那么就直接再重写下toString()方法,把你要比较的属性,都写在toString方法里面,这么一来。那个list类型的属性在toString之后就会变得不是内存地址啦
下面是我重写了toString()方法的代码,以及又一次的执行结果。
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", isLoyal=" + isLoyal +
", alive=" + alive +
'}';
}
最后,总结一下。
要想搞明白这个,你得知道:
1,为什么要重写 hashcode和equal,以及这2个方法的作用到底 是什么。
2,正确重写hashcode和equal的姿势。上面的是可以预防空指针的哟。哦,虽然是还是这个guava的工具包里面的东西。
(这个guava包这个方法被jdk1.7借鉴了,在java.util包里面也有这个方法,可以试一下)
2,当然了,这个重点还是反射的简单应用,我上面的那个问题,只是自己试验出来的问题,算是扩展问题。
关于代码里面的Lists 就是一个guava,Google的一个工具包,感兴趣的,可以查查,也是很涨姿势的。哦,也行你已经知道啦。