Java ☞ 对象的克隆

版权声明:有问题的请留言 喜欢的请给个赞 --------------------------------不定时会更新,因为学习,所以快乐,因为分享,所以便捷!转载请标注出处,哈哈! https://blog.csdn.net/Appleyk/article/details/82866242

      我喜欢用自己的想法去阐述一个在项目中用到的技术,虽然网上到处都可以搜到,但我还是决定自己写下来,就像记笔记一样,记录在自己的博客中,这就是我的每一篇博文诞生的初衷。

      不是为了抄袭,也不是为了比别人写的更好,仅仅是为了记录,正因为有了记录,我有了自己的想法,这样很棒!

                                                                                                                                                 --- 2018年9月27日14:04:41

一、为什么要用到对象的克隆?

       项目中有个地方需要借助某个对象进行某个算法的计算,假设这个对象是A,而这个算法需要对A中的某些信息进行诸如合并之类的操作,比如A中有两个成员变量,假设一个叫"m",另一个叫"n",合并后自然成了"mn",如果A这个对象在后续操作中不再使用(针对一个线程,也就是A始终贯彻整个业务线),则我门完全不用担心,但事实是,A在后续的操作中仍然会使用到,也就是对象在一个业务线中会多次使用到;

       由于A在结束一段算法的时候,其内部信息已经发生了改变(对象的引用),而我再次使用对象A时,发现原来的成员变量m已经不是m了,n也不是n了? 如何是好?

        这种情况下,就只能借助对象的克隆技术了,也就是在对A进行算法之前,对A先进行一个克隆,克隆出一个"一模一样"的对象B,这时候算法的操作过程针对的只是对象B(独立的内存空间,但是信息和A是一样的),这样一来,我即可以拿到我想要的mn,又可以在再次使用A的时候,拿到我想要的m和n,一举两得,互不干扰。

(注:如果需要用到克隆技术的,耗费一点内存空间也是值得的,前提是,克隆的对象不多,内存足够)

二、克隆有类型之分吗?

        根据克隆程度的深浅(即对象是完全克隆还是部分克隆),我们将Java中的clone分为深拷贝浅拷贝

        这是什么意思呢 ?  我举个例子

        假设有一份食谱,我们称作A,这个A的作者叫B,我们用Java类表示就是:有一个类是A,还有一个类是B,A中包含了B,也就是A和B之间是组合的关系

(1)浅拷贝

     如果我只想要食谱,而不关心其创作者是谁的话,我就用浅拷贝,我们将这种克隆方式得到的新对象称作A+,A+的内存地址是独立的,但是其内部的对象B却不是独立的内存,其仍然指向先前A对象中的B对象的内存地址,因此,改变A+的成员变量并不会影响原有的A对象(因为二者是两个完全独立的内存空间),但是改变A+中的B对象的成员变量却会影响原有A对象中的B对象

     这就好比,我食谱拿来后,我可以在原有的食谱上加以修改,但是不会破坏最开始的食谱,但是作者我却可以改变,因为如果我改变后得到一个新的食谱,理应将新食谱的作者改成我自己,所谓吃水不忘挖井人,源食谱的内容我是不会动的。

  

实现方法:实现Cloneable接口,重写基类Object的clone方法

注意:这是一个native方法,其要比对象之间的直接引用性能要高

什么是native方法?【摘自网络】

        native关键字说明其修饰的方法是一个原生态方法,方法对应的实现不是在当前文件,而是在用其他语言(如C和C++)实现的文件中。Java语言本身不能对操作系统底层进行访问和操作,但是可以通过JNI接口调用其他语言来实现对底层的访问。

什么是对象间的直接引用呢?

如: Object A = new Object(); Obejct B = A;

(2)深拷贝

      即对象完全克隆;不管A对象中包含了多少个B对象,B对象里面又套了其他对象(嵌套克隆)...etc,纵使情况如此复杂,我的目的只有一个,就是完全将A对象进行"复制",复制到什么程度呢:即复制后得到的新对象A+,不管我对A+及其内部对象做了什么操作,都不会改变原有的对象A,这就叫做深度克隆

      

实现方法:实现Serializable接口,自定义克隆方法,利用对象的字节流进行序列化和反序列化得到新的对象

