JAVA IO 序列化与设计模式

时间:2021-07-10 09:35:50

更多技术干货请戳:听云博客

序列化

什么是序列化

序列化:保存对象的状态

反序列化:读取保存对象的状态

序列化和序列化是Java提供的一种保存恢复对象状态的机制

序列化有什么用

将数据保存到文件或数据库中时

将数据通过套接字在网络上传输时

通过 RPC RMI等传输对象时

如何序列化

实现Serializable接口

实现Externalizable接口

serialVersionUID的作用serialVersionUID建议给一个确定的值,不要由系统自动生成,否则在增减字段(不能修改字段类型及长度)时,如果两边的类的版本不同会导致反序列化失败

默认序列化机制

如果仅仅只是让某个类实现Serializable接口,而没有其它任何处理的话,则就是使用默认序列化机制。使用默认机制,在序列化对象时,不仅会序列化当前对象本身,还会对该对象引用的其它对象也进行序列化,同样地,这些其它对象引用的另外对象也将被序列化,以此类推。所以,如果一个对象包含的成员变量是容器类对象,而这些容器所含有的元素也是容器类对象,那么这个序列化的过程就会较复杂,开销也较大。

(01) 序列化对static和transient变量,是不会自动进行状态保存的。

transient的作用就是,用transient声明的变量,不会被自动序列化。

(02) 对于Socket, Thread类,不支持序列化。若实现序列化的接口中,有Thread成员;在对该类进行序列化操作时,运行会出错。

这主要是基于资源分配方面的原因。如果Socket,Thread类可以被序列化,但是被反序列化之后也无法对他们进行重新的资源分配。

示例:

