Java对象深拷贝、浅拷贝之枚举类型

枚举在Java中是不可变的,因此不存在深拷贝和浅拷贝的问题。枚举实例在初始化时创建且唯一,直接引用即可。文章通过一个学生类示例展示了深拷贝的实现,说明枚举类型的年级属性在拷贝后不会因原对象的修改而改变。

问题:为什么属于引用类型的enum不会有深拷贝浅拷贝的问题?

解释:

在Java中,枚举类型是一种特殊的类类型。每个枚举值都是该枚举类型的一个实例,并且这些实例在枚举类型被初始化时就已经被创建。这些实例在程序的整个生命周期中是不可变的。因此,枚举类型的每个值都只存在一个实例。当你引用一个枚举值时,你实际上是引用了这个不可变的实例。这就解释了为什么我们不需要对枚举进行深拷贝或浅拷贝。

深拷贝”和“浅拷贝”是针对可变对象的概念。浅拷贝只复制对象的引用,而不复制对象本身因此原对象和拷贝的对象实际上指向的是同一个对象。深拷贝则是创建一个新对象,并将原对象的所有字段一一复制到新对象中,因此原对象和拷贝的对象是完全独立的。

然而,由于枚举实例是不可变的,所以不存在需要创建新的实例(深拷贝》或者复制实例用(浅拷贝)的情况。你可以直接引用这个实例,而不用担心它会被改变。如果你尝试创建个枚举的新实例或者复制一个枚举实例的引用,你实际上还是会得到指向原来那个实例的引用。这就是为什么我们说在Java中,枚举没有深拷贝和浅拷贝的概念。

举例
测试代码

/**
 * 学生类
 */
 class Student implements Serializable {
    //是否毕业
    private boolean graduate;
    //名字
    private String name;
    //年级-枚举值
    private GradeType gradeType;
    //家庭地址
    private Address address;

    public Student(){};

    public Student(boolean graduate, GradeType gradeType, String name,Address address) {
        this.graduate = graduate;
        if (gradeType == null) {
            this.gradeType = GradeType.G1;
        } else {
            this.gradeType = gradeType;
        }
        this.name = name;
        this.address = address;
    }

    //重写了clone方法来完成Student对象的深拷贝
    @Override
    public Student clone(){
        Student student = new Student();
        student.setGraduate(this.graduate);
        student.setName(this.name);
        student.setGradeType(this.gradeType);
        student.setAddress(this.address);
        return student;
    }

    public boolean isGraduate() {
        return graduate;
    }

    public void setGraduate(boolean graduate) {
        this.graduate = graduate;
    }

    public String getName() {
        return name;
    }

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

    public GradeType getGradeType() {
        return gradeType;
    }

    public void setGradeType(GradeType gradeType) {
        this.gradeType = gradeType;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }
}

/**
 * 年级枚举
 */
 enum  GradeType {
    G1,//高一
    G2,//高二
    G3;//高三
}

/**
 * 地址类
 */
class Address implements Serializable {
    //省
    private String province;
    //市
    private String city;
    //区
    private String district;

    public Address(){};

    public Address(String province, String city, String district) {
        this.province = province;
        this.city = city;
        this.district = district;
    }

    public String getProvince() {
        return province;
    }

    public void setProvince(String province) {
        this.province = province;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getDistrict() {
        return district;
    }

    public void setDistrict(String district) {
        this.district = district;
    }
}

测试方法

	    public static void main(String[] args) {
        Address a1 = new Address("广东省","广州市","天河区");
        Student s1 = new Student(Boolean.FALSE,GradeType.G1,"zhangsan",a1);
        Student s2 = s1.clone();
        System.out.println("s1="+ JSONObject.toJSONString(s1));
        System.out.println("s2="+JSONObject.toJSONString(s2));
        System.out.println("-----------做修改---------");
        //将拷贝的学生2名称改成zhangsanfeng
        s2.setName("zhagnsanfeng");
        //将拷贝的学生2的年级改成G3
        s2.setGradeType(GradeType.G3);
        //将毕业状态改成已毕业
        s2.setGraduate(Boolean.TRUE);
        //将拷贝的学生2的地址改成白云区
        Address a2 = s2.getAddress();
        a2.setDistrict("白云区");
        System.out.println("s1="+JSONObject.toJSONString(s1));
        System.out.println("s2="+JSONObject.toJSONString(s2));
    }

结果
在这里插入图片描述
可以看到在修改之前,打印出来的学生s1和学生s2有着相同的人员信息,
在修改学生s2的名字、年级、毕业状态和家庭住址后,可以发现只有家庭住址的改变影响到了学生s1,修改学生s2后,学生s1的地址也从天河区变成了白云区,而同为引用类型的年级并没有因为s2的修改而改变。

<think> 我们正在讨论浅拷贝深拷贝的常用方法。根据用户提供的引用,我们可以总结如下: ### 浅拷贝常用方法: 1. **Object.assign()** (JavaScript): 用于将所有可枚举属性的值从一个或多个源对象复制到目标对象,并返回目标对象。执行的是浅拷贝。 示例: ```javascript const obj1 = { a: 1, b: { c: 2 } }; const obj2 = Object.assign({}, obj1); obj1.b.c = 3; console.log(obj2.b.c); // 输出3,因为b是引用类型,共享同一内存 ``` 2. **数组的slice()方法** (JavaScript): 返回一个新的数组对象,这一对象是一个由原数组的浅拷贝。 示例: ```javascript const arr1 = [1, 2, [3, 4]]; const arr2 = arr1.slice(); arr1[2][0] = 5; console.log(arr2[2][0]); // 输出5,因为内部数组是引用类型,共享同一内存 ``` 3. **数组的concat()方法** (JavaScript): 用于合并两个或多个数组,返回一个新数组,也是浅拷贝。 示例: ```javascript const arr1 = [1, 2, [3, 4]]; const arr2 = [].concat(arr1); arr1[2][0] = 5; console.log(arr2[2][0]); // 输出5 ``` 4. **展开运算符(...)** (JavaScript ES6): 可以在构造字面量对象或数组时展开属性,实现浅拷贝。 示例: ```javascript const obj1 = { a: 1, b: { c: 2 } }; const obj2 = { ...obj1 }; obj1.b.c = 3; console.log(obj2.b.c); // 输出3 const arr1 = [1, 2, [3, 4]]; const arr2 = [...arr1]; arr1[2][0] = 5; console.log(arr2[2][0]); // 输出5 ``` 5. **直接赋值** (多种语言): 在Java等语言中,直接赋值操作(如`Object obj2 = obj1;`)实际上是复制引用,因此也是浅拷贝(严格来说,它只是创建了一个新的引用指向同一个对象,但通常也归为浅拷贝的一种形式)。 ### 深拷贝常用方法: 1. **递归复制**: 手动实现递归函数,遍历对象的每个属性,如果是基本类型则直接复制,如果是引用类型则递归复制。 示例(JavaScript): ```javascript function deepCopy(obj) { if (typeof obj !== 'object' || obj === null) { return obj; } const newObj = Array.isArray(obj) ? [] : {}; for (let key in obj) { if (obj.hasOwnProperty(key)) { newObj[key] = deepCopy(obj[key]); } } return newObj; } ``` 2. **JSON.parse(JSON.stringify())** (JavaScript): 利用JSON的序列化和反序列化实现深拷贝。但这种方法有局限性: - 不能复制函数(函数会被忽略) - 不能复制`undefined`(会丢失) - 不能复制正则表达式(会变成空对象) - 不能复制日期对象(会变成字符串) - 循环引用会报错 示例: ```javascript const obj1 = { a: 1, b: { c: 2 } }; const obj2 = JSON.parse(JSON.stringify(obj1)); obj1.b.c = 3; console.log(obj2.b.c); // 输出2,说明是深拷贝 ``` 3. **使用第三方库**: 例如在JavaScript中,可以使用`lodash`库的`_.cloneDeep()`方法。 示例: ```javascript const _ = require('lodash'); const obj1 = { a: 1, b: { c: 2 } }; const obj2 = _.cloneDeep(obj1); ``` 4. **Java中的深拷贝实现**: - 实现`Cloneable`接口并重写`clone()`方法,在重写时需要递归调用引用类型属性的`clone()`方法。 示例: ```java class Address implements Cloneable { private String city; // 构造方法、getter/setter省略 @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } class Person implements Cloneable { private String name; private Address address; // 构造方法、getter/setter省略 @Override protected Object clone() throws CloneNotSupportedException { Person cloned = (Person) super.clone(); cloned.address = (Address) address.clone(); // 递归克隆引用类型 return cloned; } } ``` - 通过序列化实现:将对象写入流中再从流中读取,这样会创建一个全新的对象。 示例: ```java import java.io.*; public class DeepCopyUtil { public static <T extends Serializable> T deepCopy(T object) { try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos)) { oos.writeObject(object); try (ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis)) { return (T) ois.readObject(); } } catch (Exception e) { throw new RuntimeException(e); } } } ``` 5. **其他语言**: 在Python中可以使用标准库的`copy`模块: - 浅拷贝:`copy.copy()` - 深拷贝:`copy.deepcopy()` ### 注意事项: - **浅拷贝**只复制一层,适合对象结构简单或不需要嵌套复制的情况,性能较高。 - **深拷贝**复制整个对象结构,适合对象结构复杂且需要完全独立副本的情况,但性能较低,且需要注意循环引用的问题。 ### 总结表格: | 方法类型 | 常用实现方式 | 特点 | |----------|----------------------------------|----------------------------------------------------------------------| | 浅拷贝 | Object.assign() | 仅复制一层,嵌套对象共享引用 | | | 数组的slice()、concat() | 数组浅拷贝,嵌套数组共享引用 | | | 展开运算符(...) | 对象或数组的浅拷贝 | | | 直接赋值 | 复制引用,指向同一对象 | | 深拷贝 | JSON.parse(JSON.stringify()) | 简单但有限制(忽略函数、undefined等) | | | 递归复制 | 可自定义,但需处理循环引用和复杂类型 | | | 第三方库(如lodash.cloneDeep) | 功能强大,支持复杂类型和循环引用 | | | Java中Cloneable接口重写clone() | 需要递归调用引用类型的clone方法 | | | Java中的序列化方法 | 对象需实现Serializable接口,性能较低但通用 | | | Python的copy.deepcopy() | 标准库提供,支持大多数类型的深拷贝 | ### 引用说明: - 引用[1]提到JSON方法实现深拷贝的局限性(不能复制函数、正则等)[^1]。 - 引用[2]和[4]总结了浅拷贝常用方法(如Object.assign)[^2][^4]。 - 引用[3]解释了浅拷贝Java中的概念(仅复制基本类型和引用,共享引用指向的对象)[^3]。 ### 相关问题: §§相关问题§§ 1. 在JavaScript中,使用JSON.parse(JSON.stringify())进行深拷贝有哪些局限性?如何处理这些局限性? 2. 如何用递归实现一个支持循环引用的深拷贝函数? 3. 在Java中,使用序列化实现深拷贝有什么优缺点?适用于哪些场景? [^1]:深拷贝浅拷贝的几种方法。深拷贝最简单的方法: 最简单的方法就是JSON.parse(JSON.stringify()) function deepCopy(o) { return JSON.parse(JSON.stringify(o)) } var c = { age: 1, name: undefined, sex: null, tel: /^1[34578]\d{9}$/, say: () => { console.log('hahha') } } // { age: 1, sex: null, tel: {} } 需要注意的是:这种拷贝方法不可以拷贝一些特殊的属性(例如正则表达式,undefine,function) [^2]:深拷贝浅拷贝开发常用方法总结。在前端开发的工作中经常需要对数据进行处理,这时候会经常使用到浅拷贝深拷贝先拷贝原有数据再进行处理,以下就是常用的方法总结,我将以代码的形式来带大家去使用和理解这些方式 一、浅拷贝常用方法 1. Object.assign [^3]:java深拷贝浅拷贝浅拷贝是指仅拷贝对象的基本类型字段和引用类型字段的引用,而不是引用类型所指向的对象本身。因此,浅拷贝后的对象与原对象的引用类型字段共享相同的对象。如果原对象或拷贝对象中的引用类型被修改,两个对象都会受到影响。 实现方式 [^4]:深拷贝浅拷贝方法总结。 如果属性是基本类型,则为值传递,如果属性是引用类型则拷贝内存地址,即共用内存地址,改变任意一个则另一个内存地址指向也变化。 2.实现方法 Object.assign(target,…source)Array.prototype.slice()Array.prototype.concat() 2.1 Object.assign(target,…source) var obj1 = { name : 'ggg', age : 18, sex : '女', stu : { name :'mmm' } } var obj2 = Object.assign({},obj1) console.log(obj2) obj1.name = 'hhh'; obj2.stu.name = 'kkk'; console.log(obj1); console.log(obj2); </think> ### 常用的浅拷贝深拷贝方法总结 #### 一、浅拷贝常用方法(仅复制一层数据结构) 1. **Object.assign()** JavaScript 中最常用的浅拷贝方法,复制对象的可枚举属性 ```javascript const obj2 = Object.assign({}, obj1); ``` 特点: - 仅拷贝对象的第一层属性 - 嵌套对象仍共享内存地址 - 修改嵌套属性会影响原始对象[^4] 2. **数组的 slice() 方法** 创建数组的浅副本 ```javascript const newArr = originArr.slice(); ``` 3. **数组的 concat() 方法** 合并数组时创建新数组 ```javascript const newArr = [].concat(originArr); ``` 4. **展开运算符 (...)** ES6 提供的简洁语法 ```javascript const obj2 = { ...obj1 }; const arr2 = [...arr1]; ``` 5. **手动遍历赋值** 基础实现原理: ```javascript function shallowCopy(obj) { const newObj = {}; for (let key in obj) { if (obj.hasOwnProperty(key)) { newObj[key] = obj[key]; } } return newObj; } ``` #### 二、深拷贝常用方法(递归复制所有层级) 1. **JSON 序列化方法** 最简便但有限制的深拷贝: ```javascript const deepCopy = JSON.parse(JSON.stringify(obj)); ``` **局限性**: - 无法拷贝函数(丢失) - 无法拷贝正则表达式(变为空对象) - 无法拷贝 `undefined`(丢失) - 无法拷贝循环引用(报错)[^1] 2. **递归复制函数** 手动实现完整深拷贝: ```javascript function deepClone(obj) { if (typeof obj !== 'object' || obj === null) return obj; const result = Array.isArray(obj) ? [] : {}; for (let key in obj) { if (obj.hasOwnProperty(key)) { result[key] = deepClone(obj[key]); } } return result; } ``` 3. **第三方库实现** 推荐使用成熟工具库: ```javascript // lodash import _ from 'lodash'; const copy = _.cloneDeep(obj); // jQuery const copy = $.extend(true, {}, obj); ``` 4. **MessageChannel API** 利用浏览器 API 实现异步深拷贝: ```javascript function deepCopy(obj) { return new Promise(resolve => { const { port1, port2 } = new MessageChannel(); port2.onmessage = ev => resolve(ev.data); port1.postMessage(obj); }); } ``` 5. **结构化克隆算法** HTML5 新特性(适用于浏览器环境): ```javascript const copy = structuredClone(obj); ``` #### 三、各语言实现对比 | 语言 | 浅拷贝方法 | 深拷贝方法 | |-----------|-----------------------------------|-------------------------------------| | **JavaScript** | `Object.assign()`, `slice()`, `...` | `JSON.parse/stringify`, `lodash.cloneDeep` | | **Python** | `copy.copy()` | `copy.deepcopy()` | | **Java** | 实现`Cloneable`接口(默认浅拷贝) | 序列化/手动递归复制引用对象 | | **C++** | 默认拷贝构造函数 | 重写拷贝构造函数实现递归复制 | #### 四、选择建议 - **优先浅拷贝**:当对象无嵌套引用或性能敏感时使用 - **必须深拷贝**: - 对象包含嵌套可变数据结构 - 需要隔离修改的场景(如状态管理) - 涉及多线程/并发的环境 > **重要提示**:深拷贝的成本随对象复杂度指数级增长,对于大型对象需谨慎使用[^2][^3]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值