对象拷贝是一种创建对象精确副本的方法。对象类的clone()方法就是用于拷贝对象的。
答:拷贝的对象可能包含一些已经修改过的属性,而new出来的对象的属性都还是初始化时候的值,所以当需要一个新的对象来保存当前对象的“状态”就靠clone方法了。如果把这个对象的临时属性一个一个的赋值给我新new的对象的话,可以是可以,但是一是麻烦,二是clone是一个native方法,在底层实现的,速度快。
tips:
们常见的Object a=new Object();Object b;b=a;这种形式的代码复制的是引用,即对象在内存中的地址,a和b对象仍然指向了同一个对象。而通过clone方法赋值的对象跟原来的对象时同时独立存在的。
拷贝有两种不同的方法
(1) 浅拷贝(ShallowClone)
即分配一个新的、未初始化的对象,并复制其中原始对象的所有字段(属性)。当字段值是对对象的引用时,这并不总是导致所需的行为,因为它复制了引用,因此引用了与原始对象相同的对象。因此,引用的对象是共享的,因此,如果其中一个对象被修改,那么更改在另一个对象中是可见的。
(2)深拷贝(DeepClone)
在深拷贝中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给拷贝对象,深拷贝将原型对象的所有引用对象也复制一份给拷贝对象。
简单来说,在深拷贝中,除了对象本身被复制外,对象所包含的所有成员变量也将复制
在Java语言中,通过覆盖Object类的clone()方法可以实现浅拷贝。
1)不包含引用属性(String类型比较特别,不计入内)的对象浅拷贝
public class copyTest {
public static void main(String[] args) throws CloneNotSupportedException {
Student s1 = new Student(1001,"lucy");
Student s2 = (Student) s1.clone();
System.out.println(s1);
System.out.println(s2);
}
}
class Student implements Cloneable{
private int id;
private String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public Object clone(){
Student student = null;
try {
student = (Student)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return student;
}
}
output:
com.dgut.Student@16b98e56
com.dgut.Student@7ef20235
2)包含引用属性的对象浅拷贝
class Student implements Cloneable{
private int id;
private String name;
private Book book;
public Student(int id, String name, Book book) {
this.id = id;
this.name = name;
this.book = book;
}
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 Book getBook() {
return book;
}
public void setBook(Book book) {
this.book = book;
}
@Override
public Object clone(){
Student student = null;
try {
student = (Student)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return student;
}
}
class Book implements Cloneable {
private String bookName;
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public Book(String bookName) {
this.bookName = bookName;
}
@Override
public String toString() {
return "Book{" +
"bookName='" + bookName + '\'' +
'}';
}
}
public class copyTest {
public static void main(String[] args) throws CloneNotSupportedException {
Book book = new Book("Thinking in Java");
Student s1 = new Student(1001,"lucy",book);
Student s2 = (Student) s1.clone();
//只改变s1的引用变量的内容
s1.getBook().setBookName("I live in Haven");
System.out.println(s1);
System.out.println(s2);
System.out.println("s1:" + s1.getBook());
System.out.println("s2:" + s2.getBook());
}
}
output:
com.dgut.Student@16b98e56
com.dgut.Student@7ef20235
s1:Book{bookName='I live in Haven'}
s2:Book{bookName='I live in Haven'}
根据结果可知,两个Student对象的Book属性内容都变了,因为浅拷贝只是复制了Book变量的引用,并没有真正的开辟另一块空间,将值复制后再将引用返回给新对象,要避免此结果可以考虑深拷贝。
在Java语言中,如果需要实现深拷贝,可以通过覆盖Object类的clone()方法实现,也可以通过序列化(Serialization)等方式来实现。
1)使用clone()方法,我们可以对其内的引用类型的变量,再进行一次 clone(),以此达到深拷贝效果。
public class DeepCopyTest {
public static void main(String[] args) {
Addr addr = new Addr("庞各庄");
Person per1 = new Person(1002,"jack",addr);
Person per2 = (Person) per1.clone();
addr.setDesc("大兴岭");
System.out.println(per1.getAddr());
System.out.println(per2.getAddr());
}
}
class Person implements Cloneable {
private int id;
private String name;
private Addr addr;
public Person(int id, String name, Addr addr) {
this.id = id;
this.name = name;
this.addr = addr;
}
public Addr getAddr() {
return addr;
}
@Override
protected Object clone() {
Person person = null;
try {
person = (Person)super.clone(); //浅拷贝
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
person.addr = (Addr)addr.clone(); //深拷贝
return person;
}
}
class Addr implements Cloneable {
private String desc;
public Addr(String desc) {
this.desc = desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return "Addr{" +
"desc='" + desc + '\'' +
'}';
}
@Override
protected Object clone() {
Addr addr = null;
try {
addr = (Addr)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return addr;
}
}
2)但如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深拷贝。
class Computer implements Serializable {
private String brand;
private Keyboard keyboard;
public Keyboard getKeyboard() {
return keyboard;
}
public Computer(String brand, Keyboard keyboard) {
this.brand = brand;
this.keyboard = keyboard;
}
public Computer myclone() {
Computer computer = null;
try{
//将该对象序列化成流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
//将流序列化成对象
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
computer = (Computer) objectInputStream.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return computer;
}
}
class Keyboard implements Serializable {
private String color;
public Keyboard(String color) {
this.color = color;
}
}
public class DeepCopyTest2 {
public static void main(String[] args) {
Keyboard keyboard = new Keyboard("black");
Computer c1 = new Computer("dell",keyboard);
Computer c2 = c1.myclone();
System.out.println(c1.getKeyboard() == c2.getKeyboard());
}
}
output:
false