目录

简介

clone方法案例

浅拷贝测试

实现深拷贝


简介

protected Object clone()

  • 创建并返回此对象,对象克隆。默认是浅拷贝

  • 想重写clone方法,必须实现Cloneable接口,并且需要处理异常

  • 克隆分为深浅克隆(克隆又叫拷贝)

  • 深拷贝:拷贝原对象的值,并且在堆内存中开辟了一块新的内存空间。不管什么操作,都不会影响原对象

  • 浅拷贝:即返回一个新的对象,但是新对象里的引用类型变量地址指向的还是原对象内引用类型地址,会互相影响

权限修饰符为protected,所以只能在本类,同一个包下或者不同包但是继承了它的子类中使用

由于Object是java.lang包下的,所以前两种情况肯定不行,就只能继承它的子类中使用,Object类是所有类的祖宗类,所以每个类都能使用clone方法(但是clone方法是protected修饰的,所以子类只有重写了clone方法,子类创建出来的对象才能使用clone方法)

Father.java

package Demo;

public class Father {
    public void a() {
        System.out.println("public");
    }
    protected void a1() {
        System.out.println("protected");
    }
    void a2() {
        System.out.println("default");
    }
}

Son.java

package Demo;

public class Son extends Father {
    @Override
    public void a1() {
        super.a1();
    }

    @Override
    public void a2() {
        super.a2();
    }
}

Main.java

import Demo.Son;

public class Main {
    public static void main(String[] args) {
        Son s = new Son();
        // 可以直接调用public
        s.a(); // public
        // 父类protected修饰的方法,子类对象是不能直接调用的,需要子类进行重写后(并且修饰符要改为public),子类才能调用
        s.a1(); // protected
        // 父类默认权限符修饰的方法,子类对象也是不能直接调用的,需要子类进行重写后(并且修饰符要改为public),子类才能调用
        s.a2(); // default
    }
}

clone方法案例

Animal.java

package Demo;
// 默认会继承Object类,Cloneable接口中什么都没有,把这种什么内容都没有的,叫做标记接口
// 如果想重写clone方法,就必须实现Cloneable接口
public class Animal implements Cloneable {
    public String name;
​
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
​
    public Animal() {
    }
​
    public Animal(String name) {
        this.name = name;
    }
​
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
​
    public void print() {
        System.out.println("name:" + name);
    }
}

Main.java

import Demo.Animal;
​
public class Main {
    public static void main(String[] args) {
        Animal a = new Animal("张三");
        try {
            Animal cloneA = (Animal) a.clone();
            System.out.println(a == a.clone()); // false,说明地址不一样
            // 但是clone方法,默认是浅拷贝
            // 为什么这里是深拷贝的呢?其实并不是,现在看来是深拷贝,我下面再做一个测试,就看出来了
            cloneA.setName("李四");
            System.out.println(a.getName()); // 张三
            System.out.println(cloneA.getName()); // 李四
            a.print(); // name:张三
            cloneA.print(); // name:李四
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }
}

浅拷贝测试

Run.java

package Demo;
​
public class Run {
    private String speed;
​
    public String getSpeed() {
        return speed;
    }
​
    public void setSpeed(String speed) {
        this.speed = speed;
    }
​
    public Run() {
    }
​
    public Run(String speed) {
        this.speed = speed;
    }
}

Animal.java

package Demo;
​
public class Animal implements Cloneable {
    private String name;
    // 这里定义了一个Run类型的变量
    private Run run;
​
    public Animal() {
    }
​
    public Animal(String name, Run run) {
        this.name = name;
        this.run = run;
    }
​
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
​
    public Run getRun() {
        return run;
    }
​
    public void setRun(Run run) {
        this.run = run;
    }
​
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

Main.java

import Demo.Animal;
import Demo.Run;
​
public class Main {
    public static void main(String[] args) {
        Animal a = new Animal("张三",new Run());
        try {
            Animal cloneA = (Animal) a.clone();
            // 这里用克隆出来的新对象,来修改run变量(堆内存地址)里面的speed变量
            Run r = cloneA.getRun();
            r.setSpeed("100");
            System.out.println(a.getRun().getSpeed()); // 100 发现原对象的值也变了,受到克隆对象的影响了,所以是浅拷贝
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }
}

看到这里,其实也看出来浅拷贝原因了

如果原对象定义了一个引用数据类型的变量,克隆的新对象会把原对象引用数据类型的堆内存地址复制过来。所以就是浅拷贝

那我们如何实现深拷贝呢?(网上找的实现方法)

实现深拷贝

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Cloneable{
    
    private String name;
    private Address address;
    
    /**
     * 因为Address不是基础数据类型或者String类型
     * 所以需要先重写Address类的clone方法才能完成深拷贝
     * 并且重写clone方法时可以将protected改为public 将返回类型改为重写的类
     */
    @Override
    public User clone() throws CloneNotSupportedException {
        User obj = (User) super.clone();
        obj.setAddress(this.address.clone());
        return obj;
    }
}
​
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Address implements Cloneable{
    
    private String addr;
    
    @Override
    public Address clone() throws CloneNotSupportedException {
        return (Address) super.clone();
    }
}
​
public class Main {
​
    public static void main(String[] args) throws CloneNotSupportedException {
        Address address = new Address("China");
        User user01 = new User("name", address);
        User user02 = user01.clone();
        System.out.println("u1 -> " + user01);  // User(name=name, address=Address(addr=China))
        System.out.println("u2 -> " + user02);  // User(name=name, address=Address(addr=China))
        address.setAddr("US");
        System.out.println("u1 -> " + user01);  // User(name=name, address=Address(addr=US))
        System.out.println("u2 -> " + user02);  // User(name=name, address=Address(addr=China))
    }
​
}

我自己做了下测试,确实可以

Run.java

package Demo;
​
public class Run implements Cloneable {
    private String speed;
​
    public String getSpeed() {
        return speed;
    }
​
    public void setSpeed(String speed) {
        this.speed = speed;
    }
​
    public Run() {
    }
​
    public Run(String speed) {
        this.speed = speed;
    }
​
    @Override
    public Run clone() throws CloneNotSupportedException {
        return (Run) super.clone();
    }
}

Animal.java

package Demo;
​
public class Animal implements Cloneable {
    private String name;
    // 这里定义了一个Run类型的变量
    private Run run;
​
    public Animal() {
    }
​
    public Animal(String name, Run run) {
        this.name = name;
        this.run = run;
    }
​
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
​
    public Run getRun() {
        return run;
    }
​
    public void setRun(Run run) {
        this.run = run;
    }
​
    @Override
    public Object clone() throws CloneNotSupportedException {
        Animal a = (Animal) super.clone();
        a.setRun(this.run.clone());
        return a;
    }
}

Main.java

import Demo.Animal;
import Demo.Run;
​
public class Main {
    public static void main(String[] args) {
        Animal a = new Animal("张三",new Run());
        try {
            Animal cloneA = (Animal) a.clone();
            // 这里用克隆出来的新对象,来修改run变量(堆内存地址)里面的speed变量
            Run r = cloneA.getRun();
            r.setSpeed("100");
            System.out.println(a.getRun().getSpeed()); // null 并没有受到克隆对象的影响,所以是深拷贝
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException(e);
        }
    }
}

但是也引发了我一个想法

如果Run.java中也声明了一个引用数据类型呢?恐怕又带再处理下吧。前端中可以用递归实现完全的深拷贝,但是Java中我就不太清楚了

原文链接:https://blog.csdn.net/qq_52845451/article/details/132255155

最后修改:2023 年 10 月 30 日
如果觉得我的文章对你有用,请随意赞赏