文章目录

  • Serializable接口
    • ObjectOutputStream#writeObject() 与 NotSerializableException
    • ObjectInputStream#readObject() 与 ClassNotFoundException
    • Serializable接口的类,不会调用任何构造器
    • 非基本类型成员变量,需实现Serializable接口
    • 泛型类型的成员变量,需实现Serializable接口
    • 序列化保存所有对象网
  • transient与writeObject/readObject
    • transient关键字
    • writeObject/readObject方法
    • HashMap的序列化过程
  • Externalizable接口
    • 必须有public的默认构造器
  • serialVersionUID
  • 未实现Serializable的父类,不参与序列化
  • 父类实现了Serializable接口,子类也会是Serializable的
  • 静态变量不参与到序列化过程中
  • Serializable与深拷贝/浅拷贝
  • 其他

Serializable接口

public interface Serializable {}

JDK源码里,Serializable接口只是一个空接口(标记接口),实际上,实现了Serializable接口的类,其序列化和反序列化过程,都是全自动的。

ObjectOutputStream#writeObject() 与 NotSerializableException

一个类必须实现Serializable接口才能序列化它的对象,不然调用ObjectOutputStream的writeObject方法会报错。

import java.io.*;class book {private String bookName;private int bookNum;public book(String bookName,int bookNum) {this.bookName = bookName;this.bookNum = bookNum;}
}public class test {public static void write() throws IOException {ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("book.out"));out.writeObject(new book("如何实现财务自由",1));}public static void main(String[] args) throws IOException, ClassNotFoundException {write();}
}

如上程序,运行后在ObjectOutputStream.writeObject()处报错Exception in thread “main” java.io.NotSerializableException: book。

当类定义加上Serializable后,程序不再报错:

class book implements Serializable {private String bookName;private int bookNum;public book(String bookName,int bookNum) {this.bookName = bookName;this.bookNum = bookNum;}
}

ObjectInputStream#readObject() 与 ClassNotFoundException

在运行下面程序之前,先把图中的book.class给删掉。

import java.io.*;public class test1 {public static void read() throws IOException, ClassNotFoundException {ObjectInputStream in = new ObjectInputStream(new FileInputStream("book.out"));book b = (book) in.readObject();System.out.println(b);}public static void main(String[] args) throws IOException, ClassNotFoundException {read();}
}

如上程序,运行后在ObjectInputStream.readObject()处报错Exception in thread “main” java.lang.ClassNotFoundException: book。注意报错是在readObject方法里,而与强转类型无关。

Serializable接口的类,不会调用任何构造器

需要注意的是,实现了Serializable接口的类的对象,在序列化的过程中,没有调用过任何构造器,包括默认构造器。

import java.io.*;class writer implements Serializable{String writerName;int age;writer(String writerName, int age) {this.writerName = writerName;this.age = age;}
}class book implements Serializable {private String bookName;private int bookNum;private writer w;public book() {System.out.println("这是默认构造器");}public book(String bookName,int bookNum, writer w) {System.out.println("这是有参构造器");this.bookName = bookName;this.bookNum = bookNum;this.w = w;}}public class test {public static void write() throws IOException {ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("book.out"));// 只有下面new的时候,才调用了有参构造器一下out.writeObject(new book("如何实现财务自由",1, new writer("巴菲特",56)));  }public static void read() throws IOException, ClassNotFoundException {ObjectInputStream in = new ObjectInputStream(new FileInputStream("book.out"));book b = (book) in.readObject();  //这里并没有调用任何构造器System.out.println(b);}public static void main(String[] args) throws IOException, ClassNotFoundException {write();read();}
}/*输出:
这是有参构造器
book@27d6c5e0
*/

通过输出可以看出,反序列化的过程中,任何构造器都没有调用过。

非基本类型成员变量,需实现Serializable接口

在最开始的例子中,book类有两个成员变量,其中有个String类型的成员变量,很明显,String不是一个基本类型。那让我们来看一下String的类定义:

public final class Stringimplements java.io.Serializable, Comparable<String>, CharSequence {

发现作为非基本类型的String确实实现了Serializable接口,但这不足以证明“非基本类型成员变量,需实现Serializable接口”。所以我们再反向验证一下:

import java.io.*;class writer {  //需实现Serializable接口String writerName;int age;writer(String writerName, int age) {this.writerName = writerName;this.age = age;}
}class book implements Serializable {private String bookName;private int bookNum;private writer w;  //添加一个writer成员public book(String bookName,int bookNum, writer w) {this.bookName = bookName;this.bookNum = bookNum;this.w = w;}}public class test {public static void write() throws IOException {ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("book.out"));out.writeObject(new book("如何实现财务自由",1, new writer("巴菲特",56)));}public static void main(String[] args) throws IOException, ClassNotFoundException {write();}
}
  • 现令book类新增一个成员变量为自定义类writer,且writer没有实现Serializable接口。
  • 程序运行后,报错Exception in thread “main” java.io.NotSerializableException: writer。
  • 为writer类实现Serializable接口后,程序能运行成功。

泛型类型的成员变量,需实现Serializable接口

import java.io.*;class Item {  //需实现Serializable接口String itemName;int quality;Item(String itemName, int quality) {this.itemName = itemName;this.quality = quality;}
}class Factory<I> implements Serializable{I item;Factory(I item) {this.item = item;}
}public class test2 {public static void main(String[] args) throws IOException {ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("book.out"));out.writeObject(new Factory<Item>(new Item("共享充电宝",2)));}
}

