Java中对象复制是一个十分基本的操作,也是一个容易被忽略并且易出现隐患的地方。在实际开发中,对象复制的需求是非常高的。然而,Java语言本身并没有提供复制对象的语法或接口,这就需要我们自己去实现。
一、浅拷贝和深拷贝
在Java中,对象复制分为浅拷贝和深拷贝两种方式。 浅拷贝是指只拷贝对象本身和对象中的基本类型变量,而不拷贝对象中引用类型变量所引用的对象。例如:
public class Person implements Cloneable {
private String name;
private int age;
private List hobbies;
// setters and getters
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public static void main(String[] args) throws CloneNotSupportedException {
Person person1 = new Person();
person1.setName("Alice");
person1.setAge(25);
List
hobbies = new ArrayList<>();
hobbies.add("reading");
hobbies.add("traveling");
person1.setHobbies(hobbies);
Person person2 = (Person) person1.clone();
System.out.println(person1 == person2); // false
System.out.println(person1.getHobbies() == person2.getHobbies()); // true
}
可以看出,person2和person1的引用地址不同,但是person2的hobbies引用的对象和person1的hobbies引用的对象一样。这是因为这两个对象指向了同一块堆内存中的地址,当person1的hobbies变量中的地址拷贝到person2中时,它们指向了同一块堆内存中的地址。 而深拷贝则是指复制一个对象,同时也把该对象所引用的对象都复制了一遍。例如:
public class Person implements Cloneable {
private String name;
private int age;
private List hobbies;
// setters and getters
@Override
public Object clone() throws CloneNotSupportedException {
Person person = (Person) super.clone();
person.setHobbies(new ArrayList<>(this.hobbies));
return person;
}
}
public static void main(String[] args) throws CloneNotSupportedException {
Person person1 = new Person();
person1.setName("Alice");
person1.setAge(25);
List
hobbies = new ArrayList<>();
hobbies.add("reading");
hobbies.add("traveling");
person1.setHobbies(hobbies);
Person person2 = (Person) person1.clone();
System.out.println(person1 == person2); // false
System.out.println(person1.getHobbies() == person2.getHobbies()); // false
}
在这个例子中,我们通过在clone方法中手动创建一个新的List,并复制了person1原本所引用的List中的所有元素到新的List中来实现深拷贝。当我们对person2.getHobbies()和person1.getHobbies()打印结果后,我们可以看到它们已经不再引用同一块内存地址了。
二、对象复制的方法
在Java中,我们通常使用如下几种方式来实现对象的复制: 1. 直接使用Object类的clone()方法。例如:
Object clonedObject = originalObject.clone();
需要注意的是,要在待复制类中实现Cloneable接口并重写clone()方法。 2. 使用Java序列化机制。例如:
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(originalObject);
byte[] bytes = baos.toByteArray();
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
Object clonedObject = ois.readObject();
这种方法要求待复制的类必须实现Serializable接口。 3. 使用第三方库,如Apache Commons BeanUtils。例如:
BeanUtils.copyProperties(originalObject, clonedObject);
三、对象复制的注意事项
1. 由于Java中的方法参数传递都是按值传递的,当传递一个对象作为参数时,实际上传递的是对象的引用,也就是该对象在堆中的存放地址。因此,在使用浅拷贝时需要注意,如果拷贝出来的对象中的引用类型变量所引用的对象被修改,那么原对象中的引用类型变量所引用的对象也会被修改。 2. 在使用Cloneable和序列化机制时,我们需要注意对象的构造方法的执行顺序。如果不小心实现了一个有参构造函数,在使用clone和序列化机制的时候就会出问题。因为clone或反序列化过程中并不会再次调用构造方法,因此对象中的一些属性可能没有被初始化。
四、总结
Java对象复制是一个很基本的操作,但是实现起来却需要我们注意各种细节和风险。在选择对象复制的方式时,需要根据实际需要和场景进行选择。如果要拷贝的对象中包含了引用类型的变量,那么浅拷贝很可能会存在问题,需要选择使用深拷贝。而在使用对象复制的过程中也需要注意每种方法的局限性和注意事项,以避免不必要的错误和风险。