Java序列化是JVM的内部机制,是一种高效的对象复制机制。序列化用于在不同的JVM之间传递状态的信息,实现跨平台的Java对象传输,用于RMI的实现,也可用于保存JVM中运行对象的状态。序列化只是序列化类中非static,非transient的对象,对于方法,或使用static、transient修饰的变量则跳过序列化。需要序列化的类需要实现Serializable或Externalizable。Serializable是标记接口,实现标记接口不需要实现任何方法,只要类“标记”了该接口,就具有对象序列化的能力,但实现了Serializable接口的类不具备控制序列化过程的能力。实现Externalizable接口使得用户可以手动控制类中哪些对象需要序列化,哪些不需要。
浅复制使用Object类中的clone方法。对于基本数据类型,clone方法是复制数据,对于引用数据类型,clone方法则是复制指针。如果一个类中存在引用类型,同时,希望clone也复制该引用类型的数据,需要类中的clone方法。对于引用类的处理,前一种就是浅复制,后一种就是clone的深复制。另一种实线深复制的方式就是序列化,只要引用类型本身是可以序列化的,实现深复制是非常方便且高效的
下面是一个浅复制基本数组的实验:
public class CloneTest {
private static void printArray(int[] a) {
for(int i = 0;i<a.length;i++){
System.out.print(a[i]+" ");
}
System.out.println();
}
public static void main(String args[]) {
int[] a = new int[]{1,2,3,4,5};
int[] b = a.clone();
/*修改数组a的值*/
a[0] = 2;
a[1] = 4;
a[2] = 6;
a[3] = 8;
a[4] = 10;
System.out.println("Array b is ");
printArray(b);
System.out.println("Array a is ");
printArray(a);
//System.out.println(a==b);
}
}
实验的输出结果为:
Array b is
1 2 3 4 5
Array a is
2 4 6 8 10
对于引用类型,可以被clone的类都实现了Cloneable接口,并且实现了public Object clone() throws CloneNotSupportedException的方法(String类是特殊的,本身并没有实现该方法)。
类Student
public class Student{
private String id;
private String name;
private int classno;
public Student (String id , String name , int classno) {
this.id = id;
this.name = name;
this.classno = classno;
}
public String getID() {
return this.id;
}
public String toString() {
return this.id+" "+this.name+" "+this.classno;
}
public void setName(String name) {
this.name = name;
}
}
调用和测试类:
public class BaseClass implements Cloneable {
Student stu;
int x;
public BaseClass(Student stu , int x) {
this.stu = stu;
this.x = x;
}
public BaseClass(){
}
public void setStu(Student stu) {
this.stu = stu;
}
public void setX(int x) {
this.x = x;
}
public Object clone() throws CloneNotSupportedException {
BaseClass bc = (BaseClass)super.clone();
return bc;
}
public String toString() {
return stu.toString()+" "+x;
}
public static void main(String args[]) throws CloneNotSupportedException {
Student stu = new Student("1001","Prin",100);
BaseClass bc = new BaseClass(stu,1);
BaseClass bc2 = (BaseClass) bc.clone();
System.out.println(bc2);
/*修改stu对象的name属性,测试bc和bc2是否都指向stu*/
stu.setName("Aline");
System.out.println(bc2);
}
}
运行结果为:
1001 Prin 100 1
1001 Aline 100 1
String类没有可用的clone方法,当对象中的String属性拷贝时,直接是新建一个String对象,并把内容复制过去。当单对象是其他引用类型时,浅复制仅仅复制引用。如果需要复制引用类型,则需要让Student也实现Cloneable方法,并且修改BaseClass的clone实现。具体如下
Student类:
public class Student implements Cloneable{
......
public Object clone() throws CloneNotSupportedException {
Student stu = (Student) super.clone();
return stu;
}
......
}
BaseClass类:
public class BaseClass implements Cloneable {
public Object clone() throws CloneNotSupportedException {
BaseClass bc = (BaseClass)super.clone();
/*修改bc中的stu内容,使用Student的clone*/
bc.stu = (Student) stu.clone();
return bc;
}
}
再一次运行BaseClass的main,其结果为:
1001 Prin 100 1
1001 Prin 100 1
实现了Student的深复制。
使用Serializable来实现深复制,则Student和BaseClass都需要实现Serializable接口。
修改Student类的class声明为:public class Student implements Serializable
修改BaseClass类的class声明为:public class BaseClass implements Serializable
测试深复制的代码main方法如下:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.logging.Level;
import java.util.logging.Logger;
import newenv.Student;
/** * * @author Philis.Liu */
public class BaseClass implements Serializable {
......
public static void main(String args[]) throws CloneNotSupportedException {
/*将变量放在try-catch-finally之外*/
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
BaseClass bc = null;
BaseClass bc2 = null;
try {
/*初始化变量*/
Student stu = new Student("1001","Prin",100);
bc = new BaseClass(stu,1);
/*序列化变量到磁盘文件*/
oos = new ObjectOutputStream(new FileOutputStream("base.txt"));
oos.writeObject(bc);
/*将变量考出磁盘至内存*/
ois = new ObjectInputStream(new FileInputStream("base.txt"));
bc2 = (BaseClass)ois.readObject();
/*修改拷贝前的变量状态值*/
stu.setName("Aline");
} catch (IOException ex) {
Logger.getLogger(BaseClass.class.getName()).log(Level.SEVERE, null, ex);
} catch (ClassNotFoundException ex) {
Logger.getLogger(BaseClass.class.getName()).log(Level.SEVERE, null, ex);
} finally {
try {
/*关流*/
oos.close();
ois.close();
} catch (IOException ex) {
Logger.getLogger(BaseClass.class.getName()).log(Level.SEVERE, null, ex);
}
}
/*输出测试*/
System.out.println(bc);
System.out.println(bc2);
}
}
测试结果为:
1001 Aline 100 1
1001 Prin 100 1
深复制成功!
对于继承基类的类,最好是基类和继承类都实现了序列化,这样使用继承类类序列化基类时就不会出现变量丢失的问题。如果基类没有实现序列化,而基层类实现了序列化,那么,就需要在子类中重写writeObject和readObject方法,将基类的变量一一序列化。同时,父类需要有一个public或protected的无参构造方法。因为ObjectInputStream在初始化非Serializable的类型时,将调用该类的无参构造方法来生成一个对象,再对该对象进行赋值。
以下是没有Serializable的基类代码:
public class BaseClass {
Student stu;
int x;
public BaseClass(Student stu , int x) {
this.stu = stu;
this.x = x;
}
public BaseClass(){
}
public void setStu(Student stu) {
this.stu = stu;
}
public void setX(int x) {
this.x = x;
}
public String toString() {
return stu.toString()+" "+x;
}
以下是扩展类的代码(实现了Serializable)
public class ExtendedClass extends BaseClass implements Serializable{
String str;
public ExtendedClass(Student stu, int x, String str) {
super(stu, x);
this.str = str;
}
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
oos.writeObject(this.stu);
oos.writeInt(this.x);
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
Student stu = (Student)ois.readObject();
int x = ois.readInt();
super.setStu(stu);
super.setX(x);
}
}
以下是简单测试的代码。
public class SerialTest {
public static void main(String args[]) {
Student stu = new Student("1001","Prin",301);
ExtendedClass bc = new ExtendedClass(stu,100,"student");
try {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("base.txt"));
oos.writeObject(bc);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("base.txt"));
ExtendedClass bc2 = (ExtendedClass)ois.readObject();
System.out.println(bc2.str);
System.out.println(bc2.stu);
} catch (IOException ex) {
Logger.getLogger(SerialTest.class.getName()).log(Level.SEVERE, null, ex);
} catch (ClassNotFoundException ex) {
Logger.getLogger(SerialTest.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
测试结果为:
student
1001 Prin 301
复制成功!
readResolve用于在反序列化过程中,用一个新的对象代替反序列化的对象;writeReplace用于在序列化过程中,用一个对象代替本来序列化的对象。这两个方法的使用场景之一就是深复制过程中单例模式的保持。实现了Serializable接口的单利模式虽然能够成功复制实例,但却违反了单例模式只能有一个实例的原则,为此,需要在类中添加readResolve和writeReplace的实现。
private Object writeReplace() throws ObjectStreamException {
return this.instance;
}
private Object readResolve() throws ObjectStreamException {
return this.instance;
}
如果想要让某些变量以某种规则进行序列化,或者控制某些变量不进行序列化。如果是Serializable实现的类,则可以通过transient或static先让默认的序列化规则跳过序列化这些变量,再通过write方法对规则处理后的值进行手工序列化。另一种办法是实现Externalizable接口,该方式可以实现对序列化过程的完全控制。
以下是Externalizable的简单实验
public class ExtSerialClass implements Externalizable{
private static final long serialVersionUID = 2L;
private String str ;
private int num;
/*必须是public,必须是无参*/
public ExtSerialClass() {
System.out.println("Loading No-Arg Constructor");
}
public ExtSerialClass(String str , int num) {
this.str = str;
this.num = num;
System.out.println("Load Arg Constructor");
}
public String getStr() {
return str;
}
public int getNum() {
return num;
}
public void setStr(String str) {
this.str = str;
}
public void setNum(int num) {
this.num = num;
}
public String toString(){
return this.str + this.num;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("Carring Write External");
out.writeObject(str);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
System.out.println("Carring Read External");
this.str = (String)in.readObject();
}
public static void main(String args[]) {
ExtSerialClass esc = new ExtSerialClass("Prin",100);
System.out.println(esc);
try {
FileOutputStream ofs = new FileOutputStream("t1");
ObjectOutputStream oos = new ObjectOutputStream(ofs);
oos.writeObject(esc);
FileInputStream fis = new FileInputStream("t1");
ObjectInputStream ois = new ObjectInputStream(fis);
ExtSerialClass esc2 = (ExtSerialClass) (ois.readObject());
//if(esc2==null) System.out.println("Read Fail");
System.out.println(esc2);
} catch (FileNotFoundException ex) {
Logger.getLogger(ExtSerialClass.class.getName()).log(Level.SEVERE, null, ex);
} catch (IOException ex) {
Logger.getLogger(ExtSerialClass.class.getName()).log(Level.SEVERE, null, ex);
} catch (ClassNotFoundException ex) {
Logger.getLogger(ExtSerialClass.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
测试结果为:
Load Arg Constructor
Prin 100
Carring Write External
Loading No-Arg Constructor
Carring Read External
Prin 0
前一个的num为100,后一个为0,可见,实现了num变量的序列化控制。