为了便于验证,定义一个User实体类如下:
public class User {
private int id;
private String name;
private Integer age;
private Date birthday;
public User(int id, String name, Integer age, Date birthday) {
super();
this.id = id;
this.name = name;
this.age = age.intValue();
this.birthday = birthday;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", age=" + age
+ ", birthday=" + birthday.getTime() + "]";
}
}
1.equals()
在Object类中定义的equals方法,其含义在于判断两个对象的地址是否相同,即判断两个对象是否为同一对象。这时,equals() 等价于”==”。
由于所有的类都继承自Object,所以如果自己定义的类未重写(override)equals方法,则equals判断两个对象是否为同一对象;
如果重写了equals方法,则equals的判断逻辑根据方法本身的实现。通常做法是根据业务逻辑,判断对象的属性值是否相等。
public static void main(String[] args) {
User user1 = new User(1,"John",24,new Date());
User user2 = new User(1,"John",24,new Date());
System.out.println(user1.equals(user2)); //out:false
System.out.println(user1.equals(user1)); //out:true
System.out.println(user1 == user2); //out:false
System.out.println(user1 == user1); //out:true
}
覆盖equals方法:
@Override
public boolean equals(Object obj) {
//非空对象不可与null值相等
if(obj == null) return false;
//同一对象一定相等
if(this == obj) return true;
//类型不同的两个对象一定不相等
if(this.getClass() != obj.getClass()) return false;
//当且仅当三个属性都相等时,两个对象相等
User person = (User)obj;
return id==person.id && name.equals(person.name) && age.intValue()==person.age.intValue();
}
覆盖equals方法后,user1与user2相等:
public static void main(String[] args) {
User user1 = new User(1,"John",24);
User user2 = new User(1,"John",24);
System.out.println(user1.equals(user2)); //out:true
System.out.println(user1.equals(user1)); //out:true
System.out.println(user1 == user2); //out:false
System.out.println(user1 == user1); //out:true
}
2.hashCode()
hashCode方法的返回值为int,用来确定一个对象在散列表中的索引位置。该方法在创建当前类的散列集(HashMap, HashSet, Hashtable)时才有意义,其它情况没有意义。
– equals为true的两个对象,hashCode一定相同;
– hashCode相同的两个对象,equals不一定为true(哈希冲突)
– 在使用散列集时,如果重写了equals方法,则一定要重写为hashCode方法,否则equals方法无效。
重写hashCode方法:
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((age == null) ? 0 : age.hashCode());
result = prime * result + ((birthday == null) ? 0 : birthday.hashCode());
result = prime * result + id;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
3.clone()
首先,考虑一下使用“=”赋值运算符的情况。
在Java中的数据类型分为两大类:基本类型和引用类型
基本类型:int, long, float, double, char等。
引用类型:Integer, Long, Float, Double, String等。
将一个基本类型的变量赋值给基本类型的时候,新变量会在堆内存中开辟自己的空间,再把旧变量的值写入。因此新旧变量的内存是各自独立的堆空间,对其中一个变量的值更改不会影响另一个变量的值;
将一个引用类型的变量赋值给引用类型的时候,新变量并不会在堆内存中开辟自己的空间,而是仅仅保存了旧变量引用的堆内存地址,两个变量指向同一块堆内存空间。因此,对一个变量值的更改会影响另一个变量的值。
下边这个简单的程序说明了这一点:
int a = 5;
int b = a;
a = 10;
System.out.println("a="+a+",b="+b);//out:a=10,b=5
Integer a1 = 5;
Integer b1 = a1;
System.out.println("a1="+a1+",b1="+b1);//out:a1=5,b1=5
由于我们自定义的类都是引用类型的,所以,如果需要使新对象和旧对象拥有相同的值,并且对其中一个对象状态的更改不会影响另一个对象,就需要使用clone()方法。
使用clone方法需要:
1.实现Cloneable接口;(只作为标识)
2.重写Object类的clone方法,并将访问修饰符定义为public。(因为在Object类中的clone方法是protected的,只能在本类中访问)
下面在使User类实现Cloneable接口,并重写clone方法,使用默认的实现:
public class User implements Cloneable
@Override
public User clone() throws CloneNotSupportedException {
return (User)super.clone();
}
在测试方法里验证clone方法:
User user1 = new User(1,"user1",24, new Date());
//浅拷贝
User user2 = user1.clone();
System.out.println("user1:"+user1);//out:"user1:User [id=1, name=user1, age=24, birthday=1430137523544]"
System.out.println("user2:"+user2);//out:"user2:User [id=1, name=user1, age=24, birthday=1430137523544]"
user2.setName("user2");
user2.setAge(30);
System.out.println("user1:"+user1);//out:"user1:User [id=1, name=user1, age=24, birthday=1430137523544]"
System.out.println("user2:"+user2);//out:"user2:User [id=1, name=user2, age=30, birthday=1430137523544]"
user2.getBirthday().setTime(1430137087450L);
System.out.println("user1:"+user1);//out:"user1:User [id=1, name=user1, age=24, birthday=1430137087450]"
System.out.println("user2:"+user2);//out:"user2:User [id=1, name=user2, age=30, birthday=1430137087450]"
可以看出在7、8行对新对象String类型和Integer类型进行更改时,并没有影响旧对象该域的值。因为这两种类型都是final类型的,为不可变类型,所以每次赋值都是堆内存中的一个新对象。
而第11行,对Date类型的值做改变的时候,旧对象的值也相应的改变。
由此可见,Object类中clone的默认实现是“浅拷贝”。
浅拷贝的意思是:当拷贝一个类的对象时,对于旧对象中基本类型的域和不可变引用类型(String, Integer等)的域 在堆内存中开辟独立的空间给新对象,而对于可变引用类型(Date, 自定义类型)的域 使新对象共享旧对象该域的堆内存空间。
我们通常需要的是深拷贝,即:所有域都开辟独立于旧对象的堆内存空间,各自的更改不影响对方。
重新定义clone方法,实现深拷贝:
@Override
public User clone() throws CloneNotSupportedException {
User cloned = (User)super.clone();
//将所有的可变引用类型的域都clone
cloned.birthday = (Date)birthday.clone();
return cloned;
}
运行结果:
User user1 = new User(1,"user1",24, new Date());
//深拷贝
User user2 = user1.clone();
System.out.println("user1:"+user1);//out:"user1:User [id=1, name=user1, age=24, birthday=1430139233085]"
System.out.println("user2:"+user2);//out:"user2:User [id=1, name=user1, age=24, birthday=1430139233085]"
user2.setName("user2");
user2.setAge(30);
System.out.println("user1:"+user1);//out:"user1:User [id=1, name=user1, age=24, birthday=1430139233085]"
System.out.println("user2:"+user2);//out:"user2:User [id=1, name=user2, age=30, birthday=1430139233085]"
user2.getBirthday().setTime(1430137087450L);
System.out.println("user1:"+user1);//out:"user1:User [id=1, name=user1, age=24, birthday=1430139233085]"
System.out.println("user2:"+user2);//out:"user2:User [id=1, name=user2, age=30, birthday=1430137087450]"
可见新对象的birthday的改变已不在影响旧对象该域的值