JAVA中的深拷贝和浅拷贝心得

一 先给出概念定义

浅拷贝:只复制对象的引用,而不是对象本身。也就是说,新引用和原引用指向同一个对象,对其中一个引用所指向对象的修改会影响到另一个引用。
深拷贝:会创建一个新的对象,并且递归地复制原对象的所有属性,新对象和原对象相互独立,对其中一个对象的修改不会影响另一个对象。

二 实际编程中发现的问题

2.1 错误的惯性思维

        基本数据类型不受浅拷贝影响,引用数据类型受浅拷贝印象.

        错误,错误!  大声把概念定义读三遍!

2.2 例子

public class hello01 {
	public static void main(String[] args) {
		AliOssProperties aliOssProperties = new AliOssProperties();
		aliOssProperties.setEndpoint("endpoint");
		aliOssProperties.setAccessKeyId("accessKeyId");
		aliOssProperties.setAccessKeySecret("accessKeySecret");
		aliOssProperties.setBucketName("bucketName");
		List<String> list01 = new ArrayList<>();
		list01.add("111");
		aliOssProperties.setObject(list01);
		AliOssUtil aliOssUtil = new AliOssUtil();
		aliOssUtil.setEndpoint(aliOssProperties.getEndpoint());
		aliOssUtil.setAccessKeyId(aliOssProperties.getAccessKeyId());
		aliOssUtil.setAccessKeySecret(aliOssProperties.getAccessKeySecret());
		aliOssUtil.setBucketName(aliOssProperties.getBucketName());
		aliOssUtil.setObject(aliOssProperties.getObject());
		AliOssUtil aliOssUtil2 = new AliOssUtil();
		BeanUtils.copyProperties(aliOssProperties,aliOssUtil2);
		System.out.println("aliOssProperties= "+aliOssProperties);
		System.out.println("aliOssUtil= "+aliOssUtil);
		System.out.println("aliOssUtil2= "+aliOssUtil2);
		aliOssProperties.setEndpoint("xxxx");
		List<String> list02 = new ArrayList<>();
		list02.add("222");
		aliOssProperties.setObject(list02);
		System.out.println("aliOssProperties= "+aliOssProperties);
		System.out.println("aliOssUtil= "+aliOssUtil);
		System.out.println("aliOssUtil2= "+aliOssUtil2);
		list01.set(0,"333");
		System.out.println("aliOssProperties= "+aliOssProperties);
		System.out.println("aliOssUtil= "+aliOssUtil);
		System.out.println("aliOssUtil2= "+aliOssUtil2);
	}
}

输出结果

aliOssProperties= AliOssProperties(endpoint=endpoint, accessKeyId=accessKeyId, accessKeySecret=accessKeySecret, bucketName=bucketName, object=[111])
aliOssUtil= AliOssUtil(endpoint=endpoint, accessKeyId=accessKeyId, accessKeySecret=accessKeySecret, bucketName=bucketName, object=[111])
aliOssUtil2= AliOssUtil(endpoint=endpoint, accessKeyId=accessKeyId, accessKeySecret=accessKeySecret, bucketName=bucketName, object=[111])
aliOssProperties= AliOssProperties(endpoint=xxxx, accessKeyId=accessKeyId, accessKeySecret=accessKeySecret, bucketName=bucketName, object=[222])
aliOssUtil= AliOssUtil(endpoint=endpoint, accessKeyId=accessKeyId, accessKeySecret=accessKeySecret, bucketName=bucketName, object=[111])
aliOssUtil2= AliOssUtil(endpoint=endpoint, accessKeyId=accessKeyId, accessKeySecret=accessKeySecret, bucketName=bucketName, object=[111])
aliOssProperties= AliOssProperties(endpoint=xxxx, accessKeyId=accessKeyId, accessKeySecret=accessKeySecret, bucketName=bucketName, object=[222])
aliOssUtil= AliOssUtil(endpoint=endpoint, accessKeyId=accessKeyId, accessKeySecret=accessKeySecret, bucketName=bucketName, object=[333])
aliOssUtil2= AliOssUtil(endpoint=endpoint, accessKeyId=accessKeyId, accessKeySecret=accessKeySecret, bucketName=bucketName, object=[333])