同样的,成员变量的类型为泛型类型,这个泛型类型也得实现Serializable接口。上面程序直接运行报错,但Item类加上Serializable接口后,则可运行成功。

import java.io.*;class Key {}  //需实现Serializable接口class MyHashSet<K> implements Serializable{static class Node<K> {  //需实现Serializable接口K key;Node(K key) {this.key = key;}}Node<K>[] nodes;MyHashSet(K... keys ) {nodes = new Node[keys.length];int index = 0;for(K k : keys) {nodes[index++] = new Node<K>(k);}}
}public class test2 {public static void main(String[] args) throws IOException {ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("book.out"));out.writeObject(new MyHashSet<Key>( new Key(), new Key()));}
}

另一个例子,同样的,直接运行会报错,除非你把两个类定义(MyHashSet$NodeKey)添加上Serializable接口。

import java.io.*;
import java.util.*;public class test3 {static class Key {}  //需实现Serializable接口static class Value {}  //需实现Serializable接口public static void main(String[] args) throws IOException {HashMap<Key,Value> map = new HashMap<Key,Value>();map.put(new Key(), new Value());ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("book.out"));out.writeObject(map);}
}

直接使用HashMap,两个泛型类型是自定义的类,同样没有实现Serializable接口。直接运行会报错。

序列化保存所有对象网

一个对象包含若干个成员变量,成员变量又可能包含它的成员变量,这些持有的引用最终形成了一个无形的对象网,但Java的序列化过程却能把整个对象网络包含的所有对象全部序列化下来。

