克隆:clone

  1. 复制(实际的数据)
  2. 可以通过实现Cloneable接口和重写Object类中的clone()方法来实现对象的克隆。具体步骤如下:
  3. 除了上述两种方法,Java还提供了另一种克隆方式,即使用Object类中的clone()方法进行克隆。
  4. 通过第三方工具类Gson实现深克隆

1. 复制(实际的数据)

1
Person p1 = new Person();
Person p2 = p1;

2
Person p1 = new Person(“wang”, 20);
Person p2 = new Person();
p2.setName(p1.getName());
p2.setAge(p2.getAge());
 

介绍

  1. clone()定义在Object
  2. 克隆指的是指定义一个方法,“创建”与本对象“属性”一致的新对象!
  3. 目的:快速地“复制”对象!
  4. 复制的方式是,重写clone方法
  5. 被复制的对象类型要实现Cloneable接口
  6. Object类中定义的clone方法,默认地对实例变量操作的方式:“基本数据类型”复制,引用类型复制。
  7. 带来的结果是,引用类型的实例变量指向的对象,没有复制!
  8. Object clone()这种没有复制实例变量指向对象的方式,我们称之为"浅克隆"
  9. 如果需要“深克隆”,赋值对象,以及对象实例变量指向的所有对象!需要额外地操作。
  10. 额外操作方式1: 重写clone(),把需要的逻辑,在clone()方法中自己实现!
  11. 额外操作方式2:可以把需要克隆的对象,转化成json格式的数据。再通过json格式创建对象!

2. 可以通过实现Cloneable接口和重写Object类中的clone()方法来实现对象的克隆。具体步骤如下:

  1. 实现Cloneable接口,并重写Object类中的clone()方法,以便实现克隆操作。Cloneable接口是一个标记接口,用于告诉Java虚拟机该类支持克隆操作。
  2. 在重写的clone()方法中,调用super.clone()方法获取到一个浅克隆的副本,然后根据需要对副本进行深克隆,最后返回克隆后的对象。

示例:

public class Person implements Cloneable {
    private String name;
    private int age;
    private List<String> hobbies;

    public Person(String name, int age, List<String> hobbies) {
        this.name = name;
        this.age = age;
        this.hobbies = hobbies;
    }

    // 重写clone()方法
    @Override
    public Object clone() throws CloneNotSupportedException {
        Person clone = (Person) super.clone();
        // 对hobbies进行深克隆
        clone.hobbies = new ArrayList<>(this.hobbies);
        return clone;
    }

    // 省略getter和setter方法
}

在上面的示例中,Person类实现了Cloneable接口,并重写了Object类中的clone()方法。在重写的clone()方法中,首先调用了super.clone()方法获取到一个浅克隆的副本,然后对hobbies字段进行了深克隆,最后返回克隆后的对象。

使用该类进行克隆操作的示例如下:

List<String> hobbies = new ArrayList<>();
hobbies.add("reading");
Person p1 = new Person("Tom", 20, hobbies);
Person p2 = null;
try {
    p2 = (Person) p1.clone(); // 克隆p1对象
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
}
System.out.println("p1 == p2 ? " + (p1 == p2)); // false
System.out.println("p1.hobbies == p2.hobbies ? " + (p1.getHobbies() == p2.getHobbies())); // false

在上面的示例中,首先创建了一个Person对象p1,并将其hobbies字段设置为一个包含一个元素的List。然后使用p1.clone()方法创建了一个克隆对象p2,并判断p1和p2是否相等,以及p1和p2的hobbies字段是否相等。可以看到,p1和p2不相等,而且p1和p2的hobbies字段也不相等,这说明p1和p2是两个不同的对象,且p2的hobbies字段进行了深克隆。

除了上面的示例外,下面再给出一个深克隆的示例,使用的是序列化和反序列化的方法:

import java.io.*;

public class Person implements Serializable {
    private String name;
    private int age;
    private Address address;

    public Person(String name, int age, Address address) {
        this.name = name;
        this.age = age;
        this.address = address;
    }

    public Person clone() {
        try {
            // 序列化
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

            // 反序列化
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            return (Person) ois.readObject();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
            return null;
        }
    }

    // 省略getter和setter方法
}

class Address implements Serializable {
    private String city;
    private String street;

    public Address(String city, String street) {
        this.city = city;
        this.street = street;
    }

    // 省略getter和setter方法
}

在上面的示例中,Person类实现了Serializable接口,并提供了一个clone()方法,该方法使用了序列化和反序列化的方法进行深克隆。Address类同样实现了Serializable接口,因为Person类中包含了Address类的实例。

使用该类进行克隆操作的示例如下:

Address address = new Address("Shanghai", "Nanjing Road");
Person p1 = new Person("Tom", 20, address);
Person p2 = p1.clone(); // 克隆p1对象
System.out.println("p1 == p2 ? " + (p1 == p2)); // false
System.out.println("p1.address == p2.address ? " + (p1.getAddress() == p2.getAddress())); // false

在上面的示例中,首先创建了一个Person对象p1,并将其address字段设置为一个Address对象。然后使用p1.clone()方法创建了一个克隆对象p2,并判断p1和p2是否相等,以及p1和p2的address字段是否相等。可以看到,p1和p2不相等,而且p1和p2的address字段也不相等,这说明p1和p2是两个不同的对象,且p2的address字段进行了深克隆。

3. 除了上述两种方法,Java还提供了另一种克隆方式,即使用Object类中的clone()方法进行克隆。

该方法会创建一个新的对象,并将原对象的非静态字段复制到新对象中。具体来说,需要满足以下条件:

被克隆的类必须实现Cloneable接口,否则会抛出CloneNotSupportedException异常;
克隆方法需要重写Object类中的clone()方法,并将其访问修饰符改为public;
克隆方法需要调用super.clone()方法获得一个新的对象,并将原对象的非静态字段复制到新对象中。
以下是一个使用clone()方法进行浅克隆的示例:

public class Person implements Cloneable {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Person clone() {
        try {
            return (Person) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }

    // 省略getter和setter方法
}

在上述示例中,Person类实现了Cloneable接口,并重写了Object类中的clone()方法,以便进行浅克隆操作。使用该类进行克隆操作的示例如下:

Person p1 = new Person("Tom", 20);
Person p2 = p1.clone(); // 克隆p1对象
System.out.println("p1 == p2 ? " + (p1 == p2)); // false

在上述示例中,首先创建了一个Person对象p1,并使用p1.clone()方法创建了一个克隆对象p2。然后判断p1和p2是否相等,可以看到,它们不相等,说明克隆操作成功。

需要注意的是,使用Object类中的clone()方法进行克隆操作时,如果被克隆的类包含了其他类的引用类型字段,那么克隆出来的对象和原对象将共享这些引用类型字段的实例,即进行的是浅克隆操作。如果需要进行深克隆操作,则需要在克隆方法中对这些引用类型字段进行深克隆操作。

4. 通过第三方工具类Gson实现深克隆

User user1 = new User()
Gson gson = new Gson();
//将对象序列化为json字符串
String userStr = gson.toJson(user1);
//然后将字符串反序列化为对象
User user2= gson.fromJson(userStr, User.class);

这种方式不用实现Cloneable接口和Serializable接口,也不用重写clone(),对结果没影响。