Java - 对象克隆

目录

对象克隆

浅克隆

深克隆

多层克隆


对象克隆

在讨论对象克隆之前, 可能会有人说 , 不能直接new一个吗?为什么要克隆

首先关于这个问题, 直接new一个对象, 这个对象里面包含的成员变量是null的 , 那问题又来了, 我不能去一个个set对象的值吗? 当然可以, 但这样麻烦, 对象克隆采用的是native方法,效率更高

那么该怎样去克隆呢? 克隆又分为浅克隆和深克隆

首先这样的方式不叫克隆 (这种只能叫做引用复制):

Object obj1 = new Object();
Object obj2 = obj1;

怎样区分是浅克隆还是深克隆呢?

        在java中, 数据类型分为基本类型和引用类型 , 在复制数据的过程中, 基本类型的值会直接被复制过去, 但是引用类型只能复制引用的地址 , 所以深浅克隆的区别就是是否将引用类型所指向的变量也复制了

浅克隆

       先来看浅克隆 , 有两种实现方式, 一是重写Object类中的clone() 方法, 二是spring 框架中提供 BeanUtils.copyProperties(source,target); 这里我们演示第一种

//需实现Cloneable接口
public class Person implements Cloneable {

    int num;
    String name;

    public Person() { }

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

    //重写Object中的clone()方法, 实现克隆
    @Override
    protected Person clone() throws CloneNotSupportedException {
        Person person = (Person) super.clone();
        return person;
    }
}
public static void main(String[] args) throws CloneNotSupportedException {

        Person p1 = new Person(100,"jim");
        Person p2 = p1.clone(); //调用克隆方法

        System.out.println(p1==p2);

    }

       以上输出结果当然为 false , 因为克隆后虽然包含数据相同, 但仍旧是两个对象, 采用 == 的方式比较,  输出的是两个对象的引用地址是否相同 , 当然为 false

       当然在这里肯定有人会提出这样一个问题 : String不是引用类型吗? 怎么值跟着复制了

    首先我们回想String的特点, 引用类型没错, 但是String底层的char数组是采用了 final修饰的,  被final修饰的我们叫做常量, 也就是不可改变的, 所以说 , String虽然是引用类型, 但是它的值一旦确定就不能再改变了, 所以值会跟着复制

深克隆

          深克隆会将引用类型所指向对象中包含的数据一同复制, 这里也需实现Cloneable接口

先设计一个Address类

//实现Cloneable接口
public class Address  implements Cloneable{

     String  address;

    public String getAddress() {
        return address;
    }

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

    @Override
    public String toString() {
        return "Address{" +
                "address='" + address + '\'' +
                '}';
    }
    // 重写clone()方法
    @Override
    protected Address clone() throws CloneNotSupportedException {
        return (Address)super.clone(); 
    }
}

接着是person类

//实现Cloneable接口
public class Person implements  Cloneable{

     int num;
     String name;
     //在Person中关联Address对象
     Address address;

    public Person() {
    }

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

    // 由于会导致篇幅过长, 此处省略属性的get和set方法
    
    //重写clone()方法实现深度克隆
    @Override
    protected Person clone() throws CloneNotSupportedException {
        Person person = (Person)super.clone();
        //深度复制  联同person中关联的对象也一同克隆
        person.address = (Address)address.clone();
        return person;
    }

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

test类 

public class Test {

    public static void main(String[] args) throws CloneNotSupportedException {
        //先new一个Address类对象并设置值
        Address address = new Address();
                address.setAddress("成都");
        // new 一个Person类对象并设置值(将address属性加入)
        Person p1 = new  Person(100,"jim");
               p1.setAddress(address);
        //克隆
        Person p2 =p1.clone();
               p2.setName("tom");
        //改变address所指向对象的值,如果深克隆成功,这个值即使改变也影响不了p2
        address.setAddress("西安");

        System.out.println("p1:"+p1);
        System.out.println("p2:"+p2);
        /*
        * 结果 : p1:Person{num=100, name='jim', address=Address{address='西安'}}
        *       p2:Person{num=100, name='tom', address=Address{address='成都'}}
        *       深克隆成功
        * */
    }
}

          这就是深克隆, 连同引用对象中包含的值一起复制, 在上述例子中 , 如果是浅克隆, 仅仅复制了引用, 引用一旦发生改变, 原对象和克隆的对象中的address属性都会发生改变(如果浅克隆上述例子p1和p2中的address都会为"西安")

多层克隆

       了解了深克隆, 那么有人会想到, 如果对象中套对象, 对象中再套对象, 这样一直连环套该怎样解决呢 ?

       最为简单粗暴的一种方式就是 : 手动一层一层克隆, 这样当然也可以, 但是这里我们介绍一种更为简便的方式 : 序列化   , 当然简单的深克隆也是可以去使用序列化这种方式的

     序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现Serializable 接口,否则无法实现序列化操作。

Address类

//实现Serializable接口
public class Address  implements Serializable {

     String  address;

    public String getAddress() {
        return address;
    }

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

    @Override
    public String toString() {
        return "Address{" +
                "address='" + address + '\'' +
                '}';
    }
}

Person类

//实现Serializable接口
public class Person implements Serializable {

     int num;
     String name;
     Address address;

    public Person() {
    }

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

    //省略get和set方法

    //自定义克隆方法
    public Person myclone() {
            Person person = null;
              try {
                  // 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝
                     ByteArrayOutputStream baos = new ByteArrayOutputStream();
                      ObjectOutputStream oos = new ObjectOutputStream(baos);
                      oos.writeObject(this);
            // 将流序列化成对象
                    ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
                     ObjectInputStream ois = new ObjectInputStream(bais);
                     person = (Person) ois.readObject();
                  } catch (IOException e) {
                     e.printStackTrace();
                  } catch (ClassNotFoundException e) {
                     e.printStackTrace();
                 }
             return person;
          }


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

测试不变, 不过这里我们克隆使用的是自己定义的myClone()方法 ,使用流的方式完成

关于对象克隆就说到这里, 感谢阅读. 

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值