实例分析java对象中浅克隆和深克隆
作者:laozhang 发布时间:2023-01-07 07:48:20
引言:
在Object基类中,有一个方法叫clone,产生一个前期对象的克隆,克隆对象是原对象的拷贝,由于引用类型的存在,有深克隆和浅克隆之分,若克隆对象中存在引用类型的属性,深克隆会将此属性完全拷贝一份,而浅克隆仅仅是拷贝一份此属性的引用。首先看一下容易犯的几个小问题
clone方法是Object类的,并不是Cloneable接口的,Cloneable只是一个标记接口,标记接口是用用户标记实现该接口的类具有某种该接口标记的功能,常见的标记接口有三个:Serializable、Cloneable、RandomAccess,没有实现Cloneable接口,那么调用clone方法就会爆出CloneNotSupportedException异常。
Object类中的clone方法是protected修饰的,这就表明我们在子类中不重写此方法,就在子类外无法访问,因为这个protected权限是仅仅能在Object所在的包和子类能访问的,这也验证了子类重写父类方法权限修饰符可以变大但不能变小的说法。
protected native Object clone() throws CloneNotSupportedException;
重写clone方法,内部仅仅是调用了父类的clone方法,其实是为了扩大访问权限,当然你可以把protected改为public,以后再继承就不用重写了。当然只是浅克隆的clone函数,深克隆就需要修改了。
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
属性是String的情况,String也是一个类,那String引用类型吗?String的表现有的像基本类型,归根到底就是因为String不可改变,克隆之后俩个引用指向同一个String,但当修改其中的一个,改的不是String的值,却是新生成一个字符串,让被修改的引用指向新的字符串。外表看起来就像基本类型一样。
浅克隆:
浅克隆就是引用类型的属性无法完全复制,类User中包含成绩属性Mark,Mark是由Chinese和math等等组成的,浅克隆失败的例子
class Mark{
private int chinese;
private int math;
public Mark(int chinese, int math) {
this.chinese = chinese;
this.math = math;
}
public void setChinese(int chinese) {
this.chinese = chinese;
}
public void setMath(int math) {
this.math = math;
}
@Override
public String toString() {
return "Mark{" +
"chinese=" + chinese +
", math=" + math +
'}';
}
}
public class User implements Cloneable{
private String name;
private int age;
private Mark mark;
public User(String name, int age,Mark mark) {
this.name = name;
this.age = age;
this.mark = mark;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", mark=" + mark +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
Mark mark = new Mark(100,99);
User user = new User("user",22,mark);
User userClone = (User) user.clone();
System.out.println("原user:"+user);
System.out.println("克隆的user:"+userClone);
//修改引用类型的mark属性
user.mark.setMath(60);
System.out.println("修改后的原user:"+user);
System.out.println("修改后的克隆user:"+userClone);
}
}
输出结果为:
原user:User{name='user', age=22, mark=Mark{chinese=100, math=99}}
克隆的user:User{name='user', age=22, mark=Mark{chinese=100, math=99}}
修改后的原user:User{name='user', age=22, mark=Mark{chinese=100, math=60}}
修改后的克隆user:User{name='user', age=22, mark=Mark{chinese=100, math=60}}
很清楚的看到user的mark更改后,被克隆的user也修改了。而要想不被影响,就需要深克隆了。
深克隆:
方式一:clone函数的嵌套调用
既然引用类型无法被完全克隆,那将引用类型也实现Cloneable接口重写clone方法,在User类中的clone方法调用属性的克隆方法,也就是方法的嵌套调用
class Mark implements Cloneable{
private int chinese;
private int math;
public Mark(int chinese, int math) {
this.chinese = chinese;
this.math = math;
}
public void setChinese(int chinese) {
this.chinese = chinese;
}
public void setMath(int math) {
this.math = math;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Mark{" +
"chinese=" + chinese +
", math=" + math +
'}';
}
}
public class User implements Cloneable{
private String name;
private int age;
private Mark mark;
public User(String name, int age,Mark mark) {
this.name = name;
this.age = age;
this.mark = mark;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", mark=" + mark +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
User user = (User) super.clone();
user.mark = (Mark) this.mark.clone();
return user;
}
public static void main(String[] args) throws CloneNotSupportedException {
Mark mark = new Mark(100,99);
User user = new User("user",22,mark);
User userClone = (User) user.clone();
System.out.println("原user:"+user);
System.out.println("克隆的user:"+userClone);
//修改引用类型的mark属性
user.mark.setMath(60);
System.out.println("修改后的原user:"+user);
System.out.println("修改后的克隆user:"+userClone);
}
}
输出结果为:
原user:User{name='user', age=22, mark=Mark{chinese=100, math=99}}
克隆的user:User{name='user', age=22, mark=Mark{chinese=100, math=99}}
修改后的原user:User{name='user', age=22, mark=Mark{chinese=100, math=60}}
修改后的克隆user:User{name='user', age=22, mark=Mark{chinese=100, math=99}}
方式二:序列化
上一种方法已经足够满足我们的需要,但是如果类之间的关系很多,或者是有的属性是数组呢,数组可无法实现Cloneable接口(我们可以在clone方法中手动复制数组),但是每次都得手写clone方法,很麻烦,而序列化方式只需要给每个类都实现一个Serializable接口,也是标记接口,最后同序列化和反序列化操作达到克隆的目的(包括数组的复制)。序列化和反序列化的知识请参照下一篇
import java.io.*;
class Mark implements Serializable {
private int chinese;
private int math;
public Mark(int chinese, int math) {
this.chinese = chinese;
this.math = math;
}
public void setChinese(int chinese) {
this.chinese = chinese;
}
public void setMath(int math) {
this.math = math;
}
@Override
public String toString() {
return "Mark{" +
"chinese=" + chinese +
", math=" + math +
'}';
}
}
public class User implements Serializable{
private String name;
private int age;
private Mark mark;
public User(String name, int age,Mark mark) {
this.name = name;
this.age = age;
this.mark = mark;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", mark=" + mark +
'}';
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
Mark mark = new Mark(100,99);
User user = new User("user",22,mark);
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream oo = new ObjectOutputStream(bo);
oo.writeObject(user);//序列化
ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi = new ObjectInputStream(bi);
User userClone = (User) oi.readObject();//反序列化
System.out.println("原user:"+user);
System.out.println("克隆的user:"+userClone);
user.mark.setMath(59);
System.out.println("修改后的原user:"+user);
System.out.println("修改后的克隆user:"+userClone);
}
}
输出结果:
原user:User{name='user', age=22, mark=Mark{chinese=100, math=99}}
克隆的user:User{name='user', age=22, mark=Mark{chinese=100, math=99}}
修改后的原user:User{name='user', age=22, mark=Mark{chinese=100, math=60}}
修改后的克隆user:User{name='user', age=22, mark=Mark{chinese=100, math=99}}
带数组属性的克隆
import java.io.*;
import java.util.Arrays;
public class User implements Serializable{
private String name;
private int age;
private int[] arr;
public User(String name, int age, int[] arr) {
this.name = name;
this.age = age;
this.arr = arr;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", arr=" + Arrays.toString(arr) +
'}';
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
int[] arr = {1,2,3,4,5,6};
User user = new User("user",22,arr);
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream oo = new ObjectOutputStream(bo);
oo.writeObject(user);//序列化
ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi = new ObjectInputStream(bi);
User userClone = (User) oi.readObject();//反序列化
System.out.println("原user:"+user);
System.out.println("克隆的user:"+userClone);
user.arr[1] = 9;
System.out.println("修改后的原user:"+user);
System.out.println("修改后的克隆user:"+userClone);
}
}


猜你喜欢
- 本文实例为大家分享了Android7.0 MTK设置默认桌面的具体代码,供大家参考,具体内容如下项目需求:客户安装自己公司的桌面apk,安装
- 1.申请微信公众号我们要进行微信公众平台的开发,第一步当然是要有公众号了。什么?不知道什么是微信公众号,看来你还要先回炉炼炼了,呵呵。通俗的
- Mybatis-plus全局id生成策略在配置文件中加入以下代码后就不需要在实体类种的id上添加@TableId(value = “id”,
- 一. 概述参考开源项目https://github.com/xkcoding/spring-boot-demo在系统运维中, 有时候为了避免
- Springboot 整合 RocketMQ 收发消息创建springboot项目pom.xml添加rocketmq-spring-boot
- 前言打包桌面应用程序实在是一个不常使用的东西,偶尔使用起来经常会忘东忘西的耽误时间,因此,这篇文章多以图片记录过程,也是用于备忘。下载打包工
- C语言/C++怎样产生随机数:这里要用到的是rand()函数, srand()函数,和time()函数。需要说明的是,iostream头文件
- 关于Redis的概念和应用本文就不再详解了,说一下怎么在java应用中设置过期时间。在应用中我们会需要使用redis设置过期时间,比如单点登
- java 中ArrayList与LinkedList性能比较今天看一框架的代码,看到有些 可以使用ArrayList的地方 使用的是 Lin
- 一、 ASCII码我们知道,在计算机内部,所有的信息最终都表示为一个二进制的字符串。每一个二进制位(bit)有0和1两种状态,因此八个二进制
- 开始用springboot2+hession4实现RPC服务时,发现第一个服务可以调用成功,但第二个就一直报'<'is
- 最近这段时间一直在看Android,利用Listview去实现点赞功能,下面给大家介绍下基本思路。基本思路:进入界面–》获取数据–》 在Li
- (1)不需要传递参数,也不需要返回参数ThreadStart是一个委托,这个委托的定义为void ThreadStart(),没有参数与返回
- 面试官经常喜欢问Spring中的bean是不是线程安全的这个问题用来考察对Spring 中Bean作用域的理解,先说结论,Spr
- List集合相信大家在开发过程中几乎都会用到。有时候难免会遇到集合里的数据是重复的,需要进行去除。然而,去重方式有好几种方式,你用的是哪种方
- public class ReadBitmap { public void readByte(Context c, String name,
- 如何将ResultSet结果集遍历到List中今天在使用jstl标签展示查询结果时遇到一个小问题,即如何将ResultSet对象传递给前台页
- 缘起工作时使用java开发服务器后台,用Jersey写Restful接口,发现有一个Post方法始终获取不到参数,查了半天,发现时获取参数的
- 本文实例讲述了C#装饰者模式。分享给大家供大家参考。具体方法如下:using System;using System.Collections
- 本文实例为大家分享了Android实现圆圈倒计时的具体代码,供大家参考,具体内容如下1. 显示效果如下2. 首先是创建shape的xml文件