-
2020-01-14 15:45:08
Use
public class BookChapter { public string book_id; public string book_name; public string chapter_num; public string chapter_title; public string chapter_Url; public string chapter_content; public BookChapter(BookChapter bc) { bc.CopyPublicFieldTo(this); } }
实现
/// <summary> /// 反射实现类的对象之间相同属性值的复制, s => d /// </summary> /// <typeparam name="D"></typeparam> /// <typeparam name="S"></typeparam> /// <param name="s"></param> /// <returns></returns> public static void CopyPropertiesTo<D>(this object s, D d) { try { var Types = s.GetType();//获得类型 var Typed = d.GetType(); foreach (PropertyInfo sp in Types.GetProperties())//获得类型的属性字段 { foreach (PropertyInfo dp in Typed.GetProperties()) { if (dp.Name == sp.Name)//判断属性名是否相同 { dp.SetValue(d, sp.GetValue(s, null), null);//获得s对象属性的值复制给d对象的属性 } } } } catch (Exception e) { throw e; } } /// <summary> /// 反射实现类的对象拷贝相同字段值的复制, s => d /// </summary> /// <typeparam name="D"></typeparam> /// <param name="s">源头</param> /// <param name="d">目标</param> public static void CopyPublicFieldTo<D>(this object s,D d) { try { var Types = s.GetType();//获得类型 var Typed = d.GetType(); foreach (var sp in Types.GetFields(BindingFlags.Public | BindingFlags.Instance))//获得类型的属性字段 { foreach (var dp in Typed.GetFields(BindingFlags.Public | BindingFlags.Instance)) { if (dp.Name == sp.Name)//判断名是否相同 { dp.SetValue(d, sp.GetValue(s));//获得s对象属性的值复制给d对象的属性 } } } } catch (Exception e) { throw e; } }
更多相关内容 -
对象克隆、复制工具
2018-07-31 16:25:37对象复制工具,基于cglib BeanCopier 实现对实体对象、持久化对象、代理对象的克隆和复制, 避免重复克隆和复制,避免无限循环引用,(校验hashCode) 可指定实体对象和集合属性的克隆深度 -
【面试篇】 Java对象拷贝(对象克隆 对象复制)
2020-12-05 20:37:28Java对象拷贝(对象克隆 对象复制) 1.前言 假如说你想复制一个简单变量。很简单: int apples = 5; int pears = apples; 不仅仅是int类型,其它七种原始数据类型(boolean,char,byte,short,float,double.long)同样...Java对象拷贝(对象克隆 对象复制)
1.前言
假如说你想复制一个简单变量。很简单:
int apples = 5; int pears = apples;
不仅仅是int类型,其它七种原始数据类型(boolean,char,byte,short,float,double.long)同样适用于该类情况。
但是如果你复制的是一个对象,情况就有些复杂了。
但假如是个新手,可能会这样写。
class Student { private int number; public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } } public class Test { public static void main(String args[]) { Student stu1 = new Student(); stu1.setNumber(12345); Student stu2 = stu1; System.out.println("学生1:" + stu1.getNumber()); System.out.println("学生2:" + stu2.getNumber()); } }
结果:
学生1:12345
学生2:12345
但我们试着改变stu2实例的number字段,再打印结果看看:
stu2.setNumber(54321); System.out.println("学生1:" + stu1.getNumber()); System.out.println("学生2:" + stu2.getNumber());
结果:
学生1:54321
学生2:54321
这就怪了,为什么改变学生2的学号,学生1的学号也发生了变化呢?
原因出在(stu2 = stu1) 这一句。该语句的作用是将stu1的引用赋值给stu2,
这样,stu1和stu2指向内存堆中同一个对象。如图:
2.为什么要克隆
为什么需要克隆对象呢?直接new一个对象可以吗?
克隆的对象可能包含一些已经修改过的属性,而new出来的对象的属性都还是初始化时候的值,所以当需要一个新的对象来保存当前的“状态”就靠克隆方法。
而我们常见的Object a=new Object();Object b;b=a;这种形式的代码复制的是引用,即对象在内存中的地址,a和b对象仍然指向了同一个对象。
3.如何实现对象克隆
介绍两种不同的克隆方法:浅克隆和深克隆。
(1)浅克隆
-
被复制的类需要实现Clonenable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常), 该接口为标记接口(不含任何方法)
-
覆盖clone()方法,访问修饰符设为public。方法中调用super.clone()方法得到需要的复制对象。(native为本地方法)
下面对上面那个方法进行改造:
class Student implements Cloneable{ private int number; public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } @Override public Object clone() { Student stu = null; try{ stu = (Student)super.clone(); }catch(CloneNotSupportedException e) { e.printStackTrace(); } return stu; } } public class Test { public static void main(String args[]) { Student stu1 = new Student(); stu1.setNumber(12345); Student stu2 = (Student)stu1.clone(); System.out.println("学生1:" + stu1.getNumber()); System.out.println("学生2:" + stu2.getNumber()); stu2.setNumber(54321); System.out.println("学生1:" + stu1.getNumber()); System.out.println("学生2:" + stu2.getNumber()); } }
结果:
学生1:12345
学生2:12345
学生1:12345
学生2:54321
上面的复制被称为浅克隆。
(2)深克隆
还在学生类里再加一个Address类。
class Address { private String add; public String getAdd() { return add; } public void setAdd(String add) { this.add = add; } } class Student implements Cloneable{ private int number; private Address addr; public Address getAddr() { return addr; } public void setAddr(Address addr) { this.addr = addr; } public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } @Override public Object clone() { Student stu = null; try{ stu = (Student)super.clone(); }catch(CloneNotSupportedException e) { e.printStackTrace(); } return stu; } } public class Test { public static void main(String args[]) { Address addr = new Address(); addr.setAdd("杭州市"); Student stu1 = new Student(); stu1.setNumber(123); stu1.setAddr(addr); Student stu2 = (Student)stu1.clone(); System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd()); System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd()); } }
结果:
学生1:123,地址:杭州市
学生2:123,地址:杭州市
乍一看没什么问题,真的是这样吗?
我们在main方法中试着改变addr实例的地址。
addr.setAdd("西湖区"); System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd()); System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
结果:
学生1:123,地址:杭州市 学生2:123,地址:杭州市 学生1:123,地址:西湖区 学生2:123,地址:西湖区
这就奇怪了,怎么两个学生的地址都改变了?
原因是浅复制只是复制了addr变量的引用,并没有真正的开辟另一块空间,将值复制后再将引用返回给新对象。
所以,为了达到真正的复制对象,而不是纯粹引用复制。我们需要将Address类可复制化,并且修改clone方法,完整代码如下:
package abc; class Address implements Cloneable { private String add; public String getAdd() { return add; } public void setAdd(String add) { this.add = add; } @Override public Object clone() { Address addr = null; try{ addr = (Address)super.clone(); }catch(CloneNotSupportedException e) { e.printStackTrace(); } return addr; } } class Student implements Cloneable{ private int number; private Address addr; public Address getAddr() { return addr; } public void setAddr(Address addr) { this.addr = addr; } public int getNumber() { return number; } public void setNumber(int number) { this.number = number; } @Override public Object clone() { Student stu = null; try{ stu = (Student)super.clone(); //浅复制 }catch(CloneNotSupportedException e) { e.printStackTrace(); } stu.addr = (Address)addr.clone(); //深度复制 return stu; } } public class Test { public static void main(String args[]) { Address addr = new Address(); addr.setAdd("杭州市"); Student stu1 = new Student(); stu1.setNumber(123); stu1.setAddr(addr); Student stu2 = (Student)stu1.clone(); System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd()); System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd()); addr.setAdd("西湖区"); System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd()); System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd()); } }
结果:
学生1:123,地址:杭州市 学生2:123,地址:杭州市 学生1:123,地址:西湖区 学生2:123,地址:杭州市
这样结果就符合我们的想法了。
(3)浅克隆和深克隆
浅克隆
在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。
简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。
在Java语言中,通过覆盖Object类的clone()方法可以实现浅克隆。
深克隆
在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。
简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。
在Java语言中,如果需要实现深克隆,可以通过**覆盖Object类的clone()方法实现,也**可以通过序列化(Serialization)等方式来实现。
Java语言提供的Cloneable接口和Serializable接口的代码非常简单,它们都是空接口,这种空接口也称为标识接口,标识接口中没有任何方法的定义,其作用是告诉JRE这些接口的实现类是否具有某个功能,如是否支持克隆、是否支持序列化等。
(4)解决多层克隆问题
如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。
Inner也必须实现Serializable,否则无法序列化:
public class Inner implements Serializable{ private static final long serialVersionUID = 872390113109L; //最好是显式声明ID public String name = ""; public Inner(String name) { this.name = name; } @Override public String toString() { return "Inner的name值为:" + name; } }
public class Outer implements Serializable{ private static final long serialVersionUID = 369285298572941L; //最好是显式声明ID public Inner inner; //Discription:[深度复制方法,需要对象及对象所有的对象属性都实现序列化] public Outer myclone() { Outer outer = null; try { // 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝 ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(this); // 将流序列化成对象 ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); outer = (Outer) ois.readObject(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return outer; } }
-
-
java对象复制克隆
2013-10-28 08:52:09深度复制Java对象实例,复制后对象属性值改变不影响被复制对象,有注释 -
Java基础 - 对象克隆(复制)
2021-03-16 11:58:53假如说你想复制一个简单变量。很简单:int apples = 5;int pears = apples;...但是如果你复制的是一个对象,情况就有些复杂了。假设说我是一个beginner,我会这样写:classStudent {private intnumbe...假如说你想复制一个简单变量。很简单:
int apples = 5;int pears = apples;
不仅仅是int类型,其它七种原始数据类型(boolean,char,byte,short,float,double.long)同样适用于该类情况。
但是如果你复制的是一个对象,情况就有些复杂了。
假设说我是一个beginner,我会这样写:
classStudent {private intnumber;public intgetNumber() {returnnumber;
}public void setNumber(intnumber) {this.number =number;
}
}public classTest {public static voidmain(String args[]) {
Student stu1= newStudent();
stu1.setNumber(12345);
Student stu2=stu1;
System.out.println("学生1:" +stu1.getNumber());
System.out.println("学生2:" +stu2.getNumber());
}
}
结果:
学生1:12345
学生2:12345
这里我们自定义了一个学生类,该类只有一个number字段。
我们新建了一个学生实例,然后将该值赋值给stu2实例。(Student stu2 = stu1;)
再看看打印结果,作为一个新手,拍了拍胸腹,对象复制不过如此,
难道真的是这样吗?
我们试着改变stu2实例的number字段,再打印结果看看:
stu2.setNumber(54321);
System.out.println("学生1:" +stu1.getNumber());
System.out.println("学生2:" + stu2.getNumber());
结果:
学生1:54321
学生2:54321
这就怪了,为什么改变学生2的学号,学生1的学号也发生了变化呢?
原因出在(stu2 = stu1) 这一句。该语句的作用是将stu1的引用赋值给stu2,
这样,stu1和stu2指向内存堆中同一个对象。如图:
那么,怎样才能达到复制一个对象呢?
是否记得万类之王Object。它有11个方法,有两个protected的方法,其中一个为clone方法。
在Java中所有的类都是缺省的继承自Java语言包中的Object类的,查看它的源码,你可以把你的JDK目录下的src.zip复制到其他地方然后解压,里面就是所有的源码。发现里面有一个访问限定符为protected的方法clone():
/*Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object.
The general intent is that, for any object x, the expression:
1) x.clone() != x will be true
2) x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements.
3) x.clone().equals(x) will be true, this is not an absolute requirement.*/
protected native Object clone() throws CloneNotSupportedException;
仔细一看,它还是一个native方法,大家都知道native方法是非Java语言实现的代码,供Java程序调用的,因为Java程序是运行在JVM虚拟机上面的,要想访问到比较底层的与操作系统相关的就没办法了,只能由靠近操作系统的语言来实现。
第一次声明保证克隆对象将有单独的内存地址分配。
第二次声明表明,原始和克隆的对象应该具有相同的类类型,但它不是强制性的。
第三声明表明,原始和克隆的对象应该是平等的equals()方法使用,但它不是强制性的。
因为每个类直接或间接的父类都是Object,因此它们都含有clone()方法,但是因为该方法是protected,所以都不能在类外进行访问。
要想对一个对象进行复制,就需要对clone方法覆盖。
为什么要克隆?
大家先思考一个问题,为什么需要克隆对象?直接new一个对象不行吗?
答案是:克隆的对象可能包含一些已经修改过的属性,而new出来的对象的属性都还是初始化时候的值,所以当需要一个新的对象来保存当前对象的“状态”就靠clone方法了。那么我把这个对象的临时属性一个一个的赋值给我新new的对象不也行嘛?可以是可以,但是一来麻烦不说,二来,大家通过上面的源码都发现了clone是一个native方法,就是快啊,在底层实现的。
提个醒,我们常见的Object a=new Object();Object b;b=a;这种形式的代码复制的是引用,即对象在内存中的地址,a和b对象仍然指向了同一个对象。
而通过clone方法赋值的对象跟原来的对象时同时独立存在的。
如何实现克隆
先介绍一下两种不同的克隆方法,浅克隆(ShallowClone)和深克隆(DeepClone)。
在Java语言中,数据类型分为值类型(基本数据类型)和引用类型,值类型包括int、double、byte、boolean、char等简单数据类型,引用类型包括类、接口、数组等复杂类型。浅克隆和深克隆的主要区别在于是否支持引用类型的成员变量的复制,下面将对两者进行详细介绍。
一般步骤是(浅克隆):
1. 被复制的类需要实现Clonenable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常), 该接口为标记接口(不含任何方法)
2. 覆盖clone()方法,访问修饰符设为public。方法中调用super.clone()方法得到需要的复制对象。(native为本地方法)
下面对上面那个方法进行改造:
class Student implementsCloneable{private intnumber;public intgetNumber() {returnnumber;
}public void setNumber(intnumber) {this.number =number;
}
@OverridepublicObject clone() {
Student stu= null;try{
stu= (Student)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}returnstu;
}
}public classTest {public static voidmain(String args[]) {
Student stu1= newStudent();
stu1.setNumber(12345);
Student stu2=(Student)stu1.clone();
System.out.println("学生1:" +stu1.getNumber());
System.out.println("学生2:" +stu2.getNumber());
stu2.setNumber(54321);
System.out.println("学生1:" +stu1.getNumber());
System.out.println("学生2:" +stu2.getNumber());
}
}
结果:
学生1:12345
学生2:12345
学生1:12345
学生2:54321
如果你还不相信这两个对象不是同一个对象,那么你可以看看这一句:
System.out.println(stu1 == stu2); //false
上面的复制被称为浅克隆。
还有一种稍微复杂的深度复制:
我们在学生类里再加一个Address类。
classAddress {privateString add;publicString getAdd() {returnadd;
}public voidsetAdd(String add) {this.add =add;
}
}class Student implementsCloneable{private intnumber;privateAddress addr;publicAddress getAddr() {returnaddr;
}public voidsetAddr(Address addr) {this.addr =addr;
}public intgetNumber() {returnnumber;
}public void setNumber(intnumber) {this.number =number;
}
@OverridepublicObject clone() {
Student stu= null;try{
stu= (Student)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}returnstu;
}
}public classTest {public static voidmain(String args[]) {
Address addr= newAddress();
addr.setAdd("杭州市");
Student stu1= newStudent();
stu1.setNumber(123);
stu1.setAddr(addr);
Student stu2=(Student)stu1.clone();
System.out.println("学生1:" + stu1.getNumber() + ",地址:" +stu1.getAddr().getAdd());
System.out.println("学生2:" + stu2.getNumber() + ",地址:" +stu2.getAddr().getAdd());
}
}
结果:
学生1:123,地址:杭州市
学生2:123,地址:杭州市
乍一看没什么问题,真的是这样吗?
我们在main方法中试着改变addr实例的地址。
addr.setAdd("西湖区");
System.out.println("学生1:" + stu1.getNumber() + ",地址:" +stu1.getAddr().getAdd());
System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
结果:
学生1:123,地址:杭州市
学生2:123,地址:杭州市
学生1:123,地址:西湖区
学生2:123,地址:西湖区
这就奇怪了,怎么两个学生的地址都改变了?
原因是浅复制只是复制了addr变量的引用,并没有真正的开辟另一块空间,将值复制后再将引用返回给新对象。
所以,为了达到真正的复制对象,而不是纯粹引用复制。我们需要将Address类可复制化,并且修改clone方法,完整代码如下:
packageabc;class Address implementsCloneable {privateString add;publicString getAdd() {returnadd;
}public voidsetAdd(String add) {this.add =add;
}
@OverridepublicObject clone() {
Address addr= null;try{
addr= (Address)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}returnaddr;
}
}class Student implementsCloneable{private intnumber;privateAddress addr;publicAddress getAddr() {returnaddr;
}public voidsetAddr(Address addr) {this.addr =addr;
}public intgetNumber() {returnnumber;
}public void setNumber(intnumber) {this.number =number;
}
@OverridepublicObject clone() {
Student stu= null;try{
stu= (Student)super.clone(); //浅复制
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
stu.addr= (Address)addr.clone(); //深度复制
returnstu;
}
}public classTest {public static voidmain(String args[]) {
Address addr= newAddress();
addr.setAdd("杭州市");
Student stu1= newStudent();
stu1.setNumber(123);
stu1.setAddr(addr);
Student stu2=(Student)stu1.clone();
System.out.println("学生1:" + stu1.getNumber() + ",地址:" +stu1.getAddr().getAdd());
System.out.println("学生2:" + stu2.getNumber() + ",地址:" +stu2.getAddr().getAdd());
addr.setAdd("西湖区");
System.out.println("学生1:" + stu1.getNumber() + ",地址:" +stu1.getAddr().getAdd());
System.out.println("学生2:" + stu2.getNumber() + ",地址:" +stu2.getAddr().getAdd());
}
}
结果:
学生1:123,地址:杭州市
学生2:123,地址:杭州市
学生1:123,地址:西湖区
学生2:123,地址:杭州市
这样结果就符合我们的想法了。
最后我们可以看看API里其中一个实现了clone方法的类:
java.util.Date:
/*** Return a copy of this object.*/
publicObject clone() {
Date d= null;try{
d= (Date)super.clone();if (cdate != null) {
d.cdate=(BaseCalendar.Date) cdate.clone();
}
}catch (CloneNotSupportedException e) {} //Won't happen
returnd;
}
该类其实也属于深度复制。
浅克隆和深克隆
1、浅克隆
在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。
简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。
在Java语言中,通过覆盖Object类的clone()方法可以实现浅克隆。
2、深克隆
在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。
简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。
在Java语言中,如果需要实现深克隆,可以通过覆盖Object类的clone()方法实现,也可以通过序列化(Serialization)等方式来实现。
(如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。)
序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。
解决多层克隆问题
如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。
public class Outer implementsSerializable{private static final long serialVersionUID = 369285298572941L; //最好是显式声明ID
publicInner inner;//Discription:[深度复制方法,需要对象及对象所有的对象属性都实现序列化]
publicOuter myclone() {
Outer outer= null;try { //将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝
ByteArrayOutputStream baos = newByteArrayOutputStream();
ObjectOutputStream oos= newObjectOutputStream(baos);
oos.writeObject(this);//将流序列化成对象
ByteArrayInputStream bais = newByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois= newObjectInputStream(bais);
outer=(Outer) ois.readObject();
}catch(IOException e) {
e.printStackTrace();
}catch(ClassNotFoundException e) {
e.printStackTrace();
}returnouter;
}
}
Inner也必须实现Serializable,否则无法序列化:
public class Inner implementsSerializable{private static final long serialVersionUID = 872390113109L; //最好是显式声明ID
public String name = "";publicInner(String name) {this.name =name;
}
@OverridepublicString toString() {return "Inner的name值为:" +name;
}
}
这样也能使两个对象在内存空间内完全独立存在,互不影响对方的值。
总结
实现对象克隆有两种方式:
1). 实现Cloneable接口并重写Object类中的clone()方法;
2). 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。
注意:基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用Object类的clone方法克隆对象。让问题在编译的时候暴露出来总是优于把问题留到运行时。
文章转载:对象克隆
-
Javascript中的对象拷贝(对象复制/克隆)
2021-06-09 18:36:06Javascript中的对象拷贝(对象复制) CSDN:jcLee95 邮箱:291148484@163.com 1. 对象的引用 要说“拷贝”还要先说“引用”的概念。 在JavaScript中没有“指针”的概念,但保留了对象的“引用”。首先必须明确,与...Javascript中的对象拷贝(对象复制/克隆)
Jack Lee 的 CSDN 博客
邮箱 :291148484@163.com
CSDN 主页:https://blog.csdn.net/qq_28550263?spm=1001.2101.3001.5343
本文地址:https://blog.csdn.net/qq_28550263/article/details/117751704
目 录
1. 对象的引用
要说“拷贝”还要先说“引用”的概念。
在JavaScript中没有“指针”的概念,但保留了对象的“引用”。首先必须明确,与“Java”、“Python”等经典面向对象编程语言中“一切皆可对象”不同,在JavaScript中绝非一切皆是对象,并且“引用”是“对象”的引用。这里就需要区分在赋值操作“=”的右侧,到底是一个“字面量”还是一个对象。举例而言:var a = 1; var b = "Hello World!" var c = a
这里的数字
1
和"Hello World!
"都并非对象,而是不可变的字面量值。他们分别源于简单基本类型number
和string
而不是内置对象Number
和String
。(不过,对于左侧的变量,如果你要使用一些方法或属性,如.leng
,并不需要显式将string转为String对象)由于右侧不是对象,这在赋值操作
var c = a
传递的是值,即将number 1传给变量c,换成var c = b
也类似。而下例中:
var d = { Hello : 'world' }; var e = d;
由于变量
d
的右侧是一个变量,这时var d = {Hello : 'world'};
使得变量d
创建了一个引用,即使得d中保存了右侧那个对象的地址,我们可以对变量d
间接对右侧那个变量进行操作,如d.Hello
。而后一个赋值操作var e = d;
传递的是引用的内容,由于第一个赋值使得d
引用到了其右侧那个对象的内存地址,后一个赋值只不过是将变量d
中所存储的内容地址赋值了一份给变量e
而已,因此这时变量e
与d
引用到了同一个对象。2. 浅拷贝
浅拷贝,又称“浅层拷贝”、“浅复制”、“浅层复制”等等。
一个很常见的通俗说法,“浅层拷贝就是只拷贝一层”,这样的说法其实不太准确。
浅拷贝的本质特点是:- 拷贝原对象后将得到一个新对象;
- 新对象的将中的所有
属性
都是原对象中对应属性的一个引用。
因此从表象上看,浅拷贝拷贝出来的新对象中所有属性的值会复制原对象中对应属性的值。例如:
var obj_a = { is_right : true } var obj_b == { a : 1, b : obj_a }
如果获取对象
obj_b
的浅拷贝得到一个新对象,可以使用ES6提供的Object.assign()
方法:var new_obj = Object.assign({}, obj_b);
其中
Object.assign()
方法,第一个参数是目标对象,后面为一个或多个源对象。
该方法对obj_b
实现浅拷贝的过程为,遍历一个或多个源对象(从第二个参数开始)所有可枚举的自有键,并逐个进行“=”完成复制,最后返回目标对象。
上例中,对于键值对“a:1
”,由于数字“1
”是一个不可变的值(字面量)而非数字,new_obj
的第一个键值对完全相同,故有:new_obj.a == 1 // true
然而第二个键值对的值
obj_a
引用了一个对象,进行“=
”复制到new_obj
的键值时,传递的时引用的地址,这样使得new_obj
对应与键b
的值引用到了对象obj_a
。即必有:new_obj.b === obj_a
那么,为什么称
浅层
拷贝呢?
以上我们已经了解了这种拷贝方法的本质,但是如果想要了解这个名字的由来,我们不妨假设上例中,将obj_a
修改为一个键值中含有对象的对象。如:var obj_c = { are_you_ok : true } var obj_a = { is_right : true, is_ok : obj_c } var obj_b == { a : 1, b : obj_a }
这时对
obj_b
进行浅拷贝得到new_obj
后,new_obj.is_ok
对应的值为由obj_a.is_ok
对应的值obj_c
。由于变量obj_c
中存储的是对一个对象的引用,这里传递的同样是被引用对象的地址,但地址中所存储的呢欧容不会进一步拷贝,这样在new_obj
中,并不会存储一个{are_you_ok : true}
这样的实际对象。因此看起来我们“只拷贝了表层”。这就是所谓“浅”拷贝称为的由来。
这里最后还有一个需要注意的问题,使用
Object.assign()
方法浅拷贝时由于相当于只是对源对象的所有可枚举的自有键一一进行“=”赋值操作,对于由属性描述符所描述的一些属性的特性是不会被拷贝到目标对象的。如某个属性是否可修改(Writeable)、可配置(Configurable)、可枚举(Enumerable)。3. 深拷贝
在JavaScript中,深拷贝说起来有点麻烦,因为里面情况会很比较复杂。
相比于浅拷贝,深拷贝要求要完整地拷贝下底层被引用地对象而不是仅粗略地要求拷贝下引用中对象的地址。正如之前所说通俗一点理解看:- 仅用"="将对象引用的赋值给变量时,仅传递了引用的对象地址;
- 通过“浅拷贝”返回对象时,相当于对源对象最外层的所有可枚举属性进行了“=”操作后获得的新对象;
- 通过“深拷贝”返回对象时,相当于任意一层不再简单传递引用的对象的内存地址,而是真正意义上拷贝下来每一层对象。
但是很快你就会发现,如果在一个对象中引用的对象到某层由存在循环性的引用,往往会导致一个死循环。
另外,在JavaScript中的函数也是对象,我们在JS中不能确定对一个函数进行“深拷贝”是什么,尽管由很多框架给出了自己的定义。一种比较好方法是通过JSON序列化来实现深拷贝:
var obj_b = JSON.parse(JSON.stringify(obj_a))
这种方法也不是万能的,它要求对象必须是
JSON安全
的,即:
不仅obj_a
可以被序列化为一个JSON格式的字符串,同时还可以由该字符串解析得到一个结构
与值
完全相同的对象。 -
Java编程实现对象克隆(复制)代码详解
2020-08-28 17:13:18主要介绍了Java编程实现对象克隆(复制)代码详解,涉及了克隆的原因,如何实现克隆,克隆的一般步骤,深克隆与浅克隆的介绍等相关内容,具有一定借鉴价值,需要的朋友可以参考下。 -
Java之对象克隆(复制)
2021-01-19 13:27:12假如说你想复制一个简单变量。很简单: ...但是如果你复制的是一个对象,情况就有些复杂了。 假设说我是一个beginner,我会这样写: class Student { private int number; public int getNumber() { -
js 对象克隆方法总结(不改变原对象)
2021-08-26 13:44:191.通用对象克隆: function clone(obj){ let temp = null; if(obj instanceof Array){ temp = obj.concat(); }else if(obj instanceof Function){ //函数是共享的是无所谓的,js也没有什么办法可以在定义后再... -
Java对象的深克隆与浅克隆(对象复制)
2020-03-11 16:07:35假如说你想复制一个简单变量。很简单: int apples = 5; int pears = apples;...但是如果你复制的是一个对象,情况就有些复杂了。 假设说我是一个beginner,我会这样写: class Student { private ... -
Java对象克隆
2021-02-28 12:46:46Java对象克隆1 什么是Java对象克隆对象克隆是创建一个对象的副本的方式。Object类的clone() 方法用于克隆对象。java.lang.Cloneable接口必须由我们要建立其对象克隆的类实现。如果我们不实现Cloneable接口,则clone... -
PHP面向对象程序设计之对象克隆clone和魔术方法__clone()用法分析
2021-01-20 08:08:191.对象克隆 clone PHP4面向对象功能一个很大的缺点,是将对象视为另一种数据类型,这使得很多常见的OOP方法无法使用,如设计模式。这些方法依赖于将对象作为引用传递给其他类方法,而不是作为值传递,而按值传递却是... -
什么是对象克隆(拷贝/复制)?二者有什么区别?
2018-10-09 12:16:00首先我们需要知道,什么是对象的克隆,或者说复制。一个业务逻辑,需要一个新的对象,但是类型和值都是之前的,也就是说,新状态和之前完全一样。使用new和赋值语句或者set注入都是可以的,但是,这会花费大量开销去... -
什么场景要对象克隆?
2019-06-18 11:12:19什么场景要对象克隆? 方法需要 return 引用类型,但又不希望自己持有引用类型的对象被修改。 程序之间方法的调用时参数的传递。有些场景为了保证引用类型的参数不被其他方法修改,可以使用克隆后的值作为参数... -
clone()方法示例(对象克隆)_对象克隆_nervouse78_源码
2021-10-04 01:45:12clone()方法示例(对象克隆)。作者: 初生不惑 -
Java提高—对象克隆(复制)/对象属性拷贝
2017-10-18 21:09:59对象克隆(复制)假如说你想复制一个简单变量。很简单:int apples = 5; int pears = apples; 不仅仅是int类型,其它七种原始数据类型(boolean,char,byte,short,float,double.long)同样适用于该类情况。但是如果你... -
Java对象复制
2022-02-11 11:26:57Java对象复制(拷贝) -
Unity对象复制赋值给同类型的对象(克隆对象)
2018-09-26 11:43:55当你想把对象的某一个属性更改后再添加到一个列表的时候,就会被最后的对象替换掉,就不能实现新对象的添加。下面两种方式可以实现对象赋值给同类型的对象,这两个对象的堆栈信息是不一样的。 方法一: 必须对对象... -
对象克隆
2021-07-21 16:24:09为什么需要对象克隆?直接new一个对象不行吗? 克隆的对象可能包含一些已经修改过的属性,而new出来的对象的属性都还是...这种形式的代码复制实际上只是引用,b复制了a在内存中的地址,a和b指向了同一个对象。所以这种 -
PHP对象克隆clone用法示例
2020-12-18 12:45:03浅克隆:只是克隆对象中的非对象非资源数据,即对象中属性存储的是对象类型,则会出现克隆不完全 <?php class B{ public $val = 10; } class A{ public $val = 20; public $b; public function __construct... -
26.如何实现对象克隆?
2021-06-26 18:55:54对象克隆就是对象的复制操作。 分为浅克隆(ShallowClone)和深克隆(DeepClone) 在Java语言中,数据类型分为值类型(基本数据类型)和引用类型,值类型包括int、double、byte、boolean、char等简单数据类型,引用类型... -
Java实现对象克隆的方法
2021-03-01 09:35:23正文JAVA实现克隆有两种形式浅克隆深克隆浅克隆与深克隆的区别JAVA将数据类型分为基本数据类型以及引用数据类型,我认为浅克隆与深克隆的区别主要在于对引用类型的成员属性的操作。深度克隆应该递归克隆引用类型的... -
Java copy对象的工具类
2018-07-27 13:47:52Java CopyUtil工具类,可以进行对象的深copy,比如:对象里面包含对象,对象里面包含Map,List,Set...等复杂类型的属性都可以copy,copy后的对象与原有对象没有联系,即改变原有对象内容,不会改变copy后的对象里面的... -
如何实现对象克隆?
2019-06-18 14:23:54如何实现对象克隆? 实现 Cloneable 接口,重写 clone() 方法。 不实现 Cloneable 接口,会报 CloneNotSupportedException 异常。 package constxiong.interview; /** * 测试克隆 * @author ConstXiong * @... -
Java 对象的深复制五种方式
2021-03-18 10:40:551. 概述在实际编程过程中,我们常常要遇到这种情况:有一个对象A,在某一时刻A中已经包含了一些有效值,此时可能 会需要一个和A完全相同新对象B,并且此后对B任何改动都不会影响到A中的值,也就是说,A与B是两个独立... -
Java如何实现对象克隆
2019-03-24 11:16:30Java中的对象克隆 -
对象复制的六种方法
2019-12-23 15:42:36对象复制可以分为:(地址复制),(实现Cloneable的方法),(使用BeanUtils.copyProperties() ),(PropertyUtils.copyProperties()),(序列化),(反射) 这里先总结一下浅克隆和深克隆:... -
java克隆对象(两种方法)
2017-09-17 10:50:56java的两种深度克隆方法,1cloneTest是用Cloneable接口的clone方法实现(对象必须要实现cloneable接口).2cloneSerialize.java是用对象流写对象到byte数组中,然后从byte数组中取得对象.(对象必须要实现serializble接口)