三、克隆的实现

(1)基于SpringBoot框架的项目,文章最后会提供GitHub演示demo下载地址

 

(1)构造用户类User

package com.appleyk.pojo;

import java.io.Serializable;

/**
 * @Description 用户表
 * @Author Appleyk
 * @Blob https://blog.csdn.net/appleyk
 * @Date Created on 下午 12:45 2018-9-27
 */
public class User implements Serializable {

    /**
     * 用户ID
     */
    private Long   uid ;

    /**
     * 用户名称
     */
    private String name;

    /**
     * 用户年龄
     */
    private Integer age;

    public  User(Long uid,String name,Integer age){
        this.uid = uid;
        this.name = name;
        this.age = age;
    }

    public Long getUid() {
        return uid;
    }

    public void setUid(Long uid) {
        this.uid = uid;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

    @Override
    public String toString() {
        return "姓名:"+this.name+",工号:"+this.uid+",年龄:"+this.age;
    }
}

注意:为了实现深度克隆,必须让嵌套的所有关联的对象也都实现Serializable接口

(2)构建食谱类

package com.appleyk.pojo;

import com.appleyk.utils.DateUtils;


import java.io.*;
import java.util.Date;

/**
 * @Description -- 食谱类
 * @Author Appleyk
 * @Blob https://blog.csdn.net/appleyk
 * @Date Created on 上午 11:03 2018-9-27
 */
public class CookBook implements  Cloneable,Serializable {

    // 食谱名称
    private String memu;
    // 食谱创建时间
    private Date   ctime;
    // 用户信息
    private User user;

    public  CookBook(){
        ctime = new Date();
    }

    public String getMemu() {
        return memu;
    }

    public void setMemu(String memu) {
        this.memu = memu;
    }

    public Date getCtime() {
        return ctime;
    }

    public void setCtime(Date ctime) {
        this.ctime = ctime;
    }

    public User getUser() {
        return user;
    }

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

    @Override
    public String toString(){
        return "食谱名称:"+this.memu+",食谱作者信息:--【"+user+"】 --- "+DateUtils.dateTostring(this.ctime);
    }

    /**
     * 对象之间的浅克隆【只负责copy对象本身,不负责深度copy其内嵌的成员对象】
     * @return
     */
    @Override
    public Object clone() {
        try{
            return  (CookBook) super.clone();
        }catch (CloneNotSupportedException ex){
            System.out.println(ex.getClass()+":"+ ex.getMessage());
        }
        return  null;
    }

    /**
     * 实现对象间的深度克隆【从外形到内在细胞,完完全全深度copy】
     * @return
     */
    public Object deepClone(){

        // Anything 都是可以用字节流进行表示,记住是任何!
        CookBook cookBook = null;
        try{

           ByteArrayOutputStream baos = new ByteArrayOutputStream();
           ObjectOutputStream oos = new ObjectOutputStream(baos);
           // 将当前的对象写入baos【输出流 -- 字节数组】里
           oos.writeObject(this);

           // 从输出字节数组缓存区中拿到字节流
           byte[] bytes = baos.toByteArray();

           // 创建一个输入字节数组缓冲区
           ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
           // 创建一个对象输入流
           ObjectInputStream ois = new ObjectInputStream(bais);
           // 下面将反序列化字节流 == 重新开辟一块空间存放反序列化后的对象
            cookBook = (CookBook) ois.readObject();

        }catch (Exception e){
            System.out.println(e.getClass()+":"+e.getMessage());
        }
        return  cookBook;
    }

}

注意:为了区别浅克隆和深克隆,食谱类同时实现了Cloneable和Serializable接口

(Java只能继承一个父类,但是可以实现多个接口)

(3)初始化一个食谱对象

package com.appleyk;

import com.appleyk.pojo.CookBook;
import com.appleyk.pojo.User;

/**
 * @Description
 * @Author Appleyk
 * @Blob https://blog.csdn.net/appleyk
 * @Date Created on 下午 1:52 2018-9-27
 */
public class ObjectFactory {

    /**
     * 创建一个食谱对象(实例化类)
     * @return
     */
    public static CookBook createCookBook(){
        CookBook cookBook = new CookBook();
        cookBook.setMemu("Appleyk'食谱大全");
        cookBook.setUser(new User(1001L,"appleyk",18));
        return  cookBook;
    }
}

(4)拷贝测试☞ 对象引用【共用一块内存地址】