import java.io.*;
import java.util.*;class Data implements Serializable{private int n;public Data(int n) { this.n = n; }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;// Value of i == number of segmentspublic Worm(int i, char x) {print("Worm constructor: " + i);c = x;if(--i > 0)next = new Worm(i, (char)(x + 1));}public Worm() {print("Default constructor");}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 print(Object o) { System.out.println(o); }public static void main(String[] args)throws ClassNotFoundException, IOException {Worm w = new Worm(6, 'a');print("w = " + w);ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("worm.out"));out.writeObject("Worm storage\n");out.writeObject(w);out.close(); // Also flushes outputObjectInputStream in = new ObjectInputStream(new FileInputStream("worm.out"));String s = (String)in.readObject();Worm w2 = (Worm)in.readObject();print(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();print(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)
*///:~
  • Worm类的构造器调用new,这说明是构造器的递归调用。不过有着if(--i > 0)的递归终点,所以整个递归过程只会执行 主函数中给定的数字的次数。
  • Worm对象有一个Worm成员变量,它们之间就形成了一个对象网。而且Worm对象还有着Data[]成员,整个对象网络包含的对象还不少。
  • 但即使是这样复杂的对象网络,Java的自动化序列化过程也能把所有对象都保存下来。这一点可以从打印结果看出来。
  • 注意到,第二次的序列化和反序列化,使用到了ByteArrayOutputStream/ByteArrayInputStream,这和之前没有区别,只是之前的过程是把字节流写入到了文件里,现在是把字节流写入到了内存中。

transient与writeObject/readObject

transient关键字

对象一旦经过序列化,即使是私有属性也会执行序列化,由于反射技术的存在,私有属性等同于裸奔在所有人面前。所以,为了保证某个属性根本不会执行序列化步骤,必须使用transient关键字。

import java.io.*;class book implements Serializable {private String bookName;private int bookNum;private transient String writerName;  //此属性不参与序列化@Overridepublic String toString() {return "book{" +"bookName='" + bookName + '\'' +", bookNum=" + bookNum +", writerName='" + writerName + '\'' +'}';}public book(String bookName,int bookNum,String writerName) {this.bookName = bookName;this.bookNum = bookNum;this.writerName = writerName;}}public class test {public static void write() throws IOException {ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("book.out"));out.writeObject(new book("如何实现财务自由",1, "巴菲特"));}public static void read() throws IOException, ClassNotFoundException {ObjectInputStream in = new ObjectInputStream(new FileInputStream("book.out"));book b = (book) in.readObject();System.out.println(b);}public static void main(String[] args) throws IOException, ClassNotFoundException {write();read();}
}/*输出:
book{bookName='如何实现财务自由', bookNum=1, writerName='null'}
*/

可见,在整个透明的序列化过程之后,读取出来的writerName为null。这说明带有transient关键字的成员变量,不会参与到序列化的过程中,所以,在反序列化之后,transient关键字的成员变量也只有初始化的值。

writeObject/readObject方法

  • 我们现在知道,当一个类实现了Serializable接口,那么在调用ObjectOutputStream.writeObject(该类的对象)时,整个序列化过程会全自动进行:序列化每个成员变量,如果成员变量又持有了别的对象的引用,那么这些对象又会被序列化,当然这个过程会除开带有transient关键字的成员变量
  • 但如果我们提供了writeObject/readObject方法后,那么在调用ObjectOutputStream.writeObject(该类的对象)时,将不再执行这个全自动过程,转而执行你自己实现的这两个方法。注意这两个方法的签名必须和如下签名一模一样,这有点神奇,其实它是使用到了反射技术来检测方法的签名。
    private void writeObject(java.io.ObjectOutputStream s)throws IOException {}private void readObject(java.io.ObjectInputStream s)throws IOException, ClassNotFoundException {}
  • 如果你已经提供了这两个特定签名的方法,但你还是想使用一下 序列化的全自动过程,那么你可以在writeObject/readObject方法里,调用s.defaultWriteObject();s.defaultReadObject();
import java.io.*;class book implements Serializable {private String bookName;private int bookNum;private transient String writerName;@Overridepublic String toString() {return "book{" +"bookName='" + bookName + '\'' +", bookNum=" + bookNum +", writerName='" + writerName + '\'' +'}';}public book(String bookName,int bookNum,String writerName) {this.bookName = bookName;this.bookNum = bookNum;this.writerName = writerName;}private void writeObject(java.io.ObjectOutputStream s) throws IOException {s.defaultWriteObject();s.writeObject("序列化后:"+writerName);}private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {s.defaultReadObject();writerName = (String) s.readObject();}
}public class test {public static void write() throws IOException {ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("book.out"));out.writeObject(new book("如何实现财务自由",1, "巴菲特"));}public static void read() throws IOException, ClassNotFoundException {ObjectInputStream in = new ObjectInputStream(new FileInputStream("book.out"));book b = (book) in.readObject();System.out.println(b);}public static void main(String[] args) throws IOException, ClassNotFoundException {write();read();}
}}/*输出:
book{bookName='如何实现财务自由', bookNum=1, writerName='序列化后:巴菲特'}
*/
  • 因为提供了两种特定签名的writeObject/readObject方法,所以不会走 全自动序列化过程了,转而执行你自己的逻辑。
  • 在writeObject/readObject方法里,首先调用了s.defaultWriteObject();s.defaultReadObject();,所以还是执行了 全自动序列化过程,但带有transient关键字的属性没有序列化进去。
  • 为了保证带有transient关键字的属性能够参与序列化,需要自己实现逻辑,即自己调用ObjectOutputStream.writeObject()ObjectInputStream.readObject()

HashMap的序列化过程

如下为HashMap的序列化过程:

    transient Node<K,V>[] table;  //存储节点的table,但它又是必须序列化的成员private void writeObject(java.io.ObjectOutputStream s)throws IOException {int buckets = capacity();// Write out the threshold, loadfactor, and any hidden stuffs.defaultWriteObject();  //执行自动化过程s.writeInt(buckets);  //写入容量s.writeInt(size);  //写入大小internalWriteEntries(s);}void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException {Node<K,V>[] tab;if (size > 0 && (tab = table) != null) {for (int i = 0; i < tab.length; ++i) {  //遍历整个table数组for (Node<K,V> e = tab[i]; e != null; e = e.next) {s.writeObject(e.key);  //先写入当前节点的keys.writeObject(e.value);  //再写入当前节点的value}}}}

如下为HashMap的反序列化过程:

//注意,该函数我省略了部分代码private void readObject(java.io.ObjectInputStream s)throws IOException, ClassNotFoundException {s.defaultReadObject();  //执行自动化过程s.readInt();                // 读出容量,但直接忽略掉int mappings = s.readInt(); // 读出大小// Read the keys and values, and put the mappings in the HashMapfor (int i = 0; i < mappings; i++) {  //遍历整个table数组@SuppressWarnings("unchecked")K key = (K) s.readObject();  //先读出当前节点的key@SuppressWarnings("unchecked")V value = (V) s.readObject();  //先读出当前节点的valueputVal(hash(key), key, value, false, false);}}

Externalizable接口

public interface Externalizable extends java.io.Serializable {void writeExternal(ObjectOutput out) throws IOException;void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}

看来这个Externalizable不再是个空接口了,人家可是有方法的。

必须有public的默认构造器

