浅谈Java对象的clone

本文探讨了Java中对象拷贝的原因,讲解了对象引用传递的特点,并通过实例展示了如何利用`clone()`方法、拷贝构造函数以及序列化进行对象复制。还讨论了在对象包含引用时浅复制与深复制的区别,以及如何通过重写`clone()`方法和序列化实现深复制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

谈谈Object中为什么要出现clone()

其实这部分是最难讲清楚的,很多文章只讲了,为什么要用super.clone(),为什么要implements Cloneable,如何clone等等

  • 都知道Java没有指针的概念,实际上是有“指针”引用的概念;八大基本数据类型的参数传递是按照值传递(即传递的是参数的一个拷贝,参数的值改变不会影响原值);除此之外的其他类型都是按照引用传递(传递对象的引用,指向原对象);"="传递也是引用传递;
  • 实际中当我们将对象作为参数传递时,我们不允许某一个方法对该对象有set的权限,只能get,但是其他方法又可以set对象里面的属性(这只是一种假设),或许又干脆不想别人碰你的对象,所以不得不在对象里提供set方法,即这个对象是可更改的对象,或许这可以用封装(在下面演示自己想到的一种方法)或者aop的思想来实现(其实怎么实现自己也不太熟悉),但仅仅为了这个就用封装或者aop可能显得有些麻烦,这时候你只需要在某些地方传递一份对象的拷贝就可以了,随他怎么玩都跟你没有关系。

演示:

一种利用接口来实现第二点需求(红色字体)的代码演示(这似乎违背了接口出现的初衷,即接口不是为了用来干这个事情的)

package modifyTest;

//将所有的get()抽象出来
public interface Info {
    String getName();
    int getAge();
}
//有一个Person类
package modifyTest;

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

    public Person(String name, int typeName) {
        this.name = name;
        this.age = typeName;
    }
    
    @Override
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    
    @Override
    public int getAge() {
        return this.age;
    }
    
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age='" + age + '\'' +
                '}';
    }
}

测试:

package modifyTest;

import org.junit.Test;

public class main {
    @Test
    public void run(){
        Person p = new Person("zhangsan",24);
        justGet(p);
        canSet(p);
    }
    
    //在不允许修改属性的方法里传递接口,那么就只能使用get方法了
    private static void justGet(Info inf){

        System.out.println("只能获得属性:"+inf.getName()+":"+inf.getAge());
    }
    
    //在一般的方法里传递实现类,可以正常访问
    private static void canSet(Person p){
        System.out.println("原信息:"+p.toString());

        p.setName("我改名字了");

        System.out.println("修改后:"+p.toString());
    }
}

运行结果:

只能获得属性:zhangsan:24
原信息:Person{name='zhangsan', age='24'}
修改后:Person{name='我改名字了', age='24'}

对象的拷贝

  • 前面讲了,对象的传递是引用传递,所以一般的赋值法肯定不行的,所以出现了clone,即将对象复制一份。在讲clone()之前,先谈谈另外一种拷贝对象的思路:叫做拷贝构造函数的方式拷贝一个对象,代码演示如下:
  • package CopyConstructors;
    /*
    想要复制一个对象,无非就是复制对象的成员而已,我们可以通过重载构造函数的方式实现
    */
    public final class Galaxy {  
        private Double mass;
        private final String name;
    
        /**
         * 正常的构造函数
         */
        public Galaxy(Double mass, String name) {
            this.mass = mass;
            this.name = name;
        }
    
        /**
         * 拷贝构造函数
         */
        public Galaxy(Galaxy galaxy) {
            this(galaxy.getMass(), galaxy.getName());
            //no defensive copies are created here, since
            //there are no mutable object fields (String is immutable)
        }
    
        /**
         * 或者利用newInstance的方法
         */
        public static Galaxy newInstance(Galaxy galaxy) {
            return new Galaxy(galaxy.getMass(), galaxy.getName());
        }
    
        public Double getMass() {
            return mass;
        }
    
        /*
      
         这是唯一一个能修改Galaxy的属性(状态)的方法,如果这个方法都没有了,拷贝构造函数就没有了意义
    因为不可变的对象不需要。
         */
        public void setMass(Double aMass){
            mass = aMass;
        }
    
        public String getName() {
            return name;
        }
    
    //测试
        public static void main (String... args){
            Galaxy m101 = new Galaxy(15.0, "M101");
    
            Galaxy m101CopyOne = new Galaxy(m101);
            m101CopyOne.setMass(25.0);
    
            Galaxy m101CopyTwo = Galaxy.newInstance(m101);
            m101CopyTwo.setMass(35.0);
    
            log("M101 mass: " + m101.getMass());
            log("M101CopyOne mass: " + m101CopyOne.getMass());
            log("M101CopyTwo mass: " + m101CopyTwo.getMass());
        }
    
        private static void log(String msg) {
            System.out.println(msg);
        }
    }

    M101 mass: 15.0
    M101CopyOne mass: 25.0
    M101CopyTwo mass: 35.0

