文章主要切入点
Serializable接口
Java的对象序列化将那些实现了Serializable接口的对象转换成一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象。也就是,序列化是将一个对象编码成一个字节流。 反序列化是将一个字节流转换成对象。
如何序列化一个对象:首先要创建某些OutputStream对象,然后将其封装在一个ObjectOutputStream对象内。这时,只需要调用writeObject()即可将对象序列化,并将其发送给OutputStream。要反向进行该过程,需要将一个InputStream封装在ObjectInputStream内,然后调用readObject()。和往常一样,我们最后获得的是一个引用,它指向一个向上转型的Object,所以必须向下转型才能直接设置它们。
示例:
class Data implements Serializable {
private int n;
public Data(int n) {
this.n = n;
}
@Override
public String toString() {
return Integer.toString(n);
}
}
public class Worm implements Serializable {
private static Random rand = new Random(47);
private Data[] d = {new Data(rand.nextInt(10)),
new Data(rand.nextInt(10)),
new Data(rand.nextInt(10))};
private Worm next;
private char c;
public Worm(int i, char x) {
System.out.println("Worm constructor:" + i);
c = x;
if (--i > 0) {
next = new Worm(i, (char) (x + 1));
}
}
public Worm() {
System.out.println("Default constructor");
}
@Override
public String toString() {
StringBuilder result = new StringBuilder(":");
result.append(c);
result.append("(");
for (Data dat : d) {
result.append(dat);
}
result.append(")");
if (next != null) {
result.append(next);
}
return result.toString();
}
public static void main(String[] args) throws ClassNotFoundException, IOException {
Worm w = new Worm(6, 'a');
System.out.println("w = " + w);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("worm.out"));
out.writeObject("Worm storage\n");
out.writeObject(w);
// 刷新输出
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("worm.out"));
String s = (String) in.readObject();
Worm w2 = (Worm) in.readObject();
System.out.println(s + "w2 = " + w2);
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream out2 = new ObjectOutputStream(bout);
out2.writeObject("Worm storage\n");
out2.writeObject(w);
out2.flush();
ObjectInputStream in2 = new ObjectInputStream(new ByteArrayInputStream(bout.toByteArray()));
s = (String) in2.readObject();
Worm w3 = (Worm) in2.readObject();
System.out.println(s + "w3 = " + w3);
}
}
output:
Worm constructor:6
Worm constructor:5
Worm constructor:4
Worm constructor:3
Worm constructor:2
Worm constructor:1
w = :a(853):b(119):c(802):d(788):e(199):f(881)
Worm storage
w2 = :a(853):b(119):c(802):d(788):e(199):f(881)
Worm storage
w3 = :a(853):b(119):c(802):d(788):e(199):f(881)
创建一个Serializable类,它包含一个对第二个Serializable类的对象的引用。将其序列化到硬盘上,然后可以正确地恢复它。
import java.io.*;
import java.util.Random;
class A implements Serializable {
private int i;
A(int i) {
this.i = i;
}
@Override
public String toString() {
return "A:" + i;
}
}
class B implements Serializable {
private char c;
private A a;
B(char c, A a) {
this.c = c;
this.a = a;
}
@Override
public String toString() {
return "B:" + c + " " + a;
}
}
public class SerializationTest implements Serializable {
private static Random rand = new Random();
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 序列化
B b = new B('x', new A(rand.nextInt(100)));
System.out.println("b:" + b);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("STest.out"));
out.writeObject(b);
out.close();
// 反序列化
ObjectInputStream in = new ObjectInputStream(new FileInputStream("STest.out"));
B b2 = (B) in.readObject();
System.out.println("b2:" + b2);
}
}
打开文件和读取对象中的内容都需要对应的Class对象;如果Java虚拟机找不到*.class对象,就会报ClassNotFundException异常。必须保证Java虚拟机能找到相关的.class文件。
Externalizable接口
默认的序列化机制使用方式如上所示。如果有特殊的需要,我们可通过Externalizable接口——代替实现Serializable接口——来对序列化过程进行控制。Externalizabl接口继承Serializable接口,增加了两个方法:writeExternal(ObjectOutput out)和readExternal(ObjectInput in)。这两个方法会在序列化和反序列化还原的过程中被自动调用。
示例:
import java.io.*;
class Blip1 implements Externalizable {
public Blip1() {
System.out.println("Blip1 Constructor");
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("Blip1.writeExternal");
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
System.out.println("Blip1.readExternal");
}
}
class Blip2 implements Externalizable {
/**
* 不是公共构造器
*/
Blip2() {
System.out.println("Blip2 Constructor");
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("Blip2.writeExternal");
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
System.out.println("Blip2.writeExternal");
}
}
public class Blips {
public static void main(String[] args) throws IOException, ClassNotFoundException {
System.out.println("Constructing objects:");
Blip1 b1 = new Blip1();
Blip2 b2 = new Blip2();
// 序列化
ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("BlipCheck.out"));
System.out.println("Saving objects:");
o.writeObject(b1);
o.writeObject(b2);
o.close();
// 反序列化
ObjectInputStream in = new ObjectInputStream(new FileInputStream("BlipCheck.out"));
System.out.println("Recovering b1:");
b1 = (Blip1)in.readObject();
// b2不是公共构造器,恢复会产生异常
System.out.println("Recovering b2:");
b2 = (Blip2)in.readObject();
}
}
output:
Constructing objects:
Blip1 Constructor
Blip2 Constructor
Saving objects:
Blip1.writeExternal
Blip2.writeExternal
Recovering b1:
Blip1 Constructor
Blip1.readExternal
// 将Blip2构造器改为public
Recovering b2:
Blip2 Constructor
Blip2.writeExternal
Externalizable对象与Serializable对象的不同:
- 对于Serializable对象,对象完全以它存储的二进制位为基础来构造,而不是调用构造器。
- 对于Externalizable对象,所有普通的默认构造器都会被调用,然后调用readExternal( )方法。
- 所有默认的构造器都会被调用,才能使Externalizable对象产生正确的行为。
完整示例:
import java.io.*;
public class Blip3 implements Externalizable {
private int i;
private String s;
public Blip3() {
System.out.println("Blip3 Constructor");
}
public Blip3(int i, String s) {
System.out.println("Blip3(int i, String s)");
this.i = i;
this.s = s;
}
@Override
public String toString() {
return s + i;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("Blip3.writeExternal");
out.writeObject(s);
out.writeInt(i);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
System.out.println("Blip3.readExternal");
s = (String) in.readObject();
i = in.readInt();
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
System.out.println("Constructing objects:");
Blip3 b3 = new Blip3(47, "A String ");
System.out.println(b3);
// 序列化
ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("Blip3.out"));
System.out.println("Saving object:");
o.writeObject(b3);
o.close();
// 取出来
ObjectInputStream in = new ObjectInputStream(new FileInputStream("Blip3.out"));
System.out.println("Recovering b3:");
b3 = (Blip3) in.readObject();
System.out.println(b3);
}
}
output:
Constructing objects:
Blip3(int i, String s)
A String 47
Saving object:
Blip3.writeExternal
Recovering b3:
Blip3 Constructor
Blip3.readExternal
A String 47
用途:实现Externalizable接口的类,在默认情况下不保存它们的任何字段。类实现Externalizable,可以防止对象的敏感部分被序列化。没有任何东西可以自动序列化,并且可以在writeExternal()内部只对所需部分进行显式的序列化。
transient(瞬时)关键字
如果我们操作的是一个Serializable对象,那么所有序列化操纵都会自动进行。如果某个字段不想序列化,那么用transient关键字进行修饰就可以了。
import java.io.*;
import java.util.Date;
import java.util.concurrent.TimeUnit;
public class Logon implements Serializable {
private Date date = new Date();
private String username;
private transient String password;
public Logon(String username, String password) {
this.username = username;
this.password = password;
}
@Override
public String toString() {
return "logon info:\n username:" + username +
"\n date:" + date + "\n password:" + password;
}
public static void main(String[] args) throws Exception {
Logon a = new Logon("Hulk", "myLittlePony");
System.out.println("logon a = " + a);
ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("Logon.out"));
o.writeObject(a);
o.close();
TimeUnit.SECONDS.sleep(1);
ObjectInputStream in = new ObjectInputStream(new FileInputStream("Logon.out"));
System.out.println("Recovering object at " + new Date());
a = (Logon) in.readObject();
System.out.println("logon a = " + a);
}
}
output:
logon a = logon info:
username:Hulk
date:Thu May 16 10:43:19 CST 2019
password:myLittlePony
Recovering object at Thu May 16 10:43:20 CST 2019
logon a = logon info:
username:Hulk
date:Thu May 16 10:43:19 CST 2019
password:null
static
static字段的序列化需要自己去动手实现。
序列化代码:
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
abstract class Shape implements Serializable {
public static final int RED = 1, BLUE = 2, GREEN = 3;
private int xPos, yPos, dimension;
private static Random rand = new Random(47);
private static int counter = 0;
public abstract void setColor(int newColor);
public abstract int getColor();
public Shape(int xVal, int yVal, int dim) {
xPos = xVal;
yPos = yVal;
dimension = dim;
}
@Override
public String toString() {
return getClass() + " color[" + getColor() + "] xPos[" + xPos + "] yPos[" + yPos + "] dim[" + dimension + "]\n";
}
public static Shape randomFactory() {
int xVal = rand.nextInt(100);
int yVal = rand.nextInt(100);
int dim = rand.nextInt(100);
switch (counter++ % 3) {
default:
case 0:
return new Circle(xVal, yVal, dim);
case 1:
return new Square(xVal, yVal, dim);
case 2:
return new Line(xVal, yVal, dim);
}
}
}
class Circle extends Shape {
private static int color = RED;
//public static void serializeStaticState(ObjectOutputStream os)
// throws IOException {
// os.writeInt(color);
//}
//
//public static void deserializeStaticState(ObjectInputStream os)
// throws IOException {
// color = os.readInt();
//}
public Circle(int xVal, int yVal, int dim) {
super(xVal, yVal, dim);
}
@Override
public void setColor(int newColor) {
color = newColor;
}
@Override
public int getColor() {
return color;
}
}
class Square extends Shape {
private static int color;
//public static void serializeStaticState(ObjectOutputStream os)
// throws IOException {
// os.writeInt(color);
//}
//
//public static void deserializeStaticState(ObjectInputStream os)
// throws IOException {
// color = os.readInt();
//}
public Square(int xVal, int yVal, int dim) {
super(xVal, yVal, dim);
color = RED;
}
@Override
public void setColor(int newColor) {
color = newColor;
}
@Override
public int getColor() {
return color;
}
}
class Line extends Shape {
private static int color = RED;
public static void serializeStaticState(ObjectOutputStream os) throws IOException {
os.writeInt(color);
}
public static void deserializeStaticState(ObjectInputStream os) throws IOException {
color = os.readInt();
}
public Line(int xVal, int yVal, int dim) {
super(xVal, yVal, dim);
}
@Override
public void setColor(int newColor) {
color = newColor;
}
@Override
public int getColor() {
return color;
}
}
public class StoreCADState {
public static void main(String[] args) throws IOException {
List<Shape> shapes = new ArrayList<>();
for (int i = 0; i < 10; i++) {
shapes.add(Shape.randomFactory());
}
// 将所有静态颜色设置为绿色
for (int i = 0; i < 10; i++) {
shapes.get(i).setColor(Shape.GREEN);
}
// 保存状态向量
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("CADState.out"));
//Circle.serializeStaticState(out);
//Square.serializeStaticState(out);
Line.serializeStaticState(out);
out.writeObject(shapes);
System.out.println(shapes);
}
}
output:
[class Circle color[3] xPos[58] yPos[55] dim[93]
, class Square color[3] xPos[61] yPos[61] dim[29]
, class Line color[3] xPos[68] yPos[0] dim[22]
, class Circle color[3] xPos[7] yPos[88] dim[28]
, class Square color[3] xPos[51] yPos[89] dim[9]
, class Line color[3] xPos[78] yPos[98] dim[61]
, class Circle color[3] xPos[20] yPos[58] dim[16]
, class Square color[3] xPos[40] yPos[11] dim[22]
, class Line color[3] xPos[4] yPos[83] dim[6]
, class Circle color[3] xPos[75] yPos[10] dim[42]
]
反序列化代码:
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.List;
public class RecoverCADState {
public static void main(String[] args) throws Exception {
ObjectInputStream in = new ObjectInputStream(new FileInputStream("CADState.out"));
//Circle.deserializeStaticState(in);
//Square.deserializeStaticState(in);
Line.deserializeStaticState(in);
List<Shape> shapes = (List<Shape>) in.readObject();
System.out.println(shapes);
}
}
output:
[class Circle color[1] xPos[58] yPos[55] dim[93]
, class Square color[0] xPos[61] yPos[61] dim[29]
, class Line color[3] xPos[68] yPos[0] dim[22]
, class Circle color[1] xPos[7] yPos[88] dim[28]
, class Square color[0] xPos[51] yPos[89] dim[9]
, class Line color[3] xPos[78] yPos[98] dim[61]
, class Circle color[1] xPos[20] yPos[58] dim[16]
, class Square color[0] xPos[40] yPos[11] dim[22]
, class Line color[3] xPos[4] yPos[83] dim[6]
, class Circle color[1] xPos[75] yPos[10] dim[42]
]
从输出的结果可以知道,static数据根本没有被序列化,只有Line.class中显示的是正确的。所以想序列化static的值,必须动手去实现。将上面的代码中注视的代码恢复,就可以得到正确的值了!