1.为什么实现Serializable接口
话不多说,直接上Java序列化代码
public void testSerialize() throws Exception {
StudentAssess studentAssess=new StudentAssess();
studentAssess.setStudentName("lan");
try(ObjectOutputStream objectOutputStream =
new ObjectOutputStream( new FileOutputStream( new File("student.txt") ) )){
objectOutputStream.writeObject( studentAssess );
}
}
复制代码
上面的代码就是序列化一个StudentAssess类的方式,我们查看writeObject的源码,发现里面writeObject0方法有段逻辑
// 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());
}
}
复制代码
可以得知,在序列化方法里面判断了,如果该对象不是字符串、数组、枚举,而且没有实现Serializable接口的话,在序列化的时候就会抛出NotSerializableException的异常
2.延申
1.什么属性不会被序列化
1、凡是被static修饰的字段是不会被序列化的
2、凡是被transient修饰符修饰的字段也是不会被序列化的
对于第一点,因为序列化保存的是对象的状态而非类的状态,所以会忽略static静态域也是理所应当的。
对于第二点,就需要了解一下transient修饰符的作用了。
如果在序列化某个类的对象时,就是不希望某个字段被序列化(比如这个字段存放的是隐私值,如:密码等),那这时就可以用transient修饰符来修饰该字段。
2.serialVersionUID号有何用?
1、serialVersionUID是序列化前后的唯一标识符,在反序列化时,JVM会把字节流中的序列号ID和被序列化类中的序列号ID做比对,只有两者一致,才能重新反序列化,否则就会报异常来终止反序列化的过程
2、默认如果没有人为显式定义过serialVersionUID,那编译器会为它自动声明一个!,为了serialVersionUID的确定性,写代码时还是建议
3.readObject()函数有什么用
场景1:序列化和反序列化的过程其实是有漏洞的,因为从序列化到反序列化是有中间过程的,如果被别人拿到了中间字节流,然后加以伪造或者篡改,那反序列化出来的对象就会有一定风险了
场景2:反序列化后的单例对象和原单例对象并不相等
场景1解决方案:在readObject方法上加上逻辑判断,如果不符合就抛出异常
// 调用默认的反序列化函数
objectInputStream.defaultReadObject();
// 手工检查反序列化后学生成绩的有效性,若发现有问题,即终止操作!
if( 0 > score || 100 < score ) {
throw new IllegalArgumentException("学生分数只能在0到100之间!");
}
}
复制代码
场景2解决方案:在单例类中手写readResolve()函数,直接返回单例对象,来规避之
private Object readResolve() {
return SingletonHolder.singleton;
}
复制代码
这样一来,当反序列化从流中读取对象时,readResolve()会被调用,用其中返回的对象替代反序列化新建的对象