import java.io.*;class writer implements Serializable{String writerName;int age;@Overridepublic String toString() {return "writer{" +"writerName='" + writerName + '\'' +", age=" + age +'}';}writer(String writerName, int age) {this.writerName = writerName;this.age = age;}
}class book implements Externalizable {private String bookName;private int bookNum;private writer w;@Overridepublic String toString() {return "book{" +"bookName='" + bookName + '\'' +", bookNum=" + bookNum +", writer='" + w + '\'' +'}';}// public book () {}   //解开注释以运行成功public book(String bookName,int bookNum,writer w) {this.bookName = bookName;this.bookNum = bookNum;this.w = w;}@Overridepublic void writeExternal(ObjectOutput out) throws IOException {out.writeObject(bookName);out.writeInt(bookNum);out.writeObject(w);}@Overridepublic void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {bookName = (String) in.readObject();bookNum = in.readInt();w = (writer) in.readObject();}
}public class test {public static void write() throws IOException {ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("book.out"));out.writeObject(new book("如何实现财务自由",1, new writer("巴菲特",56)));}public static void read() throws IOException, ClassNotFoundException {ObjectInputStream in = new ObjectInputStream(new FileInputStream("book.out"));book b = (book) in.readObject();System.out.println(b);}public static void main(String[] args) throws IOException, ClassNotFoundException {write();read();}
}
  • 当你直接运行此程序,发现会报错java.io.InvalidClassException: book; no valid constructor,除非你提供一个public的默认构造器。
  • 当你实现的是Externalizable接口时,所有成员都得自己实现序列化的逻辑,不然就不会参与序列化之中。
  • transient关键字就毫无意义了,特定签名的writeObject/readObject方法也毫无意义了。s.defaultWriteObject();s.defaultReadObject();也不能使用了,因为参数变成了ObjectOutputObjectInput接口了。
  • 而且注意,成员变量在定义时的初始化也会执行。(这好像是废话,因为肯定得先执行字段定义时初始化,再调用构造器)。修改部分代码以验证:
class writer implements Serializable{String writerName;int age;@Overridepublic String toString() {return "writer{" +"writerName='" + writerName + '\'' +", age=" + age +'}';}writer(String writerName, int age) {System.out.println("调用了writer的构造器");this.writerName = writerName;this.age = age;}
}class book implements Externalizable {private String bookName;private int bookNum;private writer w = new writer("索罗斯",59);  //字段定义时的初始化
...
/*输出:
调用了writer的构造器
调用了writer的构造器
调用了writer的构造器   //第三次打印,是反序列化时,调用默认构造器之前打印出来的
book{bookName='如何实现财务自由', bookNum=1, writer='writer{writerName='巴菲特', age=56}'}
*/

serialVersionUID

当你实现了Serializable接口,你需要添加一个静态变量serialVersionUID,这个静态变量是用来验证版本一致性的,当传来的字节流中的serialVersionUID,和本地class文件的serialVersionUID不一致时,反序列化就会报错。

如果你没有显式地添加静态变量serialVersionUID,那么程序会为你隐式地自动生成一个serialVersionUID,但这却带来了极大的隐患。

import java.io.*;class book implements Serializable {private String bookName;private int bookNum;// private int a;public book(String bookName,int bookNum) {this.bookName = bookName;this.bookNum = bookNum;}
}public class test {public static void write() throws IOException {ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("book.out"));out.writeObject(new book("如何实现财务自由",1));}public static void main(String[] args) throws IOException, ClassNotFoundException {write();}
}

如上程序执行完毕后,取消掉private int a;的注释。然后再执行如下代码:

import java.io.*;public class test1 {public static void read() throws IOException, ClassNotFoundException {new book("打工是不可能打工的",1);ObjectInputStream in = new ObjectInputStream(new FileInputStream("book.out"));book b = (book) in.readObject();System.out.println(b);}public static void main(String[] args) throws IOException, ClassNotFoundException {read();}
}
  • new book("打工是不可能打工的",1);这里必须执行,不然不会更新本地的class文件。由于book多了一个变量,serialVersionUID发生了改变。
  • 运行后报错:java.io.InvalidClassException: book; local class incompatible: stream classdesc serialVersionUID = 3988735506709559426, local class serialVersionUID = 6344372820327052271
  • 同样的,Externalizable接口,也最好给定serialVersionUID,不然可能会发生同样的错误。

未实现Serializable的父类,不参与序列化

