zoukankan      html  css  js  c++  java
  • 一步步分析Java深拷贝的两种方式clone和序列化

    今天遇到一道面试题,询问深拷贝的两种方法。主要就是clone方法和序列化方法。今天就来分析一下这两种方式如何实现深拷贝。如果想跳过解析的朋友,直奔“重点来了!”寻找答案。

    clone方法

    例1:我们不妨建立一个Exam对象

    考试类Exam.java文件

    public class Exam implements Cloneable {
    
        private int examId;
    
        private String examName;
    
    
        public Exam() {
        }
    
        public Exam(int examId, String examName) {
            this.examId = examId;
            this.examName = examName;
        }
    
        public int getExamId() {
            return examId;
        }
    
        public void setExamId(int examId) {
            this.examId = examId;
        }
    
        public String getExamName() {
            return examName;
        }
    
        public void setExamName(String examName) {
            this.examName = examName;
        }
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    

    测试类Main.java

    public class Main {
    
        public static void main(String[] args) throws CloneNotSupportedException {
            Exam exam = new Exam(1, "语文考试");
            Exam cloneExam = (Exam) exam.clone();
            System.out.println(cloneExam != exam);
            System.out.println(cloneExam.equals(exam));
        }
    }
    

    控制台输出:
    true
    false

    我们确实拷贝出了另一个对象。equals没有覆写,所以调用的是java.lang.Object中的以下方法:

    public boolean equals(Object obj) {
    	return (this == obj);
    }
    

    例2:假如我们给考试加个监考老师

    老师类Teacher.java,不实现Cloneable接口

    public class Teacher {
    
        private String name;
    
        public Teacher(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    

    把老师对象作为属性新增到考试类Exam.java中(设置监考老师)

    public class Exam implements Cloneable {
    
        private int examId;
    
        private String examName;
    
        private Teacher teacher;
    
        public Exam() {
        }
    
        public Exam(int examId, String examName) {
            this.examId = examId;
            this.examName = examName;
        }
    
        public int getExamId() {
            return examId;
        }
    
        public void setExamId(int examId) {
            this.examId = examId;
        }
    
        public String getExamName() {
            return examName;
        }
    
        public void setExamName(String examName) {
            this.examName = examName;
        }
    
        public Teacher getTeacher() {
            return teacher;
        }
    
        public void setTeacher(Teacher teacher) {
            this.teacher = teacher;
        }
    
        @Override
        public String toString() {
            return "Exam{" +
                    "examId=" + examId +
                    ", examName='" + examName + '\'' +
                    ", teacher=" + teacher +
                    '}';
        }
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    

    改写测试类Main.java

    public class Main {
    
        public static void main(String[] args) throws CloneNotSupportedException {
            Exam exam = new Exam(1, "语文考试");
            Teacher teacher = new Teacher("马老师");
            exam.setTeacher(teacher);
            Exam cloneExam = (Exam) exam.clone();
            System.out.println(cloneExam != exam);
            System.out.println(cloneExam.equals(exam));
    
            cloneExam.getTeacher().setName("Lily");
            System.out.println(exam.toString());
            System.out.println(cloneExam.toString());
        }
    }
    

    控制台输出:
    true
    false
    Exam{examId=1, examName='语文考试', teacher=Teacher{name='Lily'}}
    Exam{examId=1, examName='语文考试', teacher=Teacher{name='Lily'}}

    相信眼尖的朋友已经发现端倪了,详细的分析可见下文“clone方法的存在问题”

    clone方法总结:

    调用clone方法的前提:

    1. Exam需要继承java.lang.Cloneable接口。否则代码在运行时报错。

    解释:
    调用exam.clone()的对象类Exam需要继承Cloneable接口,否则会在代码运行时抛出CloneNotSupportedException异常

    1. Exam需要覆写父类的clone()方法。否则代码在编译时报错。

    解释:
    因为clone()java.lang.Object中是protected访问控制。如果不覆写,exam.clone()这句代码无法编译通过

    clone方法的存在问题:

    我们从上述例2中结果中发现,我原本只想将克隆出来的考试的监考老师改为 Lily ,但是把原考试对象的监考老师也修改了,这就十分尴尬了。

    阅读java.lang.Object中的clone()方法上的英文注释时有这样一段话:

    *** this method creates a new instance of the class of this object and initializes all its fields with exactly the contents of the corresponding fields of this object, as if by assignment; the contents of the fields are not themselves cloned. Thus, this method performs a "shallow copy" of this object, not a "deep copy" operation. ***

    翻译为:

    该方法创建该对象类的新实例,并使用该对象相应字段的内容完全初始化其所有字段,就像通过赋值一样; 字段的内容本身不会被克隆。 因此,此方法执行此对象的“浅复制”,而不是“深复制”操作。

    重点来了!使用clone方式实现“深拷贝”

    覆写考试类Exam.javaclone()方法

    @Override
    protected Object clone() throws CloneNotSupportedException {
    	Exam exam = (Exam) super.clone();
    	if (teacher != null) {
    		Teacher teacher = (Teacher) this.teacher.clone();
    		exam.setTeacher(teacher);
    	}
    	return exam;
    }
    

    解析

    用上述方法,取代return super.clone()的默认实现。同时因为这里调用了teacher.clone(),所以类Teacher也要实现Cloneable接口,覆写clone()方法。

    改写老师类Teacher.java

    public class Teacher implements Cloneable{
    
        private String name;
    
        public Teacher() {
        }
    
        public Teacher(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "Teacher{" +
                    "name='" + name + '\'' +
                    '}';
        }
    
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    

    控制台输出:
    true
    false
    Exam{examId=1, examName='语文考试', teacher=Teacher{name='马老师'}}
    Exam{examId=1, examName='语文考试', teacher=Teacher{name='Lily'}}

    序列化方法

    每个对象覆写Cloneable方法也是够麻烦的,接下来的介绍的序列化方法更为简洁。

    原理:对象->字节数组(拷贝)->对象

    提到序列化,就不得不提到java.lang.Serializable,建议好好阅读一下类上的注释。

    静态的序列化“深拷贝”方法(简易版)

    public class Util {
        private Util() {}
        public static Object deepCopy(Object exam) throws IOException, ClassNotFoundException {
            ByteArrayOutputStream bs = new ByteArrayOutputStream();
            ObjectOutputStream os = new ObjectOutputStream(bs);
            os.writeObject(exam);
    
            ByteArrayInputStream bis = new ByteArrayInputStream(bs.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            return ois.readObject();
        }
    }
    

    例1:考试类(无对象成员变量)

    考试类对象Exam.java实现Serializable接口

    public class Exam implements Serializable {
    
        private int examId;
    
        private String examName;
    
        public Exam() {
        }
    
        public Exam(int examId, String examName) {
            this.examId = examId;
            this.examName = examName;
        }
    
        public int getExamId() {
            return examId;
        }
    
        public void setExamId(int examId) {
            this.examId = examId;
        }
    
        public String getExamName() {
            return examName;
        }
    
        public void setExamName(String examName) {
            this.examName = examName;
        }
    
    }
    

    测试类Main.java

    public class Main {
        public static void main(String[] args) throws IOException, ClassNotFoundException {
            Exam exam = new Exam(1, "语文考试");
            Exam copyExam = (Exam) Util.deepCopy(exam);
            System.out.println(copyExam != exam);
            System.out.println(copyExam.equals(exam));
        }
    }
    

    控制台输出:
    true
    false

    例2:考试类(含对象成员变量)

    老师类Teacher.java

    public class Teacher implements Serializable {
    
        private String name;
    
        public Teacher() {
        }
    
        public Teacher(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "Teacher{" +
                    "name='" + name + '\'' +
                    '}';
        }
    
    }
    

    改写Exam.java,新增成员变量teacher

    public class Exam implements Serializable {
    
        private int examId;
    
        private String examName;
    
        private Teacher teacher;
    
        public Exam() {
        }
    
        public Exam(int examId, String examName) {
            this.examId = examId;
            this.examName = examName;
        }
    
        public int getExamId() {
            return examId;
        }
    
        public void setExamId(int examId) {
            this.examId = examId;
        }
    
        public String getExamName() {
            return examName;
        }
    
        public void setExamName(String examName) {
            this.examName = examName;
        }
    
        public Teacher getTeacher() {
            return teacher;
        }
    
        public void setTeacher(Teacher teacher) {
            this.teacher = teacher;
        }
    
        @Override
        public String toString() {
            return "Exam{" +
                    "examId=" + examId +
                    ", examName='" + examName + '\'' +
                    ", teacher=" + teacher +
                    '}';
        }
    }
    

    改写测试类Main.java

    public class Main {
        public static void main(String[] args) throws IOException, ClassNotFoundException {
            Exam exam = new Exam(1, "语文考试");
            Teacher teacher = new Teacher("马老师");
            exam.setTeacher(teacher);
            Exam copyExam = (Exam) Util.deepCopy(exam);
            System.out.println(copyExam != exam);
            System.out.println(copyExam.equals(exam));
    
            copyExam.getTeacher().setName("Lily");
            System.out.println(exam);
            System.out.println(copyExam);
        }
    }
    

    控制台输出:
    true
    false
    Exam{examId=1, examName='语文考试', teacher=Teacher{name='马老师'}}
    Exam{examId=1, examName='语文考试', teacher=Teacher{name='Lily'}}

    序列化方法总结

    调用deepCopy方法的前提:

    1. Exam需要实现java.lang.Serializable接口。否则代码在运行时报错。

    解释:
    对象类Exam需要实现java.lang.Serializable接口,否则会在代码执行到os.writeObject(exam)时抛出NotSerializableException异常。
    对象序列化错误

    1. Exam中的成员变量类Teacher也需要实现java.lang.Serializable接口。否则在运行时报错。

    解释:
    当类Exam中包含了成员变量Teacher时,如果只有Exam实现java.lang.Serializable接口,但是Teacher没有实现java.lang.Serializable接口,那么代码执行到os.writeObject(exam)时还是会**抛出NotSerializableException异常。
    成员变量对象序列化出错

    重点来了!使用泛型实现序列化“深拷贝”方法

    public class Util {
        private Util() {}
        @SuppressWarnings("unchecked")
        public static <T extends Serializable> T deepCopy(T obj) {
            T cloneObj = null;
            try {
                //写入字节流
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                ObjectOutputStream obs = new ObjectOutputStream(out);
                obs.writeObject(obj);
                obs.close();
    
                //分配内存,写入原始对象,生成新对象
                ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
                ObjectInputStream ois = new ObjectInputStream(ios);
                //返回生成的新对象
                cloneObj = (T) ois.readObject();
                ois.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return cloneObj;
        }
    }
    

    使用该方法可以在代码编译期检查出没有实现java.lang.Serializable接口的对象。

    总结

    1. clone()方法要求目标类及其成员变量类都需要实现java.lang.Cloneable接口,并且覆写java.lang.Objectclone()方法。
    2. 序列化方法通过静态方法实现,其目标类及其成员变量类都需要实现java.lang.Serializable接口。
  • 相关阅读:
    生成lua的静态库.动态库.lua.exe和luac.exe
    Eclipse 常用快捷键 (动画讲解)
    Lua table之弱引用
    编程语言简史(转)
    sublime text 下的Markdown写作
    使用python拼接多张图片.二三事
    Lua标准库- 模块(Modules)
    lua的私有性(privacy)
    Lua字符串库(整理)
    字符编码的故事(转)
  • 原文地址:https://www.cnblogs.com/kendoziyu/p/12043834.html
Copyright © 2011-2022 走看看