Java 对象克隆、深拷贝、浅拷贝

Java 对象克隆、深拷贝、浅拷贝

背景

前一阵子在测试的时候,开发小哥因为需要缓存一个比较常用的对象,故此保存了一份;但又因为业务需要这份对象需要做一些改变。
因为开发小哥没有考虑到这个业务需求,导致对象每次在进行业务修改后,下个用户请求进来会是以上次用户请求的对象返回,造成了这样的一个bug。
后来,开发小哥用了深拷贝的方式重新复制了一个对象。

故此,顺带再回顾下Java中对象克隆、深拷贝、浅拷贝。
在实际开发工作中会出现如下场景,当A对象在某业务场景下需要克隆出一个一模一样的对象,同时新克隆出来的对象与原有对象不能有关系。要求新旧对象是两个长的对象,但是两个完全独立的对象。

一、对象克隆

在Java中,用简单赋值语句无法满足对象克隆的需求,要满足上诉需求有很多途径,但实际上可以通过clone()方法最简单最高效的解决。
总所周知Java所有对象的父类是java.lang.Object类,同时Object类中有一个clone()方法。如下:

protected native Object clone() throws CloneNotSupportedException;
    1. 注意观察该方法,是一个native方法。一般说来native方法的效率要远远高于Java非native方法。
    1. 同时该方法是protected受保护的方法。因为所有的对象自身就是继承于Object类,所以这个方法自然存在。
    1. 为了实现自定义的clone功能,则需要重写clone方法。但如果要让其他类能够调用到clone方法,则必须将clone方法属性设置为public。
      为了达到clone方法能够使用的底部,Java提供了java.lang.Cloneable接口,接口如下:
    public interface Cloneable {
    }
    

Cloneable接口不包含任何方法!其实这个接口仅仅是一个标志,而且这个标志也仅仅是针对Object类clone()方法的,如果需要克隆的类没有Cloneable接口,同时又调用了Object的clone()方法,则Object的clone()方法就会抛出CloneNotSupportedException异常。
例如:

CloneNotSupporedException异常

二、深拷贝与浅拷贝

浅拷贝是指拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象。深拷贝不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象。举例来说更加清楚:对象A1中包含对B1的引用,B1中包含对C1的引用。浅拷贝A1得到A2,A2 中依然包含对B1的引用,B1中依然包含对C1的引用。深拷贝则是对浅拷贝的递归,深拷贝A1得到A2,A2中包含对B2(B1的copy)的引用,B2 中包含对C2(C1的copy)的引用。

以下是本文的类结构:


Car类

Student类中成员变量有Car类

Car类和Student类都实现了Cloneable接口,同时Student类中成员变量有Car类。

(一)浅拷贝

若不对clone()方法进行改写,则调用此方法得到的对象即为浅拷贝,下面我们着重谈一下深拷贝。运行下面的程序,看一看浅拷贝。

  • Car类
    public class Car implements Cloneable {
        private String color;
        private int price;
    
        public Car(String color, int price) {
            this.color = color;
            this.price = price;
        }
    
        public String getColor() {
            return color;
        }
    
        public void setColor(String color) {
            this.color = color;
        }
    
        public int getPrice() {
            return price;
        }
    
        public void setPrice(int price) {
            this.price = price;
        }
    
        @Override
        protected Object clone() {
            Car car = null;
            try {
                car = (Car) super.clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return car;
        }
    
        @Override
        public String toString() {
            return "Car{" +
                    "color='" + color + '\'' +
                    ", price=" + price +
                    '}';
        }
    }
    
  • Student类
    public class Student implements Cloneable {
        private String name;
        private int age;
        private Car car;
    
        public Student(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;
        }
    
        @Override
        protected Object clone() {
            Student student = null;
            try {
                student = (Student) super.clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return student;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", car=" + car +
                    '}';
        }
    }
    
  • 测试类
    public class TestClone {
        @Test
        public void testName() {
            Car car = new Car("red", 111);
            Student s1 = new Student("a", 12, car);
            Student s2 = (Student) s1.clone();
            System.out.println(s1);
            System.out.println(s2);
            System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~");
            s2.getCar().setColor("blue");
            s2.getCar().setPrice(110);
            s2.setAge(2);
            s2.setName("dw");
            System.out.println(s1);
            System.out.println(s2);
        }
    }
    
  • 运行结果:
    Student{name='a', age=12, car=Car{color='red', price=111}}
    Student{name='a', age=12, car=Car{color='red', price=111}}
    ~~~~~~~~~~~~~~~~~~~~~~~~~~
    Student{name='a', age=12, car=Car{color='blue', price=110}}
    Student{name='dw', age=2, car=Car{color='blue', price=110}}
    

从结果上可以看出,当s2设置了car的值后,s1的car也随着改变了。可知浅拷贝只克隆了最外一层类的对象,而其他并未克隆。

(二)深拷贝

对此,将Student类进行了修改达到深拷贝的结果。调整后的Student类如下:
```
public class Student implements Cloneable {
private String name;
private int age;
private Car car;

    public Student(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;
    }

    @Override
    protected Object clone() {
        Student student = null;
        try {
            student = (Student) super.clone();
// 如果该类里有其他类的成员变量是引用类型,则将引用类型的对象也进行克隆操作。
            Car car = (Car) student.getCar().clone();
            student.setCar(car);
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return student;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", car=" + car +
                '}';
    }
}
```
  • 运行结果:
    Student{name='a', age=12, car=Car{color='red', price=111}}
    Student{name='a', age=12, car=Car{color='red', price=111}}
    ~~~~~~~~~~~~~~~~~~~~~~~~~~
    Student{name='a', age=12, car=Car{color='red', price=111}}
    Student{name='dw', age=2, car=Car{color='blue', price=110}}
    

运行结果会发现,s2中car的改变不影响s1的car值。

浅拷贝只是进行了目标对象的拷贝,并没有对对象中的引用类型变量也进行拷贝;
深拷贝恰恰是将目标对象克隆,同时也对目标对象中的引用类型变量同时进行了克隆,如果引用对象中还存在引用对象,则也一并进行克隆,最后达到深度克隆的效果。

参考

https://www.cnblogs.com/xuanxufeng/p/6558330.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容