Serializable是java中实现对象序列化与反序列化的方式之一,使用方式非常简单,只要在在类声明时实现Serializable接口就可以。但实际开发使用过程中,还有一些要点需要注意:
一个聚合对象里的其他任何非基本数据类型和对象包装类,都必须要实现serializable接口
在实现Serializable接口后,IDE会提示我们设置该类的*serialVersionUID*,虽然我们经常设置为默认值1L,甚至不去设置,发现在开始使用并没有什么问题,但是这为以后的维护埋下了隐患,serialVersionUID在我们进行序列化时会被设置到保存的文件中去,当下次进行反序列化时,系统就会将文件中的serialVersionUID与类中的serialVersionUID进行对比,只用当两者相同时,才会被正确的反序列化。如果我们在后期的维护过程中,对原来的类中的成员变量进行了修改或删除,并且原先并没有设置serialVersionUID,此时系统会对修改后的类进行计算其hash值,从而造成与原先写入文件时的serialVersionUID不同,导致反序列化失败。相反,如果我们之前设置了serialVersionUID,即使后期类中的部分成员变量发生了改动,那么反序列化仍会成功,会最大限度的恢复数据。
Java序列化是指把Java对象转换为字节序列的过程;而Java反序列化是指把字节序列恢复为Java对象的过程。从而达到网络传输、本地存储的效果。
本文主要要看看JDK中使用Serializable和Externalizable接口来完成Java对象序列化,并给出部分属性序列化的几种方式,最终做出Serializable和Externalizable接口的几个方面的对比。
注:本文不讨论为什么不用第三方工具包完成序列化等~
要实现Java对象的序列化,只要将类实现Serializable或Externalizable接口即可。
采用类实现Serializable接口的序列化很简单,Java自动会将非transient修饰属性序列化到指定文件中去。
举个例子:
import java.io.Serializable; import java.util.List; /** * @Type Book.java * @Desc * @author wangmengjun * @date 2017年12月1日 下午7:16:29 * @version */ public class Book implements Serializable { private static final long serialVersionUID = -6212470156629515269L; /**书名*/ private String name; /**ISBN*/ private String isbn; /**作者*/ private List<String> authors; /** * @return the name */ public String getName() { return name; } /** * @param name the name to set */ public void setName(String name) { this.name = name; } /** * @return the isbn */ public String getIsbn() { return isbn; } /** * @param isbn the isbn to set */ public void setIsbn(String isbn) { this.isbn = isbn; } /** * @return the authors */ public List<String> getAuthors() { return authors; } /** * @param authors the authors to set */ public void setAuthors(List<String> authors) { this.authors = authors; } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return "Book [name=" + name + ", isbn=" + isbn + ", authors=" + authors + "]"; } }
然后编写一个用于序列化和反序列的小工具类,
import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; /** * @Type SerializationUtil.java * @Desc * @author wangmengjun * @date 2017年12月1日 下午7:23:04 * @version */ public class SerializationUtil { /** * 从一个给定的文件完成反序列化 */ public static Object deserialize(String fileName) throws IOException, ClassNotFoundException { FileInputStream fis = new FileInputStream(fileName); BufferedInputStream bis = new BufferedInputStream(fis); ObjectInputStream ois = new ObjectInputStream(bis); Object obj = ois.readObject(); ois.close(); return obj; } /** * 将给定的对象序列化到指定的文件中去 */ public static void serialize(Object obj, String fileName) throws IOException { FileOutputStream fos = new FileOutputStream(fileName); BufferedOutputStream bos = new BufferedOutputStream(fos); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(obj); oos.close(); } }
写个测试类,测试一下:
public class SerializableTest { public static void main(String[] args) throws IOException, ClassNotFoundException { Book book = new Book(); book.setIsbn("ABC123456789"); book.setName("Hello Java"); book.setAuthors(Arrays.asList("John","Eric")); //book==>Book [name=Hello Java, isbn=ABC123456789, authors=[John, Eric]] System.out.println("book==>" + book); /** * 将book对象序列化到book.temp文件中去 */ String fileName = "book.temp"; SerializationUtil.serialize(book, fileName); /** * 从book.temp文件中,反序列化一个Book对象 */ Book deserializedBook = (Book) SerializationUtil.deserialize(fileName); //deserializedBook==>Book [name=Hello Java, isbn=ABC123456789, authors=[John, Eric]] System.out.println("deserializedBook==>" + deserializedBook); } }
一个简单的示例,就完成了Book对象的序列化和反序列化。
在上述示例中,Book对象中的所有的属性都被序列化。如果里面存在部分属性,我们不想要被序列化,该如何做呢?
如果只想将部分属性进行序列化,可以采用如下几种方法:
对属性添加transient关键字,可以防止该属性序列化~
如下示例中,我们不想isbn和authors属性被序列,添加上transient关键字实现一下~
public class Book implements Serializable { private static final long serialVersionUID = -6212470156629515269L; /** 书名 */ private String name; /** ISBN */ private transient String isbn; /** 作者 */ private transient List<String> authors; ... ... }
运行上述提到的SerializableTest.java程序,输出如下结果,我们可以看出isbn和authors的值都为null,表明这两个属性没有被序列化~
book==>Book [name=Hello Java, isbn=ABC123456789, authors=[John, Eric]] deserializedBook==>Book [name=Hello Java, isbn=null, authors=null]
另外,我们也可以采用编写私有方法writeObject和readObject,完成部分属性的序列化。修改Book类,增加writeObject 和 readObject方法,如:
public class Book implements Serializable { private static final long serialVersionUID = -6212470156629515269L; /** 书名 */ private String name; /** ISBN */ private String isbn; /** 作者 */ private List<String> authors; private void writeObject(ObjectOutputStream oos) throws IOException { // oos.defaultWriteObject(); oos.writeObject(name); oos.writeObject(isbn); } private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { // ois.defaultReadObject(); name = (String) ois.readObject(); isbn = (String) ois.readObject(); } ... ... }
在上述示例中,我们选择序列化的属性为name和isbn,书本作者authors没有序列化~
同样,使用SerializableTest.java类测试一下,结果如下:
book==>Book [name=Hello Java, isbn=ABC123456789, authors=[John, Eric]] deserializedBook==>Book [name=Hello Java, isbn=ABC123456789, authors=null]
注意:
这里的writeObject和readObject是private的且是void的~
Java调用ObjectOutputStream类检查其是否有私有的,无返回值的writeObject方法,如果有,其会委托该方法进行对象序列化。
检查是否有合适的方法如下:
writeObjectMethod = getPrivateMethod(cl, "writeObject", new Class<?>[] { ObjectOutputStream.class }, Void.TYPE); readObjectMethod = getPrivateMethod(cl, "readObject", new Class<?>[] { ObjectInputStream.class }, Void.TYPE); readObjectNoDataMethod = getPrivateMethod( cl, "readObjectNoData", null, Void.TYPE); hasWriteObjectData = (writeObjectMethod != null);
还有一种方式,就是使用Externalizable完成部分属性的序列化。
Externalizable继承自Serializable,使用Externalizable接口需要实现writeExternal以及readExternal方法~在writeExternal方法中,写入想要外部序列化的元素~
public interface Externalizable extends java.io.Serializable { /** * The object implements the writeExternal method to save its contents * by calling the methods of DataOutput for its primitive values or * calling the writeObject method of ObjectOutput for objects, strings, * and arrays. * * @serialData Overriding methods should use this tag to describe * the data layout of this Externalizable object. * List the sequence of element types and, if possible, * relate the element to a public/protected field and/or * method of this Externalizable class. * * @param out the stream to write the object to * @exception IOException Includes any I/O exceptions that may occur */ void writeExternal(ObjectOutput out) throws IOException; /** * The object implements the readExternal method to restore its * contents by calling the methods of DataInput for primitive * types and readObject for objects, strings and arrays. The * readExternal method must read the values in the same sequence * and with the same types as were written by writeExternal. * * @param in the stream to read data from in order to restore the object * @exception IOException if I/O errors occur * @exception ClassNotFoundException If the class for an object being * restored cannot be found. */ void readExternal(ObjectInput in) throws IOException, ClassNotFoundException; }
修改Book类的内容,如下:
import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.util.List; /** * @Type Book.java * @Desc * @author wangmengjun * @date 2017年12月1日 下午7:16:29 * @version */ public class Book implements Externalizable { /** 书名 */ private String name; /** ISBN */ private String isbn; /** 作者 */ private List<String> authors; @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(name); out.writeObject(isbn); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { name = (String) in.readObject(); isbn = (String) in.readObject(); } /** * @return the name */ public String getName() { return name; } /** * @param name * the name to set */ public void setName(String name) { this.name = name; } /** * @return the isbn */ public String getIsbn() { return isbn; } /** * @param isbn * the isbn to set */ public void setIsbn(String isbn) { this.isbn = isbn; } /** * @return the authors */ public List<String> getAuthors() { return authors; } /** * @param authors * the authors to set */ public void setAuthors(List<String> authors) { this.authors = authors; } /* * (non-Javadoc) * * @see java.lang.Object#toString() */ @Override public String toString() { return "Book [name=" + name + ", isbn=" + isbn + ", authors=" + authors + "]"; } }
同样,使用SerializableTest.java类测试一下,同样获得如下结果:
book==>Book [name=Hello Java, isbn=ABC123456789, authors=[John, Eric]] deserializedBook==>Book [name=Hello Java, isbn=ABC123456789, authors=null]
Externalizable和Serializable的一些比较点,如下:
【1】 Serializable 是标识接口
public interface Serializable { }
实现该接口,无需重写任何方法;
public interface Externalizable extends java.io.Serializable { void writeExternal(ObjectOutput out) throws IOException; void readExternal(ObjectInput in) throws IOException, ClassNotFoundException; }
Externalizable 接口继承于Serializable,实现该接口,需要重写readExternal和writeExternal方法~
【2】Serializable提供了两种方式进行对象的序列化,
Externalizable 接口的序列化,需要重写writeExternal和readExternal方法,并且在方法中编写相关的逻辑完成序列化和反序列化。
【3】Externalizable接口的实现方式一定要有默认的无参构造函数~
如果,没有无参构造函数,反序列化会报错~ 验证一下~ Book添加一个有参数的Book构造函数~
public class Book implements Externalizable { /** 书名 */ private String name; /** ISBN */ private String isbn; /** 作者 */ private List<String> authors; public Book(String name) { this.name = name; } ... ... }
这样,序列化、反序列化之后,报no valid constructor的异常~
Exception in thread "main" java.io.InvalidClassException: Book; no valid constructor at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:150) at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:768) at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1772) at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1350) at java.io.ObjectInputStream.readObject(ObjectInputStream.java:370) at SerializationUtil.deserialize(SerializationUtil.java:27) at SerializableTest.main(SerializableTest.java:33)
Serializable接口实现,其采用反射机制完成内容恢复,没有一定要有无参构造函数的限制~
【4】采用Externalizable无需产生序列化ID(serialVersionUID)~而Serializable接口则需要~
【5】相比较Serializable, Externalizable序列化、反序列更加快速,占用相比较小的内存
在项目中,大部分的类还是推荐使用Serializable, 有些类可以使用Externalizable接口,如: