在前面,我们在java创建对象的四种方式中提到过通过clone来创建对象,点我前往。
这一篇,我们将详细说说拷贝。
拷贝分为两种,浅拷贝和深拷贝。
浅拷贝:赋值原来的对象,但是不复制对对象的引用。对于基本数据类型,克隆前后互不影响,即创建了一个新的对象。对于引用类型,克隆前后相互影响
深克隆:不仅复制了对象,还赋值了对象的引用,即克隆前后完全是两个不同的对象,两者互不影响。
下面,我们就这两种情况进行代码的实现,相信看完以后,你对深浅克隆会有更加直观的认识。
首先创建两个类,Message和User (其中Message将作为User的引用类):
package day01;
public class Message {
private String id;
private String email;
public Message(String id,String email) {
this.id=id;
this.email=email;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "Message [id=" + id + ", email=" + email + "]";
}
}
package day01;
public class User implements Cloneable{
private String username;
private String password;
private Message message;
public User() {}
public User(String username,String password,Message message) {
this.username=username;
this.password=password;
this.message=message;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Message getMessage() {
return message;
}
public void setMessage(Message message) {
this.message = message;
}
@Override
public String toString() {
return "User [username=" + username + ", password=" + password + ", message=" + message + "]";
}
@Override
protected Object clone() throws CloneNotSupportedException {
Object object=null;
object= super.clone();
return object;
}
public void Login(String username) {
System.out.println(username+"的密码是"+password+",他的邮箱是"+message.getEmail());
}
}
测试类:
package day01;
/*
* 浅克隆
*/
public class Test{
public static void main(String[] args) throws CloneNotSupportedException{
Message message = new Message("20190910","2130054941@qq.com");
User user1=new User("gangan","123",message);
User user2=(User) user1.clone();
//改变基本类型变量值
user2.setPassword("456");
//改变引用类型变量值
user2.getMessage().setEmail("1982905232@qq.com");
System.out.println(user1);
System.out.println(user2);
}
}
查看测试结果:
User [username=gangan, password=123, message=Message [id=20190910, email=1982905232@qq.com]]
User [username=gangan, password=456, message=Message [id=20190910, email=1982905232@qq.com]]
可以看出:对于成员变量是基本数据类型(包括String),克隆后会新创建一个对象,它的值的改变不会影响克隆前的那个对象
对于成员变量是引用类型,克隆后也会创建新的对象,但是和克隆前的对象共享同一个引用,它的值的改变会影响前者。
深克隆(基于前面):
修改Message(同时也需要实现cloneable接口):
package day01;
public class Message implements Cloneable{
private String id;
private String email;
public Message(String id,String email) {
this.id=id;
this.email=email;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "Message [id=" + id + ", email=" + email + "]";
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
修改User(重写clone):
@Override
protected Object clone() throws CloneNotSupportedException {
User user=(User)super.clone();;
user.message=(Message)message.clone();
return user;
}
测试类同上,直接上测试结果:
User [username=gangan, password=123, message=Message [id=20190910, email=2130054941@qq.com]]
User [username=gangan, password=456, message=Message [id=20190910, email=1982905232@qq.com]]
可以看出:克隆前后都完全互不影响,新建的对象不再引用原有的对象。同时我们也看出,深克隆需要对每个引用对象都进行克隆。如果对象是数组,还需要遍历数组,创建实例,再重新赋值。
所以,深克隆还提供了另外一种方式:序列化与反序列化
关于序列化和反序列化,前面的文章已经讲解并实现了。这里就不再赘述了,但是这里强调一下ArrayList的序列化:
ArrayList底层是个element数组,由关键字transient修饰,我们都知道transient和private修饰的变量都不会被序列化,原因在于序列化的对对象的操作,而这两个变量是类的变量。同时还存在一个效率问题。
如果ArrayList扩容后仅仅使用了一个位置,其余位置都将会是null,这个时候对数组序列化意味着将会对大量的null进行序列化,这是一种很大的浪费。所以,尽管ArrayList底层实现了Serializable接口,但是仍然不能直接序列化,而是采用自定义序列化,具体方式可以参考前面的序列化和反序列化。
这里再多啰嗦几句,序列化是如何实现的?
为什么有了Serializeable就能序列化?其实每次需要被序列化时都会检查是否实现了这个关键字,如果没有就会抛出相应的异常!