Java 对象拷贝(复制,克隆)

目录

1、理论或者业务背景

2、代码

2.0 对象定义

2.1 浅拷贝实现(实现Cloneable的接口,重写clone方法)

2.2 深拷贝实现

2.2.1 序列化

2.2.2 Apache库SerializationUtils

2.2.3 JSON Serialization With Gson

2.2.4 JSON Serialization With Jackson

3、原理和讨论

4、参考资料


讲述对象的深浅拷贝原理和简单代码实现

(篇幅原因省略getter setter方法)

1、理论或者业务背景

简单介绍几种相关名词(可只看前3个):

浅拷贝:拷贝出一个新对象,新对象与原对象地址不同,但是原对象与新对象的属性指向地址都是同一个;

深拷贝:原对象与新对象的属性指向堆空间中内存地址不同的对象;

序列化:Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型[1][runoob]。简单来说就是把对象保存到文件里,要用的时候再取出来。

值传递:顾名思义。传递数值。(浅拷贝,对象属性没有序列化的话,是不会被拷贝到的,与这个有关,下同)

引用传递:顾名思义。传递引用或者地址。

垃圾回收:没有引用的对象会被清除掉(这个看起来不相关,实际上这个可以辅助理解值传递和引用传递)

值传递和引用传递作为讨论旁支在“3、原理”中会再讨论

2、代码

2.0 对象定义

/**
 * @Description 水果信息
 * @Author: Zeng Yanru
 * @Date: 2021/7/4 10:44
 */
public class FruitPO {

    private String name;

    private CategoryPO category;

    private String color;
}
/**
 * @Description 水果分类信息
 * @Author: Zeng Yanru
 * @Date: 2021/7/4 10:44
 */
public class CategoryPO {

    private String name;

    private Integer categoryId;
}

2.1 浅拷贝实现(实现Cloneable的接口,重写clone方法)

对象:

/**
 * @Description 水果信息
 * @Author: Zeng Yanru
 * @Date: 2021/7/4 10:44
 */
public class FruitPO implements Cloneable{

    private String name;

    private CategoryPO category;

    private String color;

    @Override
    public FruitPO clone() {
        FruitPO fruit = null;
        try {
            fruit = (FruitPO) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return fruit;
    }
}

方法:

public class FruitService {

    public static void main(String[] args) {
        // 苹果
        FruitPO apple = new FruitPO();
        CategoryPO appleCtg = new CategoryPO();
        appleCtg.setName("apple_cate");
        apple.setName("apple");
        apple.setCategory(appleCtg);

        // 梨子拷贝
        FruitPO pear = apple.clone();
        // 直接原位修改category
        pear.setName("pear");
        pear.getCategory().setName("pear_cate");

        System.out.println("苹果名字:" + apple.getName());
        System.out.println("苹果分类名字:" + apple.getCategory().getName());
        System.out.println("------");
        System.out.println("梨子名字:" + pear.getName());
        System.out.println("梨子分类名字:" + pear.getCategory().getName());
    }
}

结果:

苹果名字:apple
苹果分类名字:pear_cate
------
梨子名字:pear
梨子分类名字:pear_cate

结论:浅拷贝拷贝的只有对象自己,其引用还是没有改变。需要将引用也拷贝,则需要对引用的对象也实现Cloneable的接口,重写clone方法。[2][cnblog]

2.2 深拷贝实现

2.2.1 序列化

方法:就是存文件,读文件

public class FruitService {

    public static void main(String[] args) {
        // 苹果
        FruitPO apple = new FruitPO();
        CategoryPO appleCtg = new CategoryPO();
        appleCtg.setName("apple_cate");
        apple.setName("apple");
        apple.setCategory(appleCtg);

        // 梨子拷贝
        FruitPO pear = deepClone(apple);
        // 直接原位修改category
        pear.setName("pear");
        pear.getCategory().setName("pear_cate");

        System.out.println("苹果名字:" + apple.getName());
        System.out.println("苹果分类名字:" + apple.getCategory().getName());
        System.out.println("------");
        System.out.println("梨子名字:" + pear.getName());
        System.out.println("梨子分类名字:" + pear.getCategory().getName());
    }

    public static <T> T deepClone(T object) {
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
            oos.writeObject(object);
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
            return (T) ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

结果:

苹果名字:apple
苹果分类名字:apple_cate
------
梨子名字:pear
梨子分类名字:pear_cate

结论:

深拷贝(序列化这种方法)有效,引用也拷贝一份

2.2.2 Apache库SerializationUtils

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.9</version>
</dependency>

只需要换成:

import org.apache.commons.lang3.SerializationUtils;
......

// 梨子拷贝
FruitPO pear = SerializationUtils.clone(apple);

结果:

苹果名字:apple
苹果分类名字:apple_cate
------
梨子名字:pear
梨子分类名字:pear_cate

结论:

同样是深拷贝

2.2.3 JSON Serialization With Gson

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.8.6</version>
</dependency>

同理可换成:

// 梨子拷贝
Gson gson = new Gson();
FruitPO pear = gson.fromJson(gson.toJson(apple), FruitPO.class);

结果

苹果名字:apple
苹果分类名字:apple_cate
------
梨子名字:pear
梨子分类名字:pear_cate

结论:

深拷贝

2.2.4 JSON Serialization With Jackson

故技重施与2.2.3类似,此略。

3、原理和讨论

引用传递和值传递,在java中经常会搞混,(java只有值传递)。因为会出现类似于这样的情况:

//我摘抄这位作者的代码:
//作者:Intopass
//链接:https://www.zhihu.com/question/31203609/answer/50992895
//来源:知乎
//著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

第一个例子:基本类型
void foo(int value) {
    value = 100;
}
foo(num); // num 没有被改变

第二个例子:没有提供改变自身方法的引用类型
void foo(String text) {
    text = "windows";
}
foo(str); // str 也没有被改变

第三个例子:提供了改变自身方法的引用类型
StringBuilder sb = new StringBuilder("iphone");
void foo(StringBuilder builder) {
    builder.append("4");
}
foo(sb); // sb 被改变了,变成了"iphone4"。

第四个例子:提供了改变自身方法的引用类型,但是不使用,而是使用赋值运算符。
StringBuilder sb = new StringBuilder("iphone");
void foo(StringBuilder builder) {
    builder = new StringBuilder("ipad");
}
foo(sb); // sb 没有被改变,还是 "iphone"。

实际上java对于命名、内存地址、是否有改变自身的方法(getter,setter,append等)都有考量。考虑到这三者,便可了解写的对象,其值是否有改变;

第一个,第二个例子:(没有改变自身的方法+作用域不同)

第三个例子:(改变自身的方法+作用域不同)

第四个例子:(new对象+作用域不同)

稍微整理一下,也挺好理解的

4、参考资料

[runoob][https://www.runoob.com/java/java-serialization.html]

[cnblog][https://www.cnblogs.com/fnlingnzb-learner/p/10649509.html]

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值