package ioEx;
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.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
public class Serial {
private static final String TMP_FILE = "text.txt";
public static void main(String[] args) {
testWrite();
testRead();
}
private static void testWrite() {
try {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(TMP_FILE));
AnObject box = new AnObject(1, 1, "1");
out.writeObject(box);
out.writeBoolean(true);
out.writeByte((byte)65);
out.writeChar('a');
out.writeInt(20160415);
out.writeFloat(3.14F);
out.writeDouble(Math.PI);
HashMap<String,String> map = new HashMap<String,String>();
map.put("a", "a");
map.put("b", "b");
map.put("c", "c");
out.writeObject(map);
out.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
private static void testRead() {
try {
ObjectInputStream in = new ObjectInputStream(new FileInputStream(TMP_FILE));
AnObject box = (AnObject) in.readObject();
System.out.println("testWrite box: " + box);
System.out.println("boolean:"+ in.readBoolean());
System.out.println("byte:" + (in.readByte()&0xff));
System.out.println("char:" + in.readChar());
System.out.println("int:" + in.readInt());
System.out.println("float:" + in.readFloat());
System.out.println("double:" + in.readDouble());
// 读取HashMap对象
HashMap<String,String> map = (HashMap<String,String>) in.readObject();
Iterator<Entry<String, String>> iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String,String> entry = (Entry<String, String>)iter.next();
System.out.println(entry.getKey()+"--"+ entry.getValue());
}
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
class AnObject implements Serializable { private int obja;
private int objb;
private String objc;
private static int statica;
private transient int transienda; //必须用static或transient修饰才可能序列化,否则运行报错
private static transient Thread thread = new Thread() {
@Override
public void run() {
System.out.println("Serializable");
}
}; public AnObject(int obja, int objb, String objc) {
this.obja = obja;
this.objb = objb;
this.objc = objc;
this.statica=obja;
this.transienda=obja;
} //如果要使transient序列化要重写writeObject,和readObject 方法
// private void writeObject(ObjectOutputStream out) throws IOException{
// out.defaultWriteObject();
// out.writeInt(transienda);
// }
//
// private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException{
// in.defaultReadObject();
// transienda = in.readInt();
// } @Override
public String toString() {
return "obja:"+obja+","+ "objb:"+objb+","+ "objc:"+objc+","+ "statica:"+statica+","+ "transienda:"+transienda;
}
}

JAVA IO的设计模式 

JAVA IO框架主要使用的两种设计模式 装饰模式和适配器模式

装饰模式又名包装(Wrapper)模式

装饰模式以对客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。

  • 装饰模式通过创建一个包装对象,也就是装饰,来包裹真实的对象。

  • 装饰模式以对客户端透明的方式动态地给一个对象附加上更多的责任。换言之,客户端并不会觉得对象在装饰前和装饰后有什么不同。

  • 装饰模式可以在不创造更多子类的情况下,将对象的功能加以扩展。

  • 装饰模式把客户端的调用委派到被装饰类。装饰模式的关键在于这种扩展是完全透明的。

装饰模式的角色

  • 抽象构件角色(Component):给出一个抽象接口,以规范准备接收附加责任的对象。

  • 具体构件角色(Concrete Component):定义将要接收附加责任的类。

  • 装饰角色(Decorator):持有一个构件(Component)对象的引用,并定义一个与抽象构件接口一致的接口。

  • 具体装饰角色(Concrete Decorator):负责给构件对象“贴上”附加的责任。

装饰模式的特点

  • 装饰对象和真实对象有相同的接口。这样客户端对象就可以以和真实对象相同的方式和装饰对象交互。

  • 装饰对象包含一个真实对象的引用(reference)。

  • 装饰对象接收所有来自客户端的请求,它把这些请求转发给真实的对象。

  • 装饰对象可以在转发这些请求之前或之后附加一些功能。

这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。

示例:

package Test;

public class Wrapper {
public static void main(String[] args) {
Component c = new RealizeComponent();
Component c1 = new RealizeDecorator1(c);
c1.say();
System.out.println("--");
Component c2 = new RealizeDecorator2(c1);
c2.say();
}
}
interface Component{
public void say();
}
class RealizeComponent implements Component{
@Override
public void say() {
System.out.println("A");
}
}
class Decorator implements Component{
private Component component;
public Decorator(Component component) {
this.component = component;
}
@Override
public void say(){
component.say();
}
}
class RealizeDecorator1 extends Decorator{
public RealizeDecorator1(Component component){
super(component);
}
@Override
public void say(){
super.say();
this.sayAnother();
}
private void sayAnother(){
System.out.println("B");
}
}
class RealizeDecorator2 extends Decorator{
public RealizeDecorator2(Component component){
super(component);
}
@Override
public void say(){
super.say();
this.sayAnother();
}
private void sayAnother(){
System.out.println("C");
}
}

装饰模式的优点

装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。装饰模式允许系统动态决定“贴上”一个需要的“装饰”,或者除掉一个不需要的“装饰”。继承关系则不同,继承关系是静态的,它在系统运行前就决定了。

通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。

装饰模式的缺点

由于使用装饰模式,可以比使用继承关系需要较少数目的类。使用较少的类,当然使设计比较易于进行。但是,在另一方面,使用装饰模式会产生比使用继承关系更多的对象。更多的对象会使得查错变得困难,特别是这些对象看上去都很相像。

JAVA IO中的装饰模式

抽象构件(Component)角色:由InputStream扮演。这是一个抽象类,为各种子类型提供统一的接口。

具体构件(ConcreteComponent)角色:由ByteArrayInputStream、FileInputStream、PipedInputStream、StringBufferInputStream等类扮演。它们实现了抽象构件角色所规定的接口。

抽象装饰(Decorator)角色:由FilterInputStream扮演。它实现了InputStream所规定的接口。

具体装饰(ConcreteDecorator)角色:由几个类扮演,分别是BufferedInputStream、DataInputStream以及两个不常用到的类LineNumberInputStream、PushbackInputStream。

适配器模式

JAVA IO 序列化与设计模式

上图是适配器模式的类图。Adapter 适配器设计模式中有 3 个重要角色:被适配者 Adaptee,适配器 Adapter 和目标对象 Target。其中两个现存的想要组合到一起的类分别是被适配者 Adaptee 和目标对象 Target 角色,按照类图所示,我们需要创建一个适配器 Adapter 将其组合在一起。

最简单的适配器示例:

package AdapterEx;

class Apple {
public void getAColor(String str) {
System.out.println("Apple color is: " + str);
}
}
class Orange {
public void getOColor(String str) {
System.out.println("Orange color is: " + str);
}
}
class AppleAdapter extends Apple {
private Orange orange; public AppleAdapter(Orange orange) {
this.orange = orange;
} public void getAColor(String str) {
orange.getOColor(str);
}
}
public class AdapterEx {
public static void main(String[] args) {
Apple apple = new Apple();
apple.getAColor("green");
Orange orange = new Orange();
AppleAdapter aa = new AppleAdapter(orange);
aa.getAColor("red");
}
}

Java I/O 库大量使用了适配器模式,例如 ByteArrayInputStream 是一个适配器类,它继承了 InputStream 的接口,并且封装了一个 byte 数组。换言之,它将一个 byte 数组的接口适配成 InputStream 流处理器的接口。

我们知道 Java 语言支持四种类型:Java 接口,Java 类,Java 数组,原始类型(即 int,float 等)。前三种是引用类型,类和数组的实例是对象,原始类型的值不是对象。也即,Java 语言的数组是像所有的其他对象一样的对象,而不管数组中所存储的元素类型是什么。这样一来的话,ByteArrayInputStream 就符合适配器模式的描述,是一个对象形式的适配器类。FileInputStream 是一个适配器类。在 FileInputStream 继承了 InputStrem 类型,同时持有一个对 FileDiscriptor 的引用。这是将一个 FileDiscriptor 对象适配成 InputStrem 类型的对象形式的适配器模式。

同样地,在 OutputStream 类型中,所有的原始流处理器都是适配器类。ByteArrayOutputStream 继承了 OutputStream 类型,同时持有一个对 byte 数组的引用。它一个 byte 数组的接口适配成 OutputString 类型的接口,因此也是一个对象形式的适配器模式的应用。

FileOutputStream 继承了 OutputStream 类型,同时持有一个对 FileDiscriptor 对象的引用。这是一个将 FileDiscriptor 接口适配成 OutputStream 接口形式的对象型适配器模式。

Reader 类型的原始流处理器都是适配器模式的应用。StringReader 是一个适配器类,StringReader 类继承了 Reader 类型,持有一个对 String 对象的引用。它将 String 的接口适配成 Reader 类型的接口。

装饰模式和适配器模式的对比

(1)装饰模式和适配器模式,都是通过封装其他对象达到设计目的的。

(2)理想的装饰模式在对被装饰对象进行功能增强时,要求具体构件角色、装饰角色的接口与抽象构件角色的接口完全一致;而适配器模式则不然,一般而言,适配器模式并不要求对源对象的功能进行增强,只是利用源对象的功能而已,但是会改变源对象的接口,以便和目标接口相符合。

(3)装饰模式有透明和半透明两种,区别就在于接口是否完全一致。关于装饰模式的重要的事实是,很难找到理想的装饰模式。一般而言,对一个对象进行功能增强的同时,都会导致加入新的行为,因此,装饰角色的接口比抽象构件角色的接口宽是很难避免的,这种现象存在于Java I/O库中多有的类型的链接流处理器中。一个装饰类提供的新的方法越多,它离纯装饰模式的距离就越远,离适配器模式的距离也就越近。

原文链接:http://blog.tingyun.com/web/article/detail/460