Java实现对象的克隆方式

Java实现对象克隆的方法

1、Java实现克隆有两种形式

  • 浅拷贝
  • 深拷贝

在Java中吗,我们说两个对象是否相等通常有两层含义:

  • 对象的内容是否相等,通常使用到对象的 equals(Object o) 函数;
  • 引用的地址是否相同,使用运算符 == 比较即可。

当两个对象通过赋值符号 = 赋值时,表明这两个对象指向了内存中同一个地址,所以改变其中一个对象的内容,也就间接地改变了另一个对象的内容。有时候,我们需要从一个已经存在的对象重新拷贝一份出来,并且不仅这两个对象内容相等,在内存中存在两个独立的存储地址,互不影响,这时,就需要用到 Java 中的克隆机制。

2、Cloneable

通过 Cloneable 接口可以很轻松地实现 Java 对象的克隆,只需要 implements Cloneable 并实现 Object 的 clone() 方法即可。


public class User implements Cloneable{

    private String username;

    private String password;

    public User(String username, String password) {
        super();
        this.username = username;
        this.password = password;
    }

    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;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Override
    public boolean equals(Object obj) {
        User user = (User) obj;
        if (username.equals(user.username) && password.equals(user.password)) {
            return true;
        }
        return false;
    }

}

注意这里对象实现的是 Object 类的 clone() 方法,因为 Cloneable 是一个空接口:

package java.lang;

public interface Cloneable {
}

从源码注释中可以看出,需要实现 Object 类中的 clone() 方法(注意:clone() 函数是一个 native 方法,同时抛出了一个异常)

protected native Object clone() throws CloneNotSupportedException;

从 clone() 函数的注释中能够看出对象与克隆对象之间的关系,测试代码如下(注意:我们在 User 对象中重写了 equals() 函数)

    public static void main(String[] args) throws CloneNotSupportedException{
        User userOne, userTwo, userThree;
        userOne = new User("username", "password");
        userTwo = userOne;
        userThree = (User) userOne.clone();

        System.out.println(userTwo==userOne);            //true
        System.out.println(userTwo.equals(userOne));    //true

        System.out.println(userThree==userOne);            //false
        System.out.println(userThree.equals(userOne));    //true

    }

测试结果显示,通过 clone() 函数,我们成功地从 userOne 对象中克隆出了一份独立的 userThree 对象。

3、浅拷贝和深拷贝的区别

2.1 浅拷贝

1、实现Cloneable接口,重写Object中的clone()方法
谈此之前,我们先看一个例子,定义一个名为 Company 的类,并添加一个类型为 User 的成员变量:


public class Company implements Cloneable{

    private User user;

    private String address;

    public Company(User user, String address) {
        super();
        this.user = user;
        this.address = address;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Override
    public boolean equals(Object obj) {
        Company company = (Company) obj;
        if (user.equals(company.getUser()) && address.equals(company.address)) {
            return true;
        }
        return false;
    }

}

测试代码及测试结果如下:

    public static void main(String[] args) throws CloneNotSupportedException{
        Company companyOne, companyTwo, companyThree;
        companyOne = new Company(new User("username", "password"), "上海市");
        companyTwo = companyOne;
        companyThree = (Company) companyOne.clone();

        System.out.println(companyTwo==companyOne);                //true
        System.out.println(companyTwo.equals(companyOne));        //true

        System.out.println(companyThree==companyOne);            //false
        System.out.println(companyThree.equals(companyOne));    //true

        System.out.println(companyThree.getUser()==companyOne.getUser());            //true ? 这里为什么不是false呢
        System.out.println(companyThree.getUser().equals(companyOne.getUser()));    //true

    }

问题来了,companyThree 与 companyOne 中的 User 是同一个对象!也就是说 companyThree 只是克隆了 companyOne 的基本数据类型的数据,而对于引用类型的数据没有进行深度的克隆。也就是俗称的浅克隆。

浅克隆:顾名思义,就是很表层的克隆,只克隆对象自身的引用地址;

深克隆:也称“N层克隆”,克隆对象自身以及对象所包含的引用类型对象的引用地址。

这里需要注意的是,对于基本数据类型(primitive)和使用常量池方式创建的String 类型,都会针对原值克隆,所以不存在引用地址一说。当然不包括他们对应的包装类。

2.2 深拷贝

1、递归调用clone()方法

所以使用深克隆就可以解决上述 Company 对象克隆过后两个 user 对象的引用地址相同的问题。我们修改一下 Company 类的 clone() 函数。

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Company company = (Company) super.clone();
        company.user = (User) company.getUser().clone();
        return company;
    }

再运行测试代码,就能得到 companyThree.getUser()==companyOne.getUser() 为 false 的结果了。从实现过程来说,递归克隆存在克隆过程多且复杂的缺点

2、通过序列化方式

public class Text implements Serializable{

    private static final long serialVersionUID = 8723901148964L;

    private int age;

    private Name name;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Name getName() {
        return name;
    }

    public void setName(Name name) {
        this.name = name;
    }

    public Object myClone(){
        Text text=null;
        ByteArrayOutputStream bos=new ByteArrayOutputStream();
        try {
            ObjectOutputStream oos=new ObjectOutputStream(bos);
            oos.writeObject(this);
            ByteArrayInputStream bis=new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream ois=new ObjectInputStream(bis);
            text=(Text)ois.readObject();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return text;
    }
}

class Name implements Serializable {

    private static final long serialVersionUID = 872390113109L;

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return name;
    }
}

结果分析:

采用深克隆能有效隔离源对象与克隆对象的联系。

从实现过程来说,递归克隆存在克隆过程多且复杂的缺点,所以建议采用序列化的方式进行深克隆。

总结

Java对象克隆共有两种形式,三种方法

  • 浅拷贝

    • 调用clone方法
  • 深拷贝

    • 递归调用clone方法

    • 序列化对象

三种方法之间互有优缺点,具体采用要根据实际情况。

  • 6
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java 中,可以通过实现 `Cloneable` 接口并重写 `clone()` 方法实现对象克隆。 首先,在需要被克隆的类中实现 `Cloneable` 接口,这是一个标记接口,不含任何方法,只是用来指示该类可以被克隆。 然后,重写 `clone()` 方法,该方法返回一个克隆后的对象。在重写过程中,需要调用父类的 `clone()` 方法,并进行类型转换。 下面是一个示例: ```java public class MyClass implements Cloneable { private int value; public MyClass(int value) { this.value = value; } public void setValue(int value) { this.value = value; } public int getValue() { return value; } @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } } ``` 在使用克隆时,可以使用 `clone()` 方法来创建一个原始对象的副本。注意,`clone()` 方法返回的是一个 `Object` 类型的对象,因此需要进行类型转换。 ```java public class Main { public static void main(String[] args) { MyClass obj1 = new MyClass(10); try { MyClass obj2 = (MyClass) obj1.clone(); System.out.println(obj1.getValue()); // 输出 10 System.out.println(obj2.getValue()); // 输出 10 obj2.setValue(20); System.out.println(obj1.getValue()); // 输出 10 System.out.println(obj2.getValue()); // 输出 20 } catch (CloneNotSupportedException e) { e.printStackTrace(); } } } ``` 注意:在使用克隆时,需要注意对象的可变性。如果被克隆对象包含引用类型的成员变量,需要考虑是否需要进行深拷贝,以避免克隆对象与原始对象共享同一引用,导致意外的修改。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值