2.3 观察

观察输出的第四,第五,第六行,发现aliOssUtil.endpoint和aliOssUtil2.endpoint的值都没有改变.

2.4 结论1-不可变类型不受浅拷贝影响

        BeanUtils.copyProperties(aliOssProperties, aliOssUtil2):此方法将 aliOssProperties 对象的属性值复制到 aliOssUtil2 对象。endpoint 是字符串类型,属于引用数据类型,不过字符串在 Java 中是不可变对象。在复制时,会把 aliOssProperties 中 endpoint 属性的值 "endpoint" 复制给 aliOssUtil2 的 endpoint 属性。
        aliOssProperties.setEndpoint("xxxx"):这行代码将 aliOssProperties 对象的 endpoint 属性值修改为 "xxxx"。但由于 aliOssUtil2 的 endpoint 属性在之前复制操作后已经有了自己的字符串对象,和 aliOssProperties 的 endpoint 属性指向不同的字符串对象,所以 aliOssUtil2 的 endpoint 属性值不会改变。

        不可变类型有String,基本数据类型的包装类,BigDecimal 和 BigInteger, Java 8 引入了 java.time 包,其中的类大多是不可变的,Enum 类型,UUID,自定义不可变类型.

2.5 结论2-引用数据类型有时候,也可以不受浅拷贝影响

        如上代码中

第一次  aliOssProperties.setObject(list01);

第二次  aliOssProperties.setObject(list02);

        因为aliOssProperties.object指向的对象不同了,所以结果中的第五,第六行中aliOssUtil.object和aliOssUtil2.object的值都没有改变.

2.6 结论3 -BeanUtils.copyProperties()和setter方法都是浅拷贝

        代码list01.set(0,"333");执行后,观察结果第八,第九行,发现aliOssUtil.object和aliOssUtil2.object的值都改变了,所以说BeanUtils.copyProperties()和setter方法都是浅拷贝.

三 实现深拷贝操作

3.1 深拷贝实现原理

        创建一个新的对象,对于不可变类型的属性,直接同名赋值;对于其他引用类型属性必须递归地赋值,,新对象和原对象相互独立,对其中一个对象的修改不会影响另一个对象。

        关键在于递归.

3.2 手动实现深拷贝

import java.util.ArrayList;
import java.util.List;

public class Person {
    private String name;
    private List<String> hobbies;

    public Person(String name, List<String> hobbies) {
        this.name = name;
        this.hobbies = hobbies;
    }

    // 深拷贝方法
    public Person deepCopy() {
        // 复制字符串字段(String是不可变对象,直接复制引用即可)
        String newName = this.name;

        // 复制列表字段(需要创建新的列表并复制内容)
        List<String> newHobbies = new ArrayList<>();
        for (String hobby : this.hobbies) {
            newHobbies.add(hobby); // String是不可变对象,直接添加引用
        }

        return new Person(newName, newHobbies);
    }

    // Getter和Setter方法
    public String getName() {
        return name;
    }

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

    public List<String> getHobbies() {
        return hobbies;
    }

    public void setHobbies(List<String> hobbies) {
        this.hobbies = hobbies;
    }

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

    public static void main(String[] args) {
        List<String> hobbies = new ArrayList<>();
        hobbies.add("Reading");
        hobbies.add("Traveling");

        Person original = new Person("Alice", hobbies);

        // 创建深拷贝副本
        Person copy = original.deepCopy();

        // 修改原始对象中的字段
        original.setName("Bob");
        original.getHobbies().set(0, "Gaming");

        // 输出结果
        System.out.println("Original: " + original);
        System.out.println("Copy: " + copy);
    }
}