 /**
     * 对象之间的引用
     * Object a = new Object();
     * Object b = a;
     */
    @Test
    public  void copy1(){

        CookBook cookBookSrc = ObjectFactory.createCookBook();
        // 我们这里打印下原始食谱信息
        System.out.println("源"+cookBookSrc);

        // 然后我们需要复制一份食谱,给另外一个人使用
        CookBook cookBookNew = cookBookSrc;
        // 由于上述是对象之间的直接"赋值"或"复制",因此,新的食谱在内存中的地址引用其实和原始食谱是一样的
        System.out.println("新"+cookBookNew);

        System.out.println("新食谱不做修改之前======================新食谱修改之后");
        cookBookNew.getUser().setAge(27);
        cookBookNew.setMemu("豫菜食谱大全");
        System.out.println("源"+cookBookSrc);
        System.out.println("新"+cookBookNew);
        System.out.println("源 == 新:"+(cookBookSrc.equals(cookBookNew)));
    }

上述代码,得到的新食谱,就是对原有食谱的一个彻底的对象引用,因此,改变新食谱的内容,其实就是同步改变原有食谱对象

输出效果如下:

(5)浅拷贝测试☞ 对象浅克隆【部分内存地址完全独立】

 /**
     * 对象之间的克隆
     * Object a = new Object();
     * Object b = a;
     */
    @Test
    public  void copy2(){

        CookBook cookBookSrc = ObjectFactory.createCookBook();
        // 我们这里打印下原始食谱信息
        System.out.println("源"+cookBookSrc);

        // 然后我们需要克隆一份食谱,给另外一个人使用
        CookBook cookBookNew = (CookBook) cookBookSrc.clone();
        // 由于上述是对象之间的直接"赋值"或"复制",因此,新的食谱在内存中的地址引用其实和原始食谱是一样的
        System.out.println("新"+cookBookNew);

        System.out.println("新食谱不做修改之前======================新食谱修改之后");
        cookBookNew.getUser().setAge(27);
        cookBookNew.setMemu("豫菜食谱大全");
        System.out.println("源"+cookBookSrc);
        System.out.println("新"+cookBookNew);
        System.out.println("源 == 新:"+(cookBookSrc.equals(cookBookNew)));
    }

上述代码,我们利用重写基类Object的clone方法,克隆出了一份新食谱,由于是浅克隆,因此当我们修改新食谱里面的user信息时,源食谱中的user信息也会同步进行改变,但是修改新食谱的内容时,源食谱的内容是不会改变的,这就是所谓的不完全克隆

输出效果如下:

(6)深拷贝测试☞ 对象深克隆【内存地址完全独立】


    @Test
    public  void copy3(){

        CookBook cookBookSrc = ObjectFactory.createCookBook();
        // 我们这里打印下原始食谱信息
        System.out.println("源"+cookBookSrc);

        // 然后我们需要克隆一份食谱,给另外一个人使用
        CookBook cookBookNew = (CookBook) cookBookSrc.deepClone();
        // 由于上述是对象之间的直接"赋值"或"复制",因此,新的食谱在内存中的地址引用其实和原始食谱是一样的
        System.out.println("新"+cookBookNew);

        System.out.println("新食谱不做修改之前======================新食谱修改之后");
        cookBookNew.getUser().setAge(27);
        cookBookNew.setMemu("豫菜食谱大全");
        System.out.println("源"+cookBookSrc);
        System.out.println("新"+cookBookNew);
        System.out.println("源 == 新:"+(cookBookSrc.equals(cookBookNew)));

    }

上述代码,利用我们自定义的deepClone方法(对象字节流的序列与反序列化),深度克隆了一份新的食谱,既然是深度克隆,那么就意味着,无论我怎么修改新食谱的内容(包括修改其内部嵌套的对象的信息),都不会改变源食谱对象的信息

输出效果如下:

四、GitHub项目地址

Java 实现对象的浅拷贝和深拷贝:https://github.com/kobeyk/Spring-Boot-Clone.git

猜你喜欢

转载自blog.csdn.net/Appleyk/article/details/82866242