java序列化
为什么要使用序列化
我们在java内存中的对象是无法直接进行网络通信或者将其持久化的,因为对象时无法进行网络传输或者IO通信的,我们必须在使用它之前将其转换成某种可传输的格式,那么就是序列化了。序列化传输之后要将其转化为可使用的对象,就是反序列化的过程了。所以序列化就是将一个对象转化为二进制字节数组从而可以达到进行传输或者持久化的目的;而反序列化就是将一个字节数组重新构造成对象的过程。
如何实现序列化
在java中,jdk提供序列化的方法,只需要将待序列化的类实现java.io.Serializable接口即可,而我们可以看到Serializable接口实际上是没有任何抽象方法的,所以不需要实现什么方法,有一点需要注意的是在被序列化的类中需要定义一个SerialVersionUID参数,这个参数是被序列化类的一个"版本号",在反序列化的时候java虚拟机会将接收到的字节流中的 SerialVersionUID与本地实体类中的SerialVersionUID进行比对是否一致,如果一致则进行反序列化,否则拒绝进行反序列化并且抛出异常。SerialVersionUID参数生成方式有两种:
1、手动对SerialVersionUID赋值默认值(1L或者其他);
2、根据类名、方法、接口、属性等进行hash生成一个64位的值。
如果我们事先不定义好SerialVersionUID参数,那么如果类不进行改动则反序列化不会出什么问题,如果改动之后序列化之前SerialVersionUID的值已经改变了,但是反序列化的时候SerialVersionUID如果不对应的话则会抛出异常不能进行反序列化。java为用户定义了默认的序列化和反序列化方法如下例所示:
定义一个可序列化的对象SerializableObject:
public class SerializableObject implements Serializable{
private static final long SerialVersionUID=1L;
private String str0;
private transient String str1;
private static String str2;
public SerializableObject(String str0, String str1) {
this.str0 = str0;
this.str1 = str1;
}
public String getStr0() {
return str0;
}
public void setStr0(String str0) {
this.str0 = str0;
}
public String getStr1() {
return str1;
}
public void setStr1(String str1) {
this.str1 = str1;
}
public static String getStr2() {
return str2;
}
public static void setStr2(String str2) {
SerializableObject.str2 = str2;
}
}
实现序列化与反序列化
public class SerializableMain {
public static void main(String[] args) throws Exception{
OutputStream os=new FileOutputStream(new File("D:"+File.separator+"test2.txt"));
ObjectOutputStream oos=new ObjectOutputStream(os);
oos.writeObject(new SerializableObject("abc","def"));
oos.close();
InputStream is=new FileInputStream(new File("D:"+File.separator+"test2.txt"));
ObjectInputStream ois=new ObjectInputStream(is);
SerializableObject serializableObject=(SerializableObject)ois.readObject();
System.out.println(serializableObject.getStr0());
System.out.println(serializableObject.getStr1());
}
}
执行结果如下:
abc null
上述例子演示了通过jdk提供的方法来对SerializableObject类进行序列化和反序列化,在序列化的时候实际上已经对SerializableObject的str1变量赋值过了,但是反序列化时SerializableObject的str1变量值为空。默认的序列化方式有以下几个结论:
1、序列化以后保存的是对象的信息。
2、transient修饰的变量是不会被序列化的。
3、static修饰的变量是属于类的而不是某个对象的,所以static修饰的变量也是不会被序列化的。
自定义序列化方法
java也可以提供给用户自定义的方式对类进行序列化和反序列化,只需要在类中实现readObject()方法和writeObject()方法即可,虚拟机在序列化和反序列化的时候会先查找对象中是否有readObject()方法和writeObject()方法如果有则调用对象中的该方法,如果没有则才会调用defaultWriteObject()和defaultReadObject()方法,这个也是非常有用的,例如:在开发中对象中某个字段属于敏感字段,如果直接进行序列化进行传输则安全性是非常低的,可以使用transient修饰这个字段,然后使用自定义序列化方法对该字段进行加密后传输,这样可以保证在序列化传输过程中,该字段不会被获取明文。
只需要在SerializableObject类中增加readObject()方法和writeObject()方法,如下:
private void readObject(ObjectInputStream objectInputStream) throws Exception{
System.out.println("自定义反序列化过程");
objectInputStream.defaultReadObject();
int length=objectInputStream.readInt();
char[] cs=new char[length];
for (int i=0;i<length;i++){
cs[i]=objectInputStream.readChar();
}
str1=new String(cs,0,length);
}
private void writeObject(ObjectOutputStream objectOutputStream) throws Exception{
System.out.println("自定义序列化过程");
objectOutputStream.defaultWriteObject();
objectOutputStream.writeInt(str1.length());
for (int i=0;i<str1.length();i++){
objectOutputStream.writeChar(str1.charAt(i));
}
}
再次执行上面的main方法,执行结果如下:
自定义序列化过程 自定义反序列化过程 abc def
可以看到虽然str1是transient修饰的,但是通过使用自定义的序列化和反序列化过程使得str1同样可以加入序列化的字符数组进行传输。
序列化的总结
1、父类实现serializable接口,则所有子类都可以进行序列化。
2、子类实现serializable接口,而父类没有实现该接口,若父类有无参构造函数则不报错,数据为无参构造函数中初始化的值,若父类没有无参构造函数则会抛出java.io.invalidClassException异常。
3、如果序列化的属性是一个对象,则该对象也要实现序列化接口,否则会报错。
4、反序列化时,如果对象的属性有修改或者删除,则修改部分属性丢失但是可以正常反序列化。
5、反序列化时,.SerialVersionUID修改则会报错。
来源:https://www.cnblogs.com/rhodesis/p/11484176.html