上次我们说了Copy Constructor与clone()方法。谈到用拷贝构造函数和clone()方法来实现深拷贝会很麻烦。
那么有没有什么简单的方法来实现深拷贝呢?答案是有的,使用Java Serializable来实现深拷贝。
0. 数据类模型
什么是Serializable?我们来看下代码。
public interface Serializable {
}
和之前的Cloneable是一个样子,与其说是一个接口,更像是一个属性。
我们建立一个嵌套深度为3的数据类。
注:这不是一个很好的面向对象的封装方式。此处只为缩减代码量。正式编程中请不要随便暴露类变量。
public class DataClass implements Serializable{
A a;
double valueC;
public DataClass(){
a = new A();
valueC = 1.05;
}
public DataClass(int valueA, float valueB, double valueC){
this.valueC = valueC;
a = new A(valueA, valueB);
}
@Override
public String toString(){
StringBuilder sb = new StringBuilder();
sb.append("a = ").append(a.valueA).append("\n");
sb.append("b = ").append(a.b.valueB).append("\n");
sb.append("c = ").append(valueC).append("\n");
return sb.toString();
}
}
其中,A类声明如下。
public class A implements Serializable {
int valueA;
B b;
public A(){
valueA = 100;
}
public A(int valueA, float valueB){
this.valueA = valueA;
b = new B(valueB);
}
@Override
public String toString(){
StringBuilder sb = new StringBuilder();
sb.append("a = ").append(valueA).append("\n");
sb.append("b = ").append(b.valueB).append("\n");
return sb.toString();
}
}
B类声明如下。
public class B implements Serializable {
float valueB;
public B(){
valueB = 10f;
}
public B(float valueB){
this.valueB = valueB;
}
}
1. 深拷贝的实现
核心思想是用ObjectOutputStream来实现类的序列化;用ObjectInputStream来实现类的反序列化。
承载输出数据,以及接受的输入数据可以是:来自内存(ByteArrayOutputStream),来自文件(FileOutputStream),来自网络(Socket.getOutputStream())等。
因为深拷贝,我们写到内存里就可以,不需要对硬盘进行访问。
我们对2个DataClass和1个A类进行深拷贝。
实现代码如下。
DataClass serial1 = new DataClass(1,1.1f,1.2);
DataClass serial2 = new DataClass(2,2.1f,2.2);
A serial3 = new A(3,3.3f);
//Serialize
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream objectOut = new ObjectOutputStream(byteOut);
objectOut.writeObject(serial1);
objectOut.writeObject(serial2);
objectOut.writeObject(serial3);
objectOut.close();
byte[] buffer = byteOut.toByteArray();
//Deserialize
ByteArrayInputStream byteIn = new ByteArrayInputStream(buffer);
ObjectInputStream objectIn = new ObjectInputStream(byteIn);
DataClass deSerial1 = (DataClass) objectIn.readObject();
DataClass deSerial2 = (DataClass) objectIn.readObject();
A deSerial3 = (A) objectIn.readObject();
System.out.println("Serialization:");
System.out.println("serial1:");
System.out.println(serial1);
System.out.println("serial2:");
System.out.println(serial2);
System.out.println("serial3:");
System.out.println(serial3);
System.out.println("Deserialization:");
System.out.println("de-serial1");
System.out.println(deSerial1);
System.out.println("de-serial2");
System.out.println(deSerial2);
System.out.println("de-serial3");
System.out.println(deSerial3);
得到结果如下:
Serialization:
serial1:
a = 1
b = 1.1
c = 1.2
serial2:
a = 2
b = 2.1
c = 2.2
serial3:
a = 3
b = 3.3
Deserialization:
de-serial1
a = 1
b = 1.1
c = 1.2
de-serial2
a = 2
b = 2.1
c = 2.2
de-serial3
a = 3
b = 3.3
进一步验证是否是深拷贝,我们进行如下修改。
serial1.a.b.valueB = 99.9f;
System.out.println("serial1:");
System.out.println(serial1);
System.out.println("de-serial1");
System.out.println(deSerial1);
得到结果如下。
serial1:
a = 1
b = 99.9
c = 1.2
de-serial1
a = 1
b = 1.1
c = 1.2
可知,确实为深拷贝。
如果一个类的某些变量不想被序列化传递,可以声明为transient. 例如:
transient A a;
这样a这个变量就不会被序列化传递。
与此同时,带来的问题是,在反序列化的时候,对于一个类会抛出空指针异常(NullPointerException). 对于8大基础数据类型,则会被初始化为初始值(0, false等)
2. Serializable简介
你以为这就是Serializable的作用?不,这只是其冰山一角的功能罢了。Serializable最大的作用是可以将数据序列化后通过网络传输。相当于Java内建的XML, json.
以DataClass(1, 1.1f, 1.2)为例,其序列化格式如下(从左到右,从上到下):
DataClass | 8-byte version number | h0 |
---|---|---|
2 | A | double |
A(1, 1.1f) | 1.2 | h1 |
其中h0和h1是句柄。
Serializable的其他用途我们下次再聊。