java拷贝默认是浅拷贝,比如System.arraycopy()和clone()。java中将一个对象复制到另外一个对象上主要由直接赋值、深拷贝和浅拷贝三种方式。
一、基本类型赋值
1、先看下基本类型int和String对象拷贝的例子
String s1 = "aaaa";
String s2 = s1;
int a=3;
int b=a;
System.out.print( "before: s1=" + s1 + ",s2=" + s2+",a="+a+",b="+b+"\n");
s2 = "bb";
b=4;
System.out.print("after: s1=" + s1 + ",s2=" + s2+",a="+a+",b="+b);
log打印的结果是
before: s1=aaaa,s2=aaaa,a=3,b=3
after: s1=aaaa,s2=bb,a=3,b=4
从log可以看出将字符串s1拷贝给s2,int a拷贝给b后,改变s2和b后s1和a的值没有发生变化
2、引用(对象)类型的赋值
新建对象Student
Student student1=new Student("jack",8,"shanghai");
Student student2=student1;
System.out.print( "before: student1.toString()="+student1.printSelf()+",,student2.toString()="+student2.printSelf()+"," +
"student1="+student1+",student2"+student2+"\n");
student2.setAge(88);
student2.setAddress("shanghai-pudong");
System.out.print( "after: student1.toString()="+student1.printSelf()+",,student2.toString()="+student2.printSelf()+"," +
"student1="+student1+",student2"+student2+"\n");
测试代码如下:
Student student1=new Student("jack",8,"shanghai");
Student student2=student1;
System.out.print( "before: student1.toString()="+student1.printSelf()+",,student2.toString()="+student2.printSelf()+"," +
"student1="+student1+",student2"+student2+"\n");
student2.setAge(88);
student2.setAddress("shanghai-pudong");
System.out.print( "after: student1.toString()="+student1.printSelf()+",,student2.toString()="+student2.printSelf()+"," +
"student1="+student1+",student2"+student2+"\n");
log打印:
before: student1.toString()={name=jack,age=8,address=shanghai},,student2.toString()={name=jack,age=8,address=shanghai},student1=Student@4554617c,student2Student@4554617c
after: student1.toString()={name=jack,age=88,address=shanghai-pudong},,student2.toString()={name=jack,age=88,address=shanghai-pudong},student1=Student@4554617c,student2Student@4554617c
student1和student2指向了相同的对象student2Student@4554617c,student1拷贝给student2后改变student2的字段,student1相应字段也随之改变。放到集合中也是类似情况
List<Student> alist=new ArrayList<>() ;
List<Student> blist=new ArrayList<>() ;
alist.add(new Student("jack",8,"shanghai"));
alist.add(new Student("lucy",10,"beijing"));
blist.addAll(alist);
blist.get(0).setAge(88);
blist.get(0).setAddress("shanghai-pudong");
System.out.print( "alist="+Arrays.toString(alist.toArray())+",,blist="+ Arrays.toString(blist.toArray()));
log打印如下:
alist=[Student@4554617c, Student@74a14482],,blist=[Student@4554617c, Student@74a14482]
二、浅拷贝
对象拷贝需要实现Cloneable接口并重写clone方法,默认的拷贝是浅拷贝,即如果拷贝对象是基本数据类型则拷贝的是基本类型的值(Sting是重新分配一个字符串);如果是对象的话则拷贝的对象的引用。
1、如果对象的字段都是基本类型数据,如下对象类
public class Student implements Cloneable {
private String name;
private int age;
private String address;
public Student(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String printSelf() {
return "{name="+name+",age="+age+",address="+address+"}";
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
测试代码
Student student1=new Student("jack",8,"shanghai");
Student student2= (Student) student1.clone();
System.out.print( "before: student1.toString()="+student1.printSelf()+",,student2.toString()="+student2.printSelf()+"," +
"student1="+student1+",student2"+student2+"\n");
student2.setAge(88);
student2.setAddress("shanghai-pudong");
System.out.print( "after: student1.toString()="+student1.printSelf()+",,student2.toString()="+student2.printSelf()+"," +
"student1="+student1+",student2"+student2+"\n");
日志打印:
before: student1.toString()={name=jack,age=8,address=shanghai},,student2.toString()={name=jack,age=8,address=shanghai},student1=Student@4554617c,student2Student@74a14482
after: student1.toString()={name=jack,age=8,address=shanghai},,student2.toString()={name=jack,age=88,address=shanghai-pudong},student1=Student@4554617c,student2Student@74a14482
日志看出确实出现了原对象Student@4554617c和拷贝对象student2Student@74a14482并不是同一对象了,并且基本数据类型的字段值相互不影响。
2、字段是引用型
现在我们把address字段改为一个实体引用类看看,Address类如下:
public class Address {
private String country;
private String province;
private String city;
public Address(String country, String province, String city) {
this.country = country;
this.province = province;
this.city = city;
}
@Override
public String toString() {
return "Address{" +
"country='" + country + '\'' +
", province='" + province + '\'' +
", city='" + city + '\'' +
'}';
}
public void setCountry(String country) {
this.country = country;
}
}
测试代码:
Student student1=new Student("jack",8,new Address("china","jiangsu","nanjing"));
Student student2= (Student) student1.clone();
System.out.print( "before: student1.toString()="+student1.printSelf()+",,student2.toString()="+student2.printSelf()+"," +
"student1="+student1+",student2"+student2+"\n");
student2.setAge(88);
student2.getAddress().setCountry("America");
System.out.print( "after: student1.toString()="+student1.printSelf()+",,student2.toString()="+student2.printSelf()+"," +
"student1="+student1+",student2"+student2+"\n");
日志打印如下:
before: student1.toString()={name=jack,age=8,address=Address{country='china', province='jiangsu', city='nanjing'}},,student2.toString()={name=jack,age=8,address=Address{country='china', province='jiangsu', city='nanjing'}},student1=Student@4554617c,student2Student@74a14482
after: student1.toString()={name=jack,age=8,address=Address{country='America', province='jiangsu', city='nanjing'}},,student2.toString()={name=jack,age=88,address=Address{country='America', province='jiangsu', city='nanjing'}},student1=Student@4554617c,student2Student@74a14482
可以看到,student1和student2是两个不同的对象,改变了student2基本类型字段age(改为88),student1中的age并未改变(还是8);但是改变student2引用型字段address(country改为America),student1中的address字段的country也发生了变化(country同步变成了America)。
三、深拷贝
深拷贝可以通过两种方式实现:需要重写Cloneable接口或者序列化对象
1、重写Cloneable接口
深拷贝和浅拷贝的主要区别在于深拷贝将对象也重新拷贝了一份,使得拷贝生成的对象不是原对象的引用而是一个占据独立的内存空间的对象,所以要实现深拷贝只需将对象中的每一个引用类型字段都重新复制一遍。
引用字段实现Cloneable接口并重写clone
public class Address implements Cloneable{
private String country;
private String province;
private String city;
public Address(String country, String province, String city) {
this.country = country;
this.province = province;
this.city = city;
}
@Override
public String toString() {
return "Address{" +
"country='" + country + '\'' +
", province='" + province + '\'' +
", city='" + city + '\'' +
'}';
}
public void setCountry(String country) {
this.country = country;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
原对象实现Cloneable接口并重写clone
public class Student implements Cloneable {
private String name;
private int age;
private Address address;
public Student(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public String printSelf() {
return "{name="+name+",age="+age+",address="+address+"}";
}
@Override
protected Object clone() throws CloneNotSupportedException {
Student student= (Student) super.clone();
student.setAddress((Address) student.getAddress().clone());
return student;
}
}
测试代码:
private static void shoalClone() throws CloneNotSupportedException {
Student student1=new Student("jack",8,new Address("china","jiangsu","nanjing"));
Student student2= (Student) student1.clone();
System.out.print( "before: student1.toString()="+student1.printSelf()+",,student2.toString()="+student2.printSelf()+"," +
"student1="+student1+",student2"+student2+"\n");
student2.setAge(88);
student2.getAddress().setCountry("America");
System.out.print( "after: student1.toString()="+student1.printSelf()+",,student2.toString()="+student2.printSelf()+"," +
"student1="+student1+",student2"+student2+"\n");
}
日志打印:
before: student1.toString()={name=jack,age=8,address=Address{country='china', province='jiangsu', city='nanjing'}},,student2.toString()={name=jack,age=8,address=Address{country='china', province='jiangsu', city='nanjing'}},student1=Student@4554617c,student2Student@74a14482
after: student1.toString()={name=jack,age=8,address=Address{country='china', province='jiangsu', city='nanjing'}},,student2.toString()={name=jack,age=88,address=Address{country='America', province='jiangsu', city='nanjing'}},student1=Student@4554617c,student2Student@74a14482
可以看出改变student2的引用型字段,student1也不会收到影响
备注:如果对象中包含多个引用型字段,则每个引用型字段都需要实现Colneable接口,并且在对象的Colneable接口实现对引用型字段重新赋值。
2、序列化对象
对象实现Serializable接口,对象中的引用型字段也实现Serializable接口
import java.io.Serializable;
public class Student implements Serializable {
private String name;
private int age;
private Address address;
public Student(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public String printSelf() {
return "{name="+name+",age="+age+",address="+address+"}";
}
}
import java.io.Serializable;
public class Address implements Serializable {
private String country;
private String province;
private String city;
public Address(String country, String province, String city) {
this.country = country;
this.province = province;
this.city = city;
}
@Override
public String toString() {
return "Address{" +
"country='" + country + '\'' +
", province='" + province + '\'' +
", city='" + city + '\'' +
'}';
}
public void setCountry(String country) {
this.country = country;
}
}
测试代码:
try {
Student student1=new Student("jack",8,new Address("china","jiangsu","nanjing"));
//序列化
ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream=new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(student1);
//反序列化
ByteArrayInputStream byteArrayInputStream =new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream=new ObjectInputStream(byteArrayInputStream);
Student student2= (Student) objectInputStream.readObject();
System.out.print( "before: student1.toString()="+student1.printSelf()+",,student2.toString()="+student2.printSelf()+"," +
"student1="+student1+",student2"+student2+"\n");
student2.setAge(88);
student2.getAddress().setCountry("America");
System.out.print( "after: student1.toString()="+student1.printSelf()+",,student2.toString()="+student2.printSelf()+"," +
"student1="+student1+",student2"+student2+"\n");
} catch (Exception e) {
e.printStackTrace();
}
日志打印:
before: student1.toString()={name=jack,age=8,address=Address{country='china', province='jiangsu', city='nanjing'}},,student2.toString()={name=jack,age=8,address=Address{country='china', province='jiangsu', city='nanjing'}},student1=Student@135fbaa4,student2Student@5f184fc6
after: student1.toString()={name=jack,age=8,address=Address{country='china', province='jiangsu', city='nanjing'}},,student2.toString()={name=jack,age=88,address=Address{country='America', province='jiangsu', city='nanjing'}},student1=Student@135fbaa4,student2Student@5f184fc6
可以看出student2和student1不是同一对象,并且改变student2的引用型字段,student1也不会收到影响