通过这种方式实现了对象的简单复制,但是如果对象里面含有引用对象呢?那么我们就要在get该属性的时候,get一个new出来的引用,且这个引用的初始值就是本类对象当前的值,这样返回的就不是本类的引用而是和本类引用一样的新的对象,这样在拷贝构造函数的时候拷贝的该引用也是一个新的sub对象。(感觉这里没有阐述清楚,因为不是特别想说太多)。

  • 谈谈如何使用clone()

现在来谈clone,关于clone这里不说太多,有很多写的很好的博文,只是这里自己想看看源码,也算是更深一步了解Object吧


    protected native Object clone() throws CloneNotSupportedException;
package java.lang;

public interface Cloneable {
}

如何使用:


import org.junit.Test;

import java.io.Serializable;

/**
 * Person类
 *
 */
class Person extends Object implements Cloneable {

    private static final long serialVersionUID = -9102017020286042305L;
    private String name;    // 姓名
    private int age;        // 年龄
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }

    /**
     * 演示clone()
     * @return
     */
    @Override
    public Object clone(){
       Object o=null;
        try {
            o = (Object)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return o;
    }

    @Override
    public String toString() {
        return "Info [name=" + name + ", age=" + age + "]";
    }
}

public class CloneTest {

    /**
     * object的clone()方法实现对象的浅复制
     */
    @Test
    public void Test() {
        Person p1 = new Person("zhang san",34);
        System.out.println("p1:"+p1);
        System.out.println("将p1 克隆 得到新对象p2:  ");

        Person p2 = (Person)p1.clone();

        System.out.println("p1:"+p1);
        System.out.println("p2:"+p2); 
     
    }
}
  • 谈谈clone()在什么情况下出现什么问题(有引用对象时,浅复制不行,需要深复制)

当对象的属性包含引用对象时,就出现问题了

package SerializableTest;

import org.junit.Test;

import java.io.Serializable;

/**
 * Person类
 *
 */
class Person extends Object implements Serializable,Cloneable {

    private static final long serialVersionUID = -9102017020286042305L;
    private String name;    // 姓名
    private int age;        // 年龄
    private Car car;        // 座驾
    public Person(String name, int age, Car car) {
        this.name = name;
        this.age = age;
        this.car = car;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Car getCar() {
        return car;
    }
    public void setCar(Car car) {
        this.car = car;
    }

    /**
     * 演示clone()
     * @return
     */
    @Override
    public Object clone(){
       Object o=null;
        try {
            o = (Object)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return o;
    }

    @Override
    public String toString() {
        return "Info [name=" + name + ", age=" + age + ", car=" + car + "]";
    }
}
/**
 * 汽车类
 * @author
 *
 */
class Car implements Serializable {
    private static final long serialVersionUID = -5713945027627603702L;
    private String brand;       // 品牌
    private int maxSpeed;       // 最高时速
    public Car(String brand, int maxSpeed) {
        this.brand = brand;
        this.maxSpeed = maxSpeed;
    }
    public String getBrand() {
        return brand;
    }
    public void setBrand(String brand) {
        this.brand = brand;
    }
    public int getMaxSpeed() {
        return maxSpeed;
    }
    public void setMaxSpeed(int maxSpeed) {
        this.maxSpeed = maxSpeed;
    }
    @Override
    public String toString() {
        return "Car [brand=" + brand + ", maxSpeed=" + maxSpeed + "]";
    }
}

public class CloneTest {

    /**
     * object的clone()方法实现对象的浅复制
     */
    @Test
    public void Test() {
        Person p1 = new Person("zhang san",34,new Car("IHG",330));
        System.out.println("p1:"+p1);
        System.out.println("将p1 克隆 得到新对象p2:  ");
        Person p2 = (Person)p1.clone();
        System.out.println("p1:"+p1);
        System.out.println("p2:"+p2);

        System.out.println("修改p2的Car属性的brand属性为Lj:");
        //修改克隆的Car引用对象的属性(考察这是浅复制还是深复制)
        p2.getCar().setBrand("Lj");

        System.out.println("p1:"+p1);
        System.out.println("p2:"+p2);
    }
}

结果:
p1:Info [name=zhang san, age=34, car=Car [brand=IHG, maxSpeed=330]]
将p1 克隆 得到新对象p2:  
p1:Info [name=zhang san, age=34, car=Car [brand=IHG, maxSpeed=330]]
p2:Info [name=zhang san, age=34, car=Car [brand=IHG, maxSpeed=330]]
修改p2的Car属性的brand属性为Lj:
p1:Info [name=zhang san, age=34, car=Car [brand=Lj, maxSpeed=330]]
p2:Info [name=zhang san, age=34, car=Car [brand=Lj, maxSpeed=330]]

  • 第一种深复制(略过,通过new,依次get,set)

  • 第二种(在重写clone方法的时候,对引用对象也进行clone)

上述clone()修改为:

/**
 * 演示clone()
 * @return
 */
@Override
public Object clone(){
   Person o=null;
    try {
        o = (Person) super.clone();
    } catch (CloneNotSupportedException e) {
        e.printStackTrace();
    }
   o.car= new Car(o.getCar().getBrand(),o.getCar().getMaxSpeed());
    return o;
}

即可

代码演示:

一个工具类,即利用序列化复制对象

package SerializableTest;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 * 利用序列化与反序列化实现对象的深度克隆的工具类
 *
 */
public class MyUtil {

    private MyUtil() {
        throw new AssertionError();
    }
    public static <T> T clone(T obj) throws Exception {
        //这几部实现序列化(将要被克隆的对象写进一个字节数组中)
        //创建字节数组输出流
        ByteArrayOutputStream bout = new ByteArrayOutputStream();

        //这是序列化要使用的对象,以字节输出流作bout为构造参数
        ObjectOutputStream oos = new ObjectOutputStream(bout);

        //将要被克隆(序列化)的对象写进一个内部的字节数组中
        oos.writeObject(obj);

        //进行反序列化,在这里其实就是进行克隆
        //创建字节数组的输入流,构造参数是bout内部的那个字节数组
        ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());

        //这是反序列化需要使用的对象,以bin为参数来构造
        ObjectInputStream ois = new ObjectInputStream(bin);

        //进行反序列化(即把之前的字节数据读取,并返回一个Object对象)
        return (T) ois.readObject();

        // 说明:调用ByteArrayInputStream或ByteArrayOutputStream对象的close方法没有任何意义
        // 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源,这一点不同于对外部资源(如文件流)的释放
    }
}

将之前的代码修改过后,利用序列化和反序列化实现了对象的深复制

package SerializableTest;

import org.junit.Test;

import java.io.Serializable;

/**
 * Person类
 *
 */
class Person extends Object implements Serializable,Cloneable {