输出结果

Original: Person{name='Bob', hobbies=[Gaming, Traveling]}
Copy: Person{name='Alice', hobbies=[Reading, Traveling]}

3.3 借助clone()方法

        默认地clone()方法会产生一个新对象,其属性值是浅拷贝.

         自定义的POJO类型,实现implements Cloneable,重写clone()实现深拷贝,从而保证后续代码中可以调用该clone(),实现递归地创建新对象而其属性值是深拷贝得到地.

import java.util.ArrayList;
import java.util.List;

// 定义一个内部类,表示嵌套的引用类型
class InnerClass implements Cloneable {
    private String innerData;

    public InnerClass(String innerData) {
        this.innerData = innerData;
    }

    public String getInnerData() {
        return innerData;
    }

    public void setInnerData(String innerData) {
        this.innerData = innerData;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        // 调用父类的 clone 方法创建新对象
        return super.clone();
    }

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

// 定义主类,包含引用类型的属性
class DeepCopyClass implements Cloneable {
    private String data;
    private InnerClass innerObject;
    private List<InnerClass> innerList;

    public DeepCopyClass(String data, InnerClass innerObject, List<InnerClass> innerList) {
        this.data = data;
        this.innerObject = innerObject;
        this.innerList = innerList;
    }

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }

    public InnerClass getInnerObject() {
        return innerObject;
    }

    public void setInnerObject(InnerClass innerObject) {
        this.innerObject = innerObject;
    }

    public List<InnerClass> getInnerList() {
        return innerList;
    }

    public void setInnerList(List<InnerClass> innerList) {
        this.innerList = innerList;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        // 调用父类的 clone 方法创建新对象
        DeepCopyClass clone = (DeepCopyClass) super.clone();
        // 对引用类型的属性进行深拷贝
        if (this.innerObject != null) {
            clone.innerObject = (InnerClass) this.innerObject.clone();
        }
        if (this.innerList != null) {
            clone.innerList = new ArrayList<>();
            for (InnerClass item : this.innerList) {
                clone.innerList.add((InnerClass) item.clone());
            }
        }
        return clone;
    }

    @Override
    public String toString() {
        return "DeepCopyClass{" +
                "data='" + data + '\'' +
                ", innerObject=" + innerObject +
                ", innerList=" + innerList +
                '}';
    }
}

public class DeepCopyExample {
    public static void main(String[] args) throws CloneNotSupportedException {
        InnerClass innerObject = new InnerClass("Inner Data");
        List<InnerClass> innerList = new ArrayList<>();
        innerList.add(new InnerClass("List Item 1"));
        innerList.add(new InnerClass("List Item 2"));

        DeepCopyClass original = new DeepCopyClass("Main Data", innerObject, innerList);
        // 进行深拷贝
        DeepCopyClass clone = (DeepCopyClass) original.clone();

        // 修改原始对象的属性
        original.getData();
        original.getInnerObject().setInnerData("Modified Inner Data");
        original.getInnerList().get(0).setInnerData("Modified List Item 1");

        // 输出原始对象和克隆对象
        System.out.println("Original: " + original);
        System.out.println("Clone: " + clone);
    }
}

输出结果

Original: DeepCopyClass{data='Main Data', innerObject=InnerClass{innerData='Modified Inner Data'}, innerList=[InnerClass{innerData='Modified List Item 1'}, InnerClass{innerData='List Item 2'}]}
Clone: DeepCopyClass{data='Main Data', innerObject=InnerClass{innerData='Inner Data'}, innerList=[InnerClass{innerData='List Item 1'}, InnerClass{innerData='List Item 2'}]}

3.4 借助序列化

        适用于对象及其成员变量都实现了 Serializable 接口的情况,但性能相对较低。

        不说了,因为不用.

.

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值