Java 浅拷贝以及深拷贝
1. Java数据类型
在Java程序中,存在基本类型以及引用类型两种数据类型结构
- 基本类型
- 基本类型分为3类:
- 数值性
- 整数型:byte, int, short, long
- 浮点数:float, double
- 字符型:char
- 布尔型:boolean
- 引用类型
- 引用类型分为3类:
- 对象:class
- 接口:interface
- 数组: T []
2.不同类型数据的处理方式
2.1 基本数据类型处理方法
在Java程序设计中,基本的数据类型值都是通过重新赋值的方式进行传递
public static int addSum(int a, int b){
a = a + b;
return a;
}
public static void main(String[] args) {
int a1 = 10;
int b1 = 5;
System.out.println(addSum(a1, b1));
System.out.println(a);
}
result :
15 // a+b
10 // a
可以看出在方法addSum中对a的修改不会影响到a1的值(a表示形参,a1表示实参,修改形参不会变更实参的值)
2.2引用类型处理方式
引用类型和基本类型的处理方式是不同的,Java中的对象可以包含基本类型和引用数据类型,使用关键字new进行创建,例如:
// 国家类
class Country{
//国家名称
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
// 城市类
class City {
//国家名称
private Country country;
// 城市名称
private String name;
public Country getCountry() {
return country;
}
public void setCountry(Country country) {
this.country = country;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
创建对象,并对对象进行赋值操作
Country china = new Country();//创建国家对象
china.setName("china");//赋值
City c1 = new City();//创建城市对象
c1.setName("hz");//赋值
c1.setCountry(china);//赋值
引用类型传参处理方式和基础类不同,具体参见以下代码:
public static void main(String[] args) {
Country china = new Country();
china.setName("china");
City c1 = new City();
c1.setName("hz");
c1.setCountry(china);
immigrant(china, "England");
System.out.println(china.getName());
}
public static void immigrant(Country country, String countryName) {
country.setName(countryName);
}
result://输出结果信息
England
上述代码的输出结果是England,可以发现在方法内对形参进行处理时,会影响到实际变量china的值,而在之前的addSum方法中,对形参进行处理是不会影响到实际变量的值,实际上,引起上述不同的原因的数据存储方式不同,
3.JVM数据存储方式
在Java程序的运行环境是JVM,在JVM中使用两种内存来处理数据(当然还有其它的内存用于存储特定数据,在这里暂不讨论),栈内存和对内存,
- 栈内存:
栈内存用于存储函数中定义的基础数据变量和引用变量(基础数据变量指基础类型变量,例如 int i = 1, 引用变量指的就是引用类型变量,例如 City city = new City())
- 堆内存:
堆内存用于存储引用变量所指向的实际数据信息(可以把引用变量理解为存储内存地址信息,而实际的对象数据存储在引用变量所指向的地址),创建对象时,会在JVM的堆内存区域分配一块内存用于存储对象信息,china代表一个引用,这个引用指向内存中存储的对象数据,可以通过这个引用访问数据
graph LR
china引用-->JVM内存区域存储对象数据
为了说明引用变量存储的是对象的引用信息,而不是实际的对象数据信息,进一步修改上述代码
public static void main(String[] args) {
Country china = new Country();
china.setName("china");
City c1 = new City();
c1.setName("hz");
c1.setCountry(china);
immigrant(china, "England");
System.out.println(china.getName());
}
public static void immigrant(Country country, String countryName) {
country = new Country();
country.setName(countryName);
}
result:
china
从上述结果可以看出,对country变量赋值一个新对象,修改之后不会影响到原先的china变量的值,可以用以下图片说明:
1.刚开始时变量china和country都指向内存中的对象Country1
graph LR
china-->Country1对象
country-->Country1对象
2.执行country=new Country时,china变量和country变量指向了两个不同的对象
graph LR
china-->Country1对象
country-->Country2对象
4.深拷贝和浅拷贝
通过上述说明,我们可以了解到,如果两个引用变量通过’a=b’的方式进行赋值时,通过任何一个引用变量修改对象数据时,将会影响到另外一个引用的数据,若果采用创建新对象的方式进行赋值时,修改一个引用变量的值将不会影响到另外一个引用变量的值。
4.1 重写方法实现深拷贝
通过实现Cloneable接口来申明该类是可拷贝的,重写clone方法来深拷贝对象信息
class Country implements Cloneable, Serializable {
private static final long serialVersionUID = 11111L;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public Country clone() {
Country country = null;
try {
country = (Country) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return country;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Country{");
sb.append("name='").append(name).append('\'');
sb.append('}');
return sb.toString();
}
}
class City implements Cloneable, Serializable {
private static final long serialVersionUID = 22222L;
private Country country;
private String name;
public Country getCountry() {
return country;
}
public void setCountry(Country country) {
this.country = country;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public City clone() {
City cy = null;
try {
cy = (City) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
cy.setCountry(country.clone());
return cy;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("City{");
sb.append("country=").append(country);
sb.append(", name='").append(name).append('\'');
sb.append('}');
return sb.toString();
}
}
4.2 通过序列化的方式实现深拷贝
public static <T> T cloneBeanObjectByIO(T obj) {
T retVal = null;
try {
// 将对象写入流中
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
// 从流中读出对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
retVal = (T) ois.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return retVal;
}