import java.io.*;
import java.util.*;class Father {String FatherName;public Father(String fatherName) {FatherName = fatherName;}
}class Son extends Father implements Serializable {String SonName;public Son(String fatherName, String sonName) {super(fatherName);SonName = sonName;}@Overridepublic String toString() {return "Son{" +"FatherName='" + FatherName + '\'' +", SonName='" + SonName + '\'' +'}';}
}public class test4 {public static void main(String[] args) throws IOException, ClassNotFoundException {ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.out"));out.writeObject(new Son("爸爸","儿子"));ObjectInputStream in = new ObjectInputStream(new FileInputStream("person.out"));Son s = (Son) in.readObject();System.out.println(s);}
}
  • 如上代码,父类并没有实现Serializable接口,直接运行时,会报错java.io.InvalidClassException: Son; no valid constructor,看来是没有默认构造器闹的。
  • 当你给如上代码的Father类加上implements Serializable时,程序就能直接运行成功了。且打印结果为Son{FatherName='爸爸', SonName='儿子'}
import java.io.*;
import java.util.*;class Father {String FatherName;public Father() {}public Father(String fatherName) {FatherName = fatherName;}
}class Son extends Father implements Serializable {String SonName;public Son() {super();}public Son(String fatherName, String sonName) {super(fatherName);SonName = sonName;}@Overridepublic String toString() {return "Son{" +"FatherName='" + FatherName + '\'' +", SonName='" + SonName + '\'' +'}';}
}public class test4 {public static void main(String[] args) throws IOException, ClassNotFoundException {ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.out"));out.writeObject(new Son("爸爸","儿子"));ObjectInputStream in = new ObjectInputStream(new FileInputStream("person.out"));Son s = (Son) in.readObject();System.out.println(s);}
}
  • 经过为父类和子类都添加上默认构造器后,程序能够运行成功。但注意,默认构造器可以不是public的了,private都可以。
  • 打印结果为Son{FatherName='null', SonName='儿子'},可见FatherName为null跟序列化没啥关系,都是因为你默认构造器啥也没干造成的。
  • 因为父类没有实现Serializable接口,所以程序是不会序列化父类对象的,但一个子类对象存在的前提就是得先有父类对象,所以就强制让你加一个子类的默认构造器,而子类的任何构造器都得调用到父类的构造器,所以,这样就保证了父类构造器能被调用。

父类实现了Serializable接口,子类也会是Serializable的

这个好像是废话,总之,从继承树上来说,子类是间接继承到Serializable了,所以子类肯定也可以序列化的。下一章的Shape和它的三个子类就体现了这一点。

静态变量不参与到序列化过程中

import java.io.*;
import java.util.*;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;}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 Circle(int xVal, int yVal, int dim) {super(xVal, yVal, dim);}public void setColor(int newColor) { color = newColor; }public int getColor() { return color; }
}class Square extends Shape {private static int color;public Square(int xVal, int yVal, int dim) {super(xVal, yVal, dim);color = RED;}public void setColor(int newColor) { color = newColor; }public int getColor() { return color; }
}class Line extends Shape {private static int color = RED;public static voidserializeStaticState(ObjectOutputStream os)throws IOException { os.writeInt(color); }public static voiddeserializeStaticState(ObjectInputStream os)throws IOException { color = os.readInt(); }public Line(int xVal, int yVal, int dim) {super(xVal, yVal, dim);}public void setColor(int newColor) { color = newColor; }public int getColor() { return color; }
}public class StoreCADState {public static void main(String[] args) throws Exception {List<Shape> shapes = new ArrayList<Shape>();// Make some shapes:for(int i = 0; i < 10; i++)shapes.add(Shape.randomFactory());// Set all the static colors to GREEN:for(int i = 0; i < 10; i++)((Shape)shapes.get(i)).setColor(Shape.GREEN);List<Class<? extends Shape>> shapeTypes =new ArrayList<Class<? extends Shape>>();// Add references to the class objects:shapeTypes.add(Circle.class);shapeTypes.add(Square.class);shapeTypes.add(Line.class);// Save the state vector:ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("CADState.out"));out.writeObject(shapeTypes);Line.serializeStaticState(out);out.writeObject(shapes);// Display the shapes:System.out.println(shapes);}
} /* Output:
[class Circlecolor[3] xPos[58] yPos[55] dim[93]
, class Squarecolor[3] xPos[61] yPos[61] dim[29]
, class Linecolor[3] xPos[68] yPos[0] dim[22]
, class Circlecolor[3] xPos[7] yPos[88] dim[28]
, class Squarecolor[3] xPos[51] yPos[89] dim[9]
, class Linecolor[3] xPos[78] yPos[98] dim[61]
, class Circlecolor[3] xPos[20] yPos[58] dim[16]
, class Squarecolor[3] xPos[40] yPos[11] dim[22]
, class Linecolor[3] xPos[4] yPos[83] dim[6]
, class Circlecolor[3] xPos[75] yPos[10] dim[42]
]
*///:~
  • 这个例子来自Java编程思想,稍作改动。简单介绍一下就是:有个Shape父类,其有三个子类,这三个子类都新增了一个静态变量color,此代码把三个子类的Class对象序列化到文件,以观察反序列后静态变量。
  • 静态变量color会在字段定义就初始化,紧接着会被静态代码块修改(代码中没有静态代码块)。再然后,就是当构造器调用或函数调用时,也会改变静态变量(提供的成员方法setColor可修改,Square的构造器里会修改,Line的静态方法deserializeStaticState也会修改)。
  • 在调用for(int i = 0; i < 10; i++) ((Shape)shapes.get(i)).setColor(Shape.GREEN);的循环后,三个子类的静态变量color都会被修改为3,以至于在之后的打印结果里,所有静态变量都是3。在这个循环后,我们将Class对象放入List,然后将Class对象的List序列化,以观察反序列化后,序列化之前静态变量的修改是否能体现。
import java.io.*;
import java.util.*;public class RecoverCADState {@SuppressWarnings("unchecked")public static void main(String[] args) throws Exception {ObjectInputStream in = new ObjectInputStream(new FileInputStream("CADState.out"));// Read in the same order they were written:List<Class<? extends Shape>> shapeTypes =(List<Class<? extends Shape>>)in.readObject();Line.deserializeStaticState(in);List<Shape> shapes = (List<Shape>)in.readObject();System.out.println(shapes);}
} /* Output:
[class Circlecolor[1] xPos[58] yPos[55] dim[93]
, class Squarecolor[0] xPos[61] yPos[61] dim[29]
, class Linecolor[3] xPos[68] yPos[0] dim[22]
, class Circlecolor[1] xPos[7] yPos[88] dim[28]
, class Squarecolor[0] xPos[51] yPos[89] dim[9]
, class Linecolor[3] xPos[78] yPos[98] dim[61]
, class Circlecolor[1] xPos[20] yPos[58] dim[16]
, class Squarecolor[0] xPos[40] yPos[11] dim[22]
, class Linecolor[3] xPos[4] yPos[83] dim[6]
, class Circlecolor[1] xPos[75] yPos[10] dim[42]
]
*///:~
  • 现在把序列化后的东西都反序列化取出来,观察打印结果:

    • Circlecolor为1,这是因为Circle的color在字段定义时就已经初始化为1了。
    • Squarecolor为0,这是因为Square的color在字段定义时没有初始化,但java会给它一个初始值0。
    • Linecolor为3,这是因为你调用了deserializeStaticState,将序列化时存储的一个int值取出来了,并赋值给color静态变量了。不然Linecolor也为1。
  • 综上,几乎可以认为, 序列化时并没有存储静态变量,因为反序列化时得到的静态变量,也只是从静态变量字段定义和静态代码块得到的。
  • 但上面这个程序并没有从List<Class<? extends Shape>> shapeTypes里得到静态变量,好像有点佐证不足,所以请看下面这个程序:
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.*;public class RecoverCADState {@SuppressWarnings("unchecked")public static void main(String[] args) throws Exception {ObjectInputStream in = new ObjectInputStream(new FileInputStream("CADState.out"));// Read in the same order they were written:List<Class<? extends Shape>> shapeTypes =(List<Class<? extends Shape>>)in.readObject();for(Class<? extends Shape> clazz: shapeTypes) {System.out.println(clazz.getSimpleName());Field field = clazz.getDeclaredField("color");field.setAccessible(true);for(Field f: clazz.getFields()) {System.out.print(f.getName()+" ");}System.out.println();System.out.println(String.format("当前Class对象的类名是%s,通过Class对象获得静态变量color为%d",clazz.getSimpleName(), (Integer)field.get(clazz)));  // (Integer)field.get(clazz)通过Class对象来获得静态变量}Circle circle = new Circle(1,2,3);System.out.println("调用构造器后 Circle类的color静态变量为"+circle.getColor());Square square = new Square(1,2,3);System.out.println("调用构造器后 Square类的color静态变量为"+square.getColor());Line line = new Line(1,2,3);System.out.println("调用构造器后 Line类的color静态变量为"+line.getColor());//Line.deserializeStaticState(in);in.readInt();List<Shape> shapes = (List<Shape>)in.readObject();System.out.println(shapes);}
} /* Output:
Circle
RED BLUE GREEN
当前Class对象的类名是Circle,通过Class对象获得静态变量color为1
Square
RED BLUE GREEN
当前Class对象的类名是Square,通过Class对象获得静态变量color为0
Line
RED BLUE GREEN
当前Class对象的类名是Line,通过Class对象获得静态变量color为1
调用构造器后 Circle类的color静态变量为1
调用构造器后 Square类的color静态变量为1
调用构造器后 Line类的color静态变量为1
[class Circlecolor[1] xPos[58] yPos[55] dim[93]
, class Squarecolor[1] xPos[61] yPos[61] dim[29]
, class Linecolor[1] xPos[68] yPos[0] dim[22]
, class Circlecolor[1] xPos[7] yPos[88] dim[28]
, class Squarecolor[1] xPos[51] yPos[89] dim[9]
, class Linecolor[1] xPos[78] yPos[98] dim[61]
, class Circlecolor[1] xPos[20] yPos[58] dim[16]
, class Squarecolor[1] xPos[40] yPos[11] dim[22]
, class Linecolor[1] xPos[4] yPos[83] dim[6]
, class Circlecolor[1] xPos[75] yPos[10] dim[42]
]
*///:~
  • 此程序终于使用到了List<Class<? extends Shape>> shapeTypes,并且从里面的各个Class对象获得到了静态变量color,从打印结果看,终于坐实了:序列化时不存储静态变量,反序列化时得到的静态变量只是从静态变量字段定义和静态代码块获得的,换句话说,是从本地class文件中获得的,因为静态变量字段定义和静态代码块就存储在本地class文件里。
  • 不再调用Line.deserializeStaticState(in),所以Line的静态变量也不会修改了。但必须调用in.readInt()以保证后面能正常读取。
  • 最后打印对象时,Squarecolor为1,是因为调用了它的构造器。

Serializable与深拷贝/浅拷贝

//: io/MyWorld.java
import java.io.*;
import java.util.*;
import static net.mindview.util.Print.*;class House implements Serializable {}class Animal implements Serializable {private String name;private House preferredHouse;Animal(String nm, House h) {name = nm;preferredHouse = h;}public String toString() {return name + "[" + super.toString() +  //super.toString()为了打印出子类对象的父类对象的地址"], " + preferredHouse + "\n";}
}public class MyWorld {public static void main(String[] args)throws IOException, ClassNotFoundException {House house = new House();List<Animal> animals = new ArrayList<Animal>();animals.add(new Animal("Bosco the dog", house));animals.add(new Animal("Ralph the hamster", house));animals.add(new Animal("Molly the cat", house));print("animals: " + animals);ByteArrayOutputStream buf1 =new ByteArrayOutputStream();ObjectOutputStream o1 = new ObjectOutputStream(buf1);o1.writeObject(animals);o1.writeObject(animals); // Write a 2nd set// Write to a different stream:ByteArrayOutputStream buf2 =new ByteArrayOutputStream();ObjectOutputStream o2 = new ObjectOutputStream(buf2);o2.writeObject(animals);// Now get them back:ObjectInputStream in1 = new ObjectInputStream(new ByteArrayInputStream(buf1.toByteArray()));ObjectInputStream in2 = new ObjectInputStream(new ByteArrayInputStream(buf2.toByteArray()));Listanimals1 = (List)in1.readObject(),animals2 = (List)in1.readObject(),animals3 = (List)in2.readObject();print("animals1: " + animals1);print("animals2: " + animals2);print("animals3: " + animals3);}
} /* Output: (Sample)
animals: [Bosco the dog[Animal@addbf1], House@42e816
, Ralph the hamster[Animal@9304b1], House@42e816
, Molly the cat[Animal@190d11], House@42e816
]
animals1: [Bosco the dog[Animal@de6f34], House@156ee8e
, Ralph the hamster[Animal@47b480], House@156ee8e
, Molly the cat[Animal@19b49e6], House@156ee8e
]
animals2: [Bosco the dog[Animal@de6f34], House@156ee8e
, Ralph the hamster[Animal@47b480], House@156ee8e
, Molly the cat[Animal@19b49e6], House@156ee8e
]
animals3: [Bosco the dog[Animal@10d448], House@e0e1c6
, Ralph the hamster[Animal@6ca1c], House@e0e1c6
, Molly the cat[Animal@1bf216a], House@e0e1c6
]
*///:~
  • 代码来自Java编程思想,我觉得很经典。
  • List中添加了三个Animal对象,三者持有的House对象引用是一样的,也就是同一个House对象。
  • 从打印结果来看,对比animals和animals1:发现animals1反序列化取出的对象,完全是不同的了,也就是说,反序列化实现了深拷贝。而且反序列化还保持了同样的关系——三个Animal对象持有同一个House对象。
  • 从打印结果来看,对比animals1和animals2:发现两个打印结果完全一样,可以说是完全的浅拷贝。原因是这二者的输出流是同一个ByteArrayOutputStream buf1,对于同一个输出流,相同对象再次写入,不会发生拷贝。
  • 从打印结果来看,对比animals2和animals3:发现这二者又是深拷贝了,因为这二者对应着两个不同的输出流ByteArrayOutputStream buf1ByteArrayOutputStream buf2,不同的输出流相当于两个隔离的空间,自然会重新生成对象。
  • 把ByteArrayOutputStream换成FileOutputStream也是一样的。

其他

  • 类的静态变量不会参与到序列化过程中,也就是说,反序列化时,静态变量总是会从本地class文件读取出来。但serialVersionUID这个静态变量是个特例,在序列化过程中输出的字节流中,将带有serialVersionUID的信息。

Java序列化 Serializable和Externalizable浅析相关推荐

  1. Java 序列化Serializable详解

    转载 Java 序列化Serializable详解(附详细例子) 1.什么是序列化和反序列化 Serialization(序列化)是一种将对象以一连串的字节描述的过程:反序列化deserializat ...

  2. Java 序列化Serializable详解(附详细例子)

    Java 序列化Serializable详解(附详细例子) 1.什么是序列化和反序列化 Serialization(序列化)是一种将对象以一连串的字节描述的过程:反序列化deserialization ...

  3. java序列化Serializable

    目前网络上关于对象序列化的文章不少,但是我发现详细叙述用法和原理的文章太少.本人把自己经过经验总结和实际运用中的体会写成的学习笔记贡献给大家.希望能为整个java社区的繁荣做一点事情. 序列化的过程就 ...

  4. Java序列化(Serializable)与反序列化

    学习Java的同学注意了!!!  学习过程中遇到什么问题或者想获取学习资源的话,欢迎加入Java学习交流群,群号码:589809992 我们一起学Java! 序列化是干什么的 简单说就是为了保存在内存 ...

  5. 一篇搞懂java序列化Serializable

    序列化 (Serialization)将对象的状态信息转换为可以存储或传输的形式的过程. 一.序列化和反序列化的概念 把对象转换为字节序列的过程称为对象的序列化. 把字节序列恢复为对象的过程称为对象的 ...

  6. java serializable用法_JAVA序列化Serializable及Externalizable区别详解

    序列化简介 Java 的对象序列化将那些实现 Serializable 接口的对象转换成一个字节序列,并能在之后将这个字节序列完全恢复为原来的对象. 这就意味着 Java 对象在网络上的传输可以不依赖 ...

  7. Java 序列化 之 Serializable

    概念 序列化:就是把对象转化成字节. 反序列化:把字节数据转换成对象. 对象序列化场景: 1.对象网络传输 例如:在微服务系统中或给第三方提供接口调用时,使用rpc进行调用,一般会把对象转化成字节序列 ...

  8. 为什么要在Java的Serializable类中使用SerialVersionUID

    序列化和SerialVersionUID始终是许多Java开发人员的难题. 我经常会看到类似此SerialVersionUID的问题,或者如果不在我的Serializable类中声明SerialVer ...

  9. 一文了解Java序列化与反序列化

    目录 序列化示例 有父类的对象序列化 Serializable和Externalizable区别 序列化和反序列化实现 serialVersionUID不一致有什么问题 1.先注释掉反序列化代码, 执 ...

最新文章

  1. 详解DNS的常用记录(下):DNS系列之三
  2. 全面升级!星环科技基础软件再升级,赋能数字中国建设
  3. python【力扣LeetCode算法题库】1013-将数组分成和相等的三个部分(贪心)
  4. OSGi简介–模块化Java
  5. linux 的date命令详解,linux之date命令详解
  6. 【Python自然语言处理】中文分词技术——统计分词
  7. 动手学servlet(六) 过滤器和监听器
  8. 服务器装不上无线网卡代码10,win10专业版无线网卡出现错误代码10怎么办?
  9. vue项目中常见的跨域问题解决
  10. 甲每秒跑3米,乙每秒跑4米,丙每秒跑2米,三人沿600米的环形跑道从同一地点同时同方向跑步,经过多少时间三人又同时从出发点出发?
  11. c语言abs作用是什么意思,c语言fabs()是什么意思?和abs()有什么区别
  12. uniapp语音识别_有没有语音转文字的APP?
  13. 与京东物流合作,能不能补全东方甄选的最后一块拼图?
  14. linux中ipa服务器搭建,Mac搭建内网服务器无线安装ipa包
  15. 点餐推荐系统_麦当劳智慧餐厅的微信小程序终究将取代人工点餐和自助点餐机...
  16. 实验:ospf与BFD联动实验(EVE模拟器-Cisco)
  17. 【数据中台】数据中台架构搭建百科全书
  18. 如何更优雅的对接第三方API
  19. 洛谷 P2327 [SCOI2005]扫雷
  20. oracle核销预付账款,AP模块外币预付款核销的CNY尾差问题

热门文章

  1. 安全事故频出?私有云成为企业首选项
  2. iOS开发,获取手机型号,系统版本号
  3. SCU 4438:Censor
  4. lc501.二叉搜索树中的众数【线索二叉树Morris遍历->lc538】
  5. react学习--左手法则走迷宫
  6. 软件版本中的Alpha,Beta,Gamma,RC等
  7. 如何使用机器学习对CFD模拟结果进行预测
  8. 正则表达式 - 在线工具汇总
  9. 欣赏别人,是一种境界
  10. 随机密码生成。编写程序,接收列表在26个字母大小写和10个数字组成的列表中随机生成10个8位密码。