    private static final long serialVersionUID = -9102017020286042305L;
    private String name;    // 姓名
    private int age;        // 年龄
    private Car car;        // 座驾
    public Person(String name, int age, Car car) {
        this.name = name;
        this.age = age;
        this.car = car;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Car getCar() {
        return car;
    }
    public void setCar(Car car) {
        this.car = car;
    }

    /**
     * 演示clone()
     * @return
     */
    @Override
    public Object clone(){
       Person o=null;
        try {
            o = (Person) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
       o.car= new Car(o.getCar().getBrand(),o.getCar().getMaxSpeed());
        return o;
    }

    @Override
    public String toString() {
        return "Info [name=" + name + ", age=" + age + ", car=" + car + "]";
    }
}
/**
 * 汽车类
 * @author
 *
 */
class Car implements Serializable {
    private static final long serialVersionUID = -5713945027627603702L;
    private String brand;       // 品牌
    private int maxSpeed;       // 最高时速
    public Car(String brand, int maxSpeed) {
        this.brand = brand;
        this.maxSpeed = maxSpeed;
    }
    public String getBrand() {
        return brand;
    }
    public void setBrand(String brand) {
        this.brand = brand;
    }
    public int getMaxSpeed() {
        return maxSpeed;
    }
    public void setMaxSpeed(int maxSpeed) {
        this.maxSpeed = maxSpeed;
    }
    @Override
    public String toString() {
        return "Car [brand=" + brand + ", maxSpeed=" + maxSpeed + "]";
    }
}

public class CloneTest {

   
    public static void main(String[] args) {
        try {
            Person p1 = new Person("SL Ren", 25, new Car("Benz", 300));
            Person p2 = MyUtil.clone(p1);// 深度克隆
            System.out.println("克隆后的p1:"+p1);
            System.out.println("克隆对象p2:"+p2);
            System.out.print("p1==p2?:");
            System.out.println(p1==p2);
            System.out.println("-----------------------------------------");
            p2.getCar().setBrand("KDFIO");
            p2.getCar().setMaxSpeed(400);
            p2.setName("rsl");

            // 修改克隆的Person对象p2关联的汽车对象的品牌属性
            // 原来的Person对象p1关联的汽车不会受到任何影响
            // 因为在克隆Person对象时其关联的汽车对象也被克隆了
            System.out.println("更改克隆对象P2后的结果:");
            System.out.println("p1:"+p1);
            System.out.println("p2:"+p2);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值