-
深克隆与浅克隆
2014-04-18 13:32:401)深克隆与浅克隆概念 首先在此做一点声明,本文所说的克隆和有的地方所说的拷贝及复制是一个概念,我比较喜欢叫克隆,下文当中全部叫“克隆”。 ⑴浅克隆 被克隆对象的所有变量都含有与原来的对象相同的值,而...1)深克隆与浅克隆概念
首先在此做一点声明,本文所说的克隆和有的地方所说的拷贝及复制是一个概念,我比较喜欢叫克隆,下文当中全部叫“克隆”。
⑴浅克隆
被克隆对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅克隆仅仅克隆所考虑的对象,而不克隆它所引用的对象。
⑵深克隆
被克隆对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被克隆过的新对象,而不再是原有的那些被引用的对象。换言之,深克隆把要克隆的对象所引用的对象都克隆了一遍。
如果你对这个概念不太清楚,没关系可以直接看下文的代码及图例
2)Java的clone()方法
⑴clone方法将对象克隆了一份并返回给调用者。一般而言,clone()方法满足:
①对任何的对象x,都有x.clone() !=x//克隆对象与原对象不是同一个对象
②对任何的对象x,都有x.clone().getClass()= =x.getClass()//克隆对象与原对象的类型一样
③如果对象x的equals()方法定义恰当,那么x.clone().equals(x)应该成立。
⑵Java中对象的克隆
①为了获取对象的一份克隆,我们可以利用Object类的clone()方法。
②在子类中覆盖基类的clone()方法,并声明为public。
③在子类的clone()方法中,调用super.clone()。
④在子类中实现Cloneable接口。
请看如下代码:
public class Student implements java.lang.Cloneable{
private String name;
private int age;
public Student(String name,int age){
this.name=name;
this.age=age;
}
//set 和get 方法
public void clone()throws CloneNotSupportedException{
super.clone();
}
}
public class Test{
public static void main(String[] args)throws
CloneNotsupportedException{
Student s1=new Student("zhangsan",30);
//直接用等号赋值,会发现指向的是同一个对象,所以s2 的age变了之后,s1 的
age 也发生了改变。
Student s2=s1;
s2.setAge(20);
Student s3=null;
System.out.println(s1.getAge());
//注意在这里是不能直接调用object 的clone 方法的
//s3=s1.clone();会报错,因为clone 是protected 修饰的
//在Student 类中添加了clone 方法时候可以调用了
s3=(Student)s1.clone();
//需要让Student 类实现java.lang.Cloneable 接口,要注意的是cloneable接
//口是标记接口,它只是标记该类对象可以被克隆,标记接口中没有方法。
//否则会抛出CloneNotSupportedException 异常
s3.setAge(40);
//注意观察下一句s1 的age 有没有发生变化
System.out.println(s1.getAge());
}
}
说明:继承自java.lang.Object类的clone()方法是浅克隆。以下代码可以证明。
class Teacher {
String name;
int age;
Teacher(String name, int age) {
this.name = name;
this.age = age;
}
}
class Student implements Cloneable {
String name;
int age;
Teacher t;// 学生1和学生2的引用值都是一样的。
Student(String name, int age, Teacher t) {
this.name = name;
this.age = age;
this.t = t;
}
public Object clone() {
Student stu = null;
try {
stu = (Student) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
public static void main(String[] args) {
Teacher t = new Teacher("lisi", 30);
Student s1 = new Student("zhangsan", 18, t);
Student s2 = (Student) s1.clone();
s2.t.name = "wangwu";
s2.t.age = 40;
System.out.println("name=" + s1.t.name + "," + "age=" + s1.t.age);
// 学生1的老师name为wangwu,age为40。
}
}
那应该如何实现深层次的克隆,即修改s2的老师不会影响s1的老师?代码改进如下。
class Teacher implements Cloneable {
String name;
int age;
Teacher(String name, int age) {
this.name = name;
this.age = age;
}
public Object clone() {
Object obj = null;
try {
obj = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return obj;
}
}
class Student implements Cloneable {
String name;
int age;
Teacher t;
Student(String name, int age, Teacher t) {
this.name = name;
this.age = age;
this.t = t;
}
public Object clone() {
Student stu = null;
try {
stu = (Student) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
stu.t = (Teacher) t.clone();
return stu;
}
public static void main(String[] args) {
Teacher t = new Teacher("lisi", 30);
Student s1 = new Student("zhangsan", 18, t);
Student s2 = (Student) s1.clone();
s2.t.name = "wangwu";
s2.t.age = 40;
System.out.println("name=" + s1.t.name + "," + "age=" + s1.t.age);
// 学生1的老师不改变。
}
}
下面我们通过一个简单的图例来说明深克隆与浅克隆是怎么一回事
如果像上面这样来实现有没有问题,大家可以思考一下,在业界都是用对象序列化来实现深克隆,下面我们就来看一下。
3)利用对象序列化来实现深克隆
把对象写到流里的过程是序列化(Serilization)过程,Java程序员又非常形象地称为“冷冻”或者“腌咸菜(picking)”过程;而把对象从流中读出来的反序列化(Deserialization)过程则叫做“解冻”或者“回鲜(depicking)”过程。应当指出的是,写在流里的是对象的一个克隆,而原对象仍然存在于JVM里面,因此“腌成咸菜”的只是对象的一个克隆,Java咸菜还可以回鲜。
在Java语言里深克隆一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的一个克隆)写到一个流里(腌成咸菜),再从流里读出来(把咸菜回鲜),便可以重建对象。
下面为深克隆源代码。
这样做的前提是对象以及对象内部所有引用到的对象都是可序列化的,否则,就需要仔细考察那些不可序列化的对象可否设成transient,从而将之排除在克隆过程之外。上例代码改进如下。
import java.io.*;
class Teacher implements Serializable {
String name;
int age;
Teacher(String name, int age) {
this.name = name;
this.age = age;
}
}
class Student implements Serializable {
String name;// 常量对象。
int age;
Teacher t;// 学生1和学生2的引用值都是一样的。
Student(String name, int age, Teacher t) {
this.name = name;
this.age = age;
this.t = t;
}
public Object deepClone() throws IOException, OptionalDataException,
ClassNotFoundException {
// 将对象写到流里
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream oo = new ObjectOutputStream(bo);
oo.writeObject(this);
// 从流里读出来
ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi = new ObjectInputStream(bi);
return (oi.readObject());
}
public static void main(String[] args) {
Teacher t = new Teacher("lisi", 30);
Student s1 = new Student("zhangsan", 18, t);
Student s2 = null;
try {
s2 = (Student) s1.deepClone();
} catch (OptionalDataException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
s2.t.name = "wangwu";
s2.t.age = 40;
System.out.println("name=" + s1.t.name + "," + "age=" + s1.t.age);
// 学生1的老师不改变。
}
}
-
前端-深克隆与浅克隆
2020-03-30 23:13:07深克隆与浅克隆前端-深克隆与浅克隆
区别:浅克隆只是原始数据的引用,在堆中与原数据仍然共用一块内存。
深克隆为新数据在堆中重新分配一块内存。
浅克隆影响原始数据,深克隆不影响原始数据。
重点:javascript中有两种数据类型,分别是基础数据类型和引用数据类型。基础数据类型是按值访问的,常见的基础数据类型有Number、String、Boolean、Null、Undefined,这类变量克隆的时候会完整的复制一份;引用数据类型比如Array、Object,在克隆的时候克隆的是数据的引用,当克隆数据发生变化的时候,原数据也跟着变化。
常见的几种克隆方法:
1、直接赋值(浅克隆)
let aaa = {a:1,b:2,c:3}; let bbb = aaa; bbb.a = '我被bbb改啦'; console.log(aaa); // {a: "我被bbb改啦", b: 2, c: 3}
2、Array.prototype.concat() 只针对最外层为数组(浅克隆)
let aaa = [{a:1,b:2,c:{a:3,b:4}}]; let bbb = aaa.concat(); // concat是连接数组,如果不传参,则表示复制原数组的数据到目标数组中 bbb[0].a = '我被bbb改啦'; console.log(aaa); // [{a:'我被bbb改啦',b:2,c:{a:3,b:4}}]
3、Array.prototype.slice() 只针对最外层为数组(浅克隆)
let aaa = [{a:1,b:2,c:{a:3,b:4}}]; let bbb = aaa.slice(); // slice是截取数组或者字符串,不传参表示默认全部 bbb[0].a = '我被bbb改啦'; console.log(aaa); // [{a:'我被bbb改啦',b:2,c:{a:3,b:4}}]
4、Object.assign()(可浅克隆也可深克隆)
// 一 原数据只有基础数据类型(深克隆) let aaa = {a:1,b:2,c:3}; let bbb = Object.assign({},aaa); bbb.a = '我被bbb改啦'; console.log(aaa); // {a: 1, b: 2, c: 3}
// 二 原数据有引用数据类型(浅克隆) let aaa = {a:1,b:2,c:{a:3,b:4}}; let bbb = Object.assign({},aaa); bbb.c.a = '我被bbb改啦'; console.log(aaa); // {a:1,b:2,c:{a:'我被bbb改啦',b:4}}
5、扩展运算符(...)(可浅克隆也可深克隆)
与Object.assign()克隆类似
//一 原数据只有基础数据类型 (深克隆) let aaa = {a:1,b:2,c:3}; let bbb = {...aaa}; // 扩展运算符(…)用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中 bbb.a = '我被bbb改啦' console.log(aaa);// {a:1,b:2,c:3}
// 二 原数据有引用数据类型(浅克隆) let aaa = {a:1,b:2,c:{a:3,b:4}}; let bbb = {...aaa}; bbb.c.a = '我被bbb改啦'; console.log(aaa); // {a:1,b:2,c:{a:'我被bbb改啦',b:4}}
6、JSON.parse(JSON.stringify())(深克隆)
let aaa = [{a:1,b:2,c:{a:3,b:4}}]; let bbb = JSON.parse(JSON.stringify(aaa)); bbb[0].a = '我被bbb改啦'; console.log(aaa); // [{a:1,b:2,c:{a:3,b:4}}]
-
深克隆 与 浅克隆
2013-02-28 14:37:39克隆是创建作为当前实例副本的新对象。 克隆分为深度克隆和浅度克隆 深度克隆:会克隆当前实例的所有所有成员. 浅度克隆:只会克隆当前实例的所有值类型的. 浅度克隆Object类为我们提供了一个受保护的...克隆是创建作为当前实例副本的新对象。
克隆分为深度克隆和浅度克隆
深度克隆:会克隆当前实例的所有所有成员.
浅度克隆:只会克隆当前实例的所有值类型的.
浅度克隆Object类为我们提供了一个受保护的克隆方法MemberwiseClone()深度克隆要我们自己实现
实现深度克隆的方式一般有2种...
我来创建2个类
[Serializable]//这个标记是表示可以序列化这个类
//Address类 有2个属性 Province 和City
public class Address
{private string _city;
public string City
{
get { return _city; }
set { _city = value; }
}
private string _province;
public string Province
{
get { return _province; }
set { _province = value; }
}
}
//Person 类有3个属性 Name 、Age、Address
[Serializable]
public class Person
{
private string _name;
private int _age;
private Address _address;
public Person(string name,int age,Address address)
{
_name = name;
_age = age;
_address = address;
}public Person()
我们现在来让Person实现克隆
在Person类中加入
public Person Clone()
{
MemoryStream ms = new MemoryStream();
bf.Serialize(ms, this);
ms.Seek(0, SeekOrigin.Begin);
return (Person)bf.Deserialize(ms);
}这个方法就可以实现克隆了
这个方法是利用序列化和反序列化来实现克隆 比较方便但是类必须用[Serializable]标记可以序列化
令一种方式:
public Person Clone()
{Person temp = new Person();
temp.Name = this.Name;
temp.Age = this.Age;
temp.Address.Province = this.Address.Province;
temp.Address.City = this.Address.City;
return temp;
}这种方式容易出错 当一个类的成员过多时容易出错。当你要修改类的成员时,这个克隆方法也要修改 。
建议用第序列化的方式来实现克隆。
public Person Clone()
{return this.MemberwiseClone();
}MemberwiseClone 方法创建一个浅表副本,具体来说就是创建一个新对象,然后将当前对象的非静态字段复制到该新对象。如果字段是值类型的,则对该字段执行逐位复制。如果字段是引用类型,则复制引用但不复制引用的对象;因此,原始对象及其复本引用同一对象。
这个第二种方法有点复杂,我觉的还有一种方法 来时实现:
就是讲A类中的B类属性来实现clone()接口,在他中间实现MeberWiseClone的方法,然后A类也来集成Clone()接口
在A类中 实现一个构造函数来,参数为B类,在这个构造函数中完成,B类的克隆
在A类中试下一个clone函数,进行构造赋值等。
毕竟说是说的不是很清楚,有时间附代码。
-
深克隆与浅克隆(深克隆的封装)
2019-02-21 10:11:27深克隆与浅克隆的概念与案例、误区,简洁明了浅克隆:克隆的时候没有创建新对象,还是共用原来的地址,克隆出来的对象跟着一起变化。
/*浅克隆*/ var arr=[1,2,3,4,5]; var arr2=arr; arr[0]=100; console.log(arr2); //[100, 2, 3, 4, 5]
深克隆:克隆的时候创建了一个新对象,不再共用地址,克隆出来的对象不跟着一起变化。
/*深克隆封装*/ function deepClone(origin,target){ var target = target || {}, toStr = Object.prototype.toString, arrType = '[object Array]'; for(var prop in origin){ if (origin.hasOwnProperty(prop)) { if (origin[prop] !== 'null' && typeof origin[prop] === 'object') { if (toStr.call(origin[prop]) == arrType) { target[prop] = []; }else{ target[prop] = {}; } deepClone(origin[prop],target[prop]); }else{ target[prop] = origin[prop]; } } } } var obj = { name : 'syy', age : 12, tabs : [1,2,3,4,5], hobbies : { a : 'a', b : [4,5,6,7], c : { d : 'd', e : 'e' } } } var obj1 = {}; deepClone(obj,obj1) obj.tabs[0]=100; obj.hobbies.c.d="wxy"; console.log(obj1); //obj1打印出来的值见下图
有些文章说到深拷贝时说,用slice、concat、扩展运算符也可实现,这是个很大的误区。写个简单的demo,乍一看,很有道理,这里边却有个不易察觉的误区:var a = [1,2,3]; var b = a.slice(0); var c = a.concat(); b.push(4); c.push(5); var d = [...a]; console.log(a); // [1,2,3] console.log(b); // [1,2,3,4] console.log(c); // [1,2,3,5] console.log(d); // [1,2,3]
看到上述打印结果,大家一定以为这么容易就能实现深克隆,上面深克隆的封装还巴拉巴拉写辣么多,装13的么?别急,且往下看,稍微变点形式,就能看出差别了:
var a = [1,2,3,[6,7]]; var b = a.slice(0); var c = a.concat(); b[3][0] = 100; c.push(5); var d = [...a]; console.log(a); // [1,2,3,[100,7]] console.log(b); // [1,2,3,[100,7]] console.log(c); // [1,2,3,[100,7],5] console.log(d); // [1,2,3,[100,7]]
可以看到,silce、concat、扩展运算符的写法,外面的引用不共用地址,深层的元素引用却还是共享一个地址。不知道这个小知识点,写项目的时候这个bug少说要花费一两个小时呦!
结论:slice、concat、扩展运算符仅适用于不包含引用对象的一维数组的深拷贝。 -
java深克隆与浅克隆
2019-09-08 17:35:11java深克隆与浅克隆 浅克隆:克隆对象与被克隆对象共用一个地址,克隆对象和被克隆对象的属性值会一起变化. 深克隆:地址不同,其中一个变化不会影响另一个,相当于一个新的对象. ... -
js深克隆与浅克隆
2020-12-24 17:22:47深克隆与浅克隆的区别 浅克隆不仅赋值了数据,还赋值了地址,操作第二级数据会改变原数据 深克隆赋值了数据,但内存地址不同,不会影响原有数据 定义一个obj 分别看看深浅克隆的效果 let obj = { a:100, b:[10,... -
js的深克隆与浅克隆
2020-08-20 21:06:38js的深克隆与浅克隆浅克隆深克隆手写实现深、浅克隆浅克隆浅克隆JSON.stringify(obj)的弊端 浅克隆 var o={ a:1, } var cloneObj=o; cloneObj.a=2; console.log(o.a);//2 浅克隆其实就是将o的地址复制一份给... -
深克隆与浅克隆的区别
2021-03-20 14:35:38这篇文章就来带你了解一下深克隆与浅克隆,想要了解深克隆与浅克隆,就要先了解一下数据是怎样存储的。 对于基本数据的存储是直接将数据存在栈中的,而1对于引用数据类型的存储是将地址存储在栈中,数据存储在堆中 ... -
java学习-深克隆与浅克隆
2020-09-14 14:47:45深克隆与浅克隆 详见代码: import lombok.AllArgsConstructor; import lombok.Data; /** * clone的要求 实现CloneAble接口 修改clone方法为public,然后覆写clone方法 * 分类:深克隆与浅克隆 * 浅克隆:实现... -
1-02 深克隆与浅克隆
2020-06-09 14:24:341-02 深克隆与浅克隆 what 定义 深拷贝与浅拷贝 jdk有个接口java.lang.Cloneable 这个接口是空接口,里面什么东西都没有 它的意思是实现了这个接口的类都是可以克隆的 真正实现了clone方法的是java.lang.Object父类... -
JS中的深克隆与浅克隆
2020-09-09 16:46:34JS中的深克隆与浅克隆 浅克隆是原始类型为值传递,对象类型仍为引用传递,与原数据公用一套内存地址,修改一处别的也会受到影响。 深克隆是所有元素或属性完全复制,与原对象完全脱离,也就是说所有对新对象的修改...