《Java基础知识》Java序列化与反序列化详解
序列化的作用:为了不同jvm之间共享实例对象的一种解决方案.由java提供此机制。
序列化应用场景:
1. 分布式传递对象。
2. 网络传递对象。
3. tomcat关闭以后会把session对象序列化到SESSIONS.ser文件中,等下次启动的时候就把这些session再加载到内存中。
完整案例:
import java.io.Serializable; public class Box implements Serializable { public Box(){ System.out.println("调用构造Box方法"); } private String width; private String height; public String getWidth() { return width; } public void setWidth(String width) { this.width = width; } public String getHeight() { return height; } public void setHeight(String height) { this.height = height; } @Override public String toString() { return "Box{" + "width=" + width + ", height=" + height + '}'; } }
import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class SerializableDemo { public static void main(String[] args){ Box myBox = new Box(); myBox.setWidth("50"); myBox.setHeight("30"); try{ File file = new File("src\\demo\\knowledgepoints\\file\\foo.ser"); //把对象信息写入文件中。 ObjectOutputStream oout = new ObjectOutputStream (new FileOutputStream(file)); oout.writeObject(myBox); oout.close(); //把对象信息从文件中获取出来。 ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file)); Box newMyBox = (Box)oin.readObject(); // 没有强制转换到Person类型 oin.close(); System.out.println(newMyBox); }catch(Exception ex){ ex.printStackTrace(); } } }
运行结果:
序列化相关注意事项:
a)序列化时,只对对象的状态进行保存,而不管对象的方法;
b)当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;
c)当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;
d) 被序列化的对象需要 实现(Serializable)接口;
e) 案例:构造方法没有执行,说明生成新对象,不是通过New出来的;
Java 对象被序列化需要实现(Serializable)接口,原因:
在 我们将对象序列化的类 ObjectOutputStream 的方法 writeObject0 中可以找到答案。
以下是JDK8的部分源码,红色字体部分就是原因。
private void writeObject0(Object obj, boolean unshared) throws IOException { boolean oldMode = bout.setBlockDataMode(false); depth++; try { // handle previously written and non-replaceable objects int h; if ((obj = subs.lookup(obj)) == null) { writeNull(); return; } else if (!unshared && (h = handles.lookup(obj)) != -1) { writeHandle(h); return; } else if (obj instanceof Class) { writeClass((Class) obj, unshared); return; } else if (obj instanceof ObjectStreamClass) { writeClassDesc((ObjectStreamClass) obj, unshared); return; } // check for replacement object Object orig = obj; Class<?> cl = obj.getClass(); ObjectStreamClass desc; for (;;) { // REMIND: skip this check for strings/arrays? Class<?> repCl; desc = ObjectStreamClass.lookup(cl, true); if (!desc.hasWriteReplaceMethod() || (obj = desc.invokeWriteReplace(obj)) == null || (repCl = obj.getClass()) == cl) { break; } cl = repCl; } if (enableReplace) { Object rep = replaceObject(obj); if (rep != obj && rep != null) { cl = rep.getClass(); desc = ObjectStreamClass.lookup(cl, true); } obj = rep; } // if object replaced, run through original checks a second time if (obj != orig) { subs.assign(orig, obj); if (obj == null) { writeNull(); return; } else if (!unshared && (h = handles.lookup(obj)) != -1) { writeHandle(h); return; } else if (obj instanceof Class) { writeClass((Class) obj, unshared); return; } else if (obj instanceof ObjectStreamClass) { writeClassDesc((ObjectStreamClass) obj, unshared); return; } } // remaining cases if (obj instanceof String) { writeString((String) obj, unshared); } else if (cl.isArray()) { writeArray(obj, desc, unshared); } else if (obj instanceof Enum) { writeEnum((Enum<?>) obj, desc, unshared); } else if (obj instanceof Serializable) { writeOrdinaryObject(obj, desc, unshared); } else { if (extendedDebugInfo) { throw new NotSerializableException( cl.getName() + "\n" + debugInfoStack.toString()); } else { throw new NotSerializableException(cl.getName()); } } } finally { depth--; bout.setBlockDataMode(oldMode); } }
如何将序列化控制在自己手中?
1. 通过关键字【transient】实现,字段值不被序列化。
改一下Box类:
import java.io.Serializable; public class Box implements Serializable { public Box(){ System.out.println("调用构造Box方法"); } // 关键字:transient 控制width不被序列化,保护数据。 private transient String width; private String height; public String getWidth() { return width; } public void setWidth(String width) { this.width = width; } public String getHeight() { return height; } public void setHeight(String height) { this.height = height; } @Override public String toString() { return "Box{" + "width=" + width + ", height=" + height + '}'; } }
运行结果:
这个box的width值被擦除了。
2. writeObject()方法与readObject()方法。
import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class Box implements Serializable { public Box(){ System.out.println("调用构造Box方法"); } // 关键字:transient 控制width不被序列化,保护数据。 private transient String width; private String height; public String getWidth() { return width; } public void setWidth(String width) { this.width = width; } public String getHeight() { return height; } public void setHeight(String height) { this.height = height; } private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); out.writeChars(width); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); width = in.readLine(); } @Override public String toString() { return "Box{" + "width=" + width + ", height=" + height + '}'; } }
运行结果:
加入writeObject()方法与readObject()方法后,width值又回来了,这两个方法都是私有的,已经可以基本猜测是通过反射调用方法,赋值的。
3.Externalizable 接口,自定义实现写入和读取序列化对象
import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; public class BoxTmp implements Externalizable { public BoxTmp(){ System.out.println("调用构造Box方法"); } private String width; private String height; public String getWidth() { return width; } public void setWidth(String width) { this.width = width; } public String getHeight() { return height; } public void setHeight(String height) { this.height = height; } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeChars(width+","+width); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { String str = in.readLine(); this.width = str.split(",")[0]; this.height = str.split(",")[1]; } @Override public String toString() { return "BoxTmp{" + "width='" + width + '\'' + ", height='" + height + '\'' + '}'; } }
public class ExternalizableDemo { public static void main(String[] args){ BoxTmp boxTmp = new BoxTmp(); boxTmp.setWidth("50"); boxTmp.setHeight("30"); try{ File file = new File("src\\demo\\knowledgepoints\\file\\foo.txt"); //把对象信息写入文件中。 ObjectOutputStream oout = new ObjectOutputStream (new FileOutputStream(file)); oout.writeObject(boxTmp); oout.close(); //把对象信息从文件中获取出来。 ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file)); BoxTmp newBoxTmp = (BoxTmp)oin.readObject(); // 没有强制转换到Person类型 oin.close(); System.out.println(newBoxTmp); }catch(Exception ex){ ex.printStackTrace(); } } }
运行结果:
Externalizable接口中的writeExternal和readExternal 方法可以用来自定义实现值的传递,覆盖等其他操作。
同时构造方法被执行了,说明这个类是被new出来后,由你支配。
4. readResolve()方法
import java.io.ObjectStreamException; import java.io.Serializable; public class Box implements Serializable { public static Box box = new Box(); public Box(){} public Box(String height,String width){ this.height = height; this.width = width; } public static Box getInstance() { if(box == null){ box = new Box("20","10"); } return box; } private String width; private String height; public String getWidth() { return width; } public void setWidth(String width) { this.width = width; } public String getHeight() { return height; } public void setHeight(String height) { this.height = height; } private Object readResolve() throws ObjectStreamException { return Box.box; } }
import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class SerializableDemo { public static void main(String[] args){ Box myBox = Box.getInstance(); try{ File file = new File("src\\demo\\knowledgepoints\\file\\foo.ser"); //把对象信息写入文件中。 ObjectOutputStream oout = new ObjectOutputStream (new FileOutputStream(file)); oout.writeObject(myBox); oout.close(); //把对象信息从文件中获取出来。 ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file)); Box newMyBox = (Box)oin.readObject(); // 没有强制转换到Person类型 oin.close(); System.out.println("newMyBox == myBox : "+(newMyBox == myBox)); }catch(Exception ex){ ex.printStackTrace(); } } }
运行结果:
Box类中实现readResolve() 方法可以实现单例对象还是同一个。
参考资料