前言
在Java中,有赋值的地方,就存在浅拷贝和深拷贝的问题.稍有不慎,就会为项目埋下隐藏的BUG.本文将对问题进行总结,以减少此类问题的产生.
浅拷贝及深拷贝定义
浅拷贝:在拷贝对象时,对基础数据类型进行赋值,对引用数据类型进行传递引用.
深拷贝:在拷贝对象时,对基础数据类型进行赋值,对引用数据类型进行创建新对象并复制其内的成员变量
java的基础数据类型:byte/short/int/long/float/double/char/boolean
引用数据类型:类实例对象
特别注意:
基本数据类型只能按值传递
每个基本数据类型对应的封装类是按引用传递的
基本数据类型的好处就是速度快(不涉及到对象的构造和回收),封装类的好处是工具方法多,用起来方便。
实际上,封装类型在深拷贝及浅拷贝上的体现并不明显,实验中它的表现效果跟基础数据类型差不多.
上面说[封装类型在深拷贝及浅拷贝上的体现并不明显,实验中它的表现效果跟基础数据类型差不多],这里我们使用一个实验来验证:
@Test
public void check(){
String userName = "张三";
String userName2 = userName;
System.out.println("userName:"+userName.hashCode()+" userName2:"+userName2.hashCode()+
" hashCode 一致说明确实是引用传值");
//封装类型在深拷贝及浅拷贝上的体现并不明显,实验中它的表现效果跟基础数据类型差不多
//其实相当于 userName2 = new String("李四");
userName2 = "李四";
System.out.println("userName:"+userName.hashCode()+" userName2:"+userName2.hashCode()+
" userName2 的hashCode 发生了改变");
}
示例分析(java代码)
浅拷贝的实现
1.我们定义了一个用户实体类.类中成员变量很简单,一个是String类型的用户名,一个是地址子类(Address)
package keytop.com.demo.copy;
/**
* 用户实体类(浅拷贝测试)
* Created by fengwenhua on 2018/3/7.
*/
public class UserEntity implements Cloneable{
private String mUserName;
private Address mAddress;
public UserEntity(String id,Address address){
this.mUserName = id;
this.mAddress = address;
}
public String getmUserName() {
return mUserName;
}
public void setmUserName(String mUserName) {
this.mUserName = mUserName;
}
public Address getmAddress() {
return mAddress;
}
public void setmAddress(Address mAddress) {
this.mAddress = mAddress;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "UserEntity{" +
"mUserName='" + mUserName + '\'' +
", mAddress=" + mAddress +
'}';
}
}
Address.java
package keytop.com.demo.copy;
/**
* 当前用户地址
* Created by fengwenhua on 2018/3/7.
*/
public class Address {
/**国家*/
private String country;
/**省份*/
private String province;
public Address(String country,String province){
this.country = country;
this.province = province;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
@Override
public String toString() {
return "Address{" +
"country='" + country + '\'' +
", province='" + province + '\'' +
'}';
}
}
很好.我们需要测试的原材料已经准备好了.接下来是我们的测试用例:
package keytop.com.demo.copy;
import org.junit.Test;
/**
* 浅拷贝及深拷贝测试
* Created by fengwenhua on 2018/3/7.
*/
public class MainTest {
@Test
public void testShallowCopy() throws CloneNotSupportedException {
shallowCopy();
}
/**
* 浅拷贝
* @throws CloneNotSupportedException 类型不支持
*/
private void shallowCopy() throws CloneNotSupportedException {
Address address = new Address("中国","福建");
UserEntity entity = new UserEntity("张三",address);
//尝试修改数据的内容
UserEntity copyData = (UserEntity) entity.clone();
copyData.setmUserName("李四");
copyData.getmAddress().setProvince("北京");
System.out.println("entity hashcode:"+entity.hashCode()+" address hashCode"
+entity.getmAddress().hashCode()+" 数据内容:"+entity.toString());
System.out.println("copyData hashcode:"+copyData.hashCode()+" address hashCode"
+copyData.getmAddress().hashCode()+" 数据内容:"+copyData.toString());
}
}
你没看错,修改李四所在的省份地址.张三的省份地址也动到了.因为张三李四的地址其实指向的是同一个实例
如何实现深拷贝
前面我们总结过了.浅拷贝是因为基础数据类型是值传递,引用数据类型仍然是传值引用.
那么实现深拷贝可以 对实例对象的引用数据类型 变量再进行 clone() 处理.
在以上试验的基础上,我们新增加Score.java类
package keytop.com.demo.copy;
/**
* 成绩
* Created by fengwenhua on 2018/3/7.
*/
public class Score implements Cloneable{
/**英语成绩*/
private String mEnglish;
/**语文成绩*/
private String mChinese;
public Score(String mEnglish,String mChinese){
this.mEnglish = mEnglish;
this.mChinese = mChinese;
}
public String getmEnglish() {
return mEnglish;
}
public void setmEnglish(String mEnglish) {
this.mEnglish = mEnglish;
}
public String getmChinese() {
return mChinese;
}
public void setmChinese(String mChinese) {
this.mChinese = mChinese;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Score{" +
"mEnglish='" + mEnglish + '\'' +
", mChinese='" + mChinese + '\'' +
'}';
}
}
为UserEntity 添加Score成员属性,并在clone()上做新的处理(焦点)
package keytop.com.demo.copy;
/**
* 用户实体类(浅拷贝测试)
* Created by fengwenhua on 2018/3/7.
*/
public class UserEntity implements Cloneable{
private String mUserName;
private Address mAddress;
private Score mScore;
public UserEntity(String id,Address address){
this.mUserName = id;
this.mAddress = address;
}
public UserEntity(String id,Address address,Score score){
this.mUserName = id;
this.mAddress = address;
this.mScore = score;
}
public String getmUserName() {
return mUserName;
}
public void setmUserName(String mUserName) {
this.mUserName = mUserName;
}
public Address getmAddress() {
return mAddress;
}
public void setmAddress(Address mAddress) {
this.mAddress = mAddress;
}
public Score getmScore() {
return mScore;
}
public void setmScore(Score mScore) {
this.mScore = mScore;
}
@Override
protected Object clone() throws CloneNotSupportedException {
UserEntity userEntity = (UserEntity) super.clone();
userEntity.setmScore((Score) this.mScore.clone());//对 引用数据类型 变量再进行 clone() 处理
return userEntity;
}
@Override
public String toString() {
return "UserEntity{" +
"mUserName='" + mUserName + '\'' +
", mAddress=" + mAddress +
", mScore=" + mScore +
'}';
}
}
扩展我们的测试用例:
package keytop.com.demo.copy;
import org.junit.Test;
/**
* 浅拷贝及深拷贝测试
* Created by fengwenhua on 2018/3/7.
*/
public class MainTest {
@Test
public void testShallowCopy() throws CloneNotSupportedException {
shallowCopy();
}
/**
* 浅拷贝及深拷贝测试
* @throws CloneNotSupportedException 类型不支持
*/
private void shallowCopy() throws CloneNotSupportedException {
Address address = new Address("中国","福建");
Score score = new Score("99.9","100");
UserEntity entity = new UserEntity("张三",address,score);
//尝试修改数据的内容
UserEntity copyData = (UserEntity) entity.clone();
copyData.setmUserName("李四");
copyData.getmAddress().setProvince("北京");
copyData.getmScore().setmEnglish("50"); //--->修改李四的英语成绩为50
System.out.println("address hashCode:"+entity.getmAddress().hashCode()+" score hashcode:"
+entity.getmScore().hashCode()+" 数据内容:"+entity.toString());
System.out.println("address hashCode:"+copyData.getmAddress().hashCode()+" score hashcode:"
+copyData.getmScore().hashCode()+" 数据内容:"+copyData.toString());
}
}
很好,这次我们的引用数据类型变量不再相互影响.李四也不用变成学渣了
扩展阅读:细说 Java 的深拷贝和浅拷贝