序列化

Java 序列化
序列化作用
注意事项
Java 序列化的缺点
举例说明
项目中曾遇到的一个小问题
参考

序列化定义:将一个对象编码成一个字节流,称作将该对象序列化,反之,将字节流重新构建成对象,则称作反序列化。

序列化作用
序列化将对象编码成字节流,主要用于对象的持久化,远程通信,跨进程访问等地方。
比如开发中常用到的 ORM
框架 Mybatis
,或者 JPA
都是需要将实体类实现序列化接口才能使用,再者如缓存,缓存的对象如果没有实现 Serializable
接口,那么会抛出异常。
注意事项
父类实现了序列化,则子类自动实现了序列化,即子类不需要显式实现 Serializable
接口。
当父类没有实现序列化,而子类需要实现时,子类需要显式实现 Serializable
接口,并且父类中需要有无参的构造函数。
序列化只对对象的属性进行保存,而不会保存其方法。
当类中的实例变量引用了其他对象,那么在对该类进行序列化时,引用的对象也会被序列化。

Java 序列化的缺点
序列化会让类变得不灵活。
实现序列化之后,会有一个序列化 ID ,我们可以使用默认的 ID ,也可以重写这 ID,如果没有显式指定该序列 ID ,系统会经过一系列复杂的计算算出该 ID,那当我们改变类中的方法时,这个 ID 就会变化,这时候往往就会报 InvalidClassException
异常。

序列化可以重构,存在安全隐患。

无法跨语言,Java 进行序列化,别的语言无法进行反序列化。

序列化后的码流太大。

Java 序列化性能较低。

举例说明
父类实现了序列化,则子类自动实现了序列化,即子类不需要显式实现 Serializable
接口

Parent.java

public class Parent implements Serializable { 
int age;
 public Parent(int age)
 { 
this.age = age; 
}
 public int getAge()
 {
 return age; 
}
 public void setAge(int age)
 { 
this.age = age;
 } 
@Override public String toString()
 { 
return "Parent{" + "age=" + age + '}'; }
}

Children.java

public class Children extends Parent { 
public Children(int age) 
{ 
super(age);
 } 
public void say() 
{ 
System.out.println("Hello World " + age);
 } 
@Override public String toString() 
{
 return "Children{" + "} " + super.toString(); }
}

Test.java

public class Test {
 public static void main(String[] args) throws IOException, ClassNotFoundException { 
// 持久化到文件中 Children children = new Children(12);
FileOutputStream outputStream = new FileOutputStream("test.txt"); ObjectOutputStream objectOutputStream = new
ObjectOutputStream(outputStream); objectOutputStream.writeObject(children); 
outputStream.close();
objectOutputStream.close();
// 从文件中读取 FileInputStream inputStream = new FileInputStream("test.txt");
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); 
Children o = (Children) objectInputStream.readObject(); 
o.say();
inputStream.close();
objectInputStream.close();
 System.out.println(o);
// new File("test.txt").delete(); }
}

控制台打印出

children

在这个例子中,父类 Parent实现了 Serializable接口,子类序列化时并不需要显式实现 Serializable。 当父类没有实现序列化,而子类需要实现时,子类需要显式实现 Serializable接口,并且父类中需要有无参的构造函数。

如果将 Parent改写,不实现序列化,让子类自己来实现会怎样呢?

Parent.java

public class Parent { int age; public Parent(int age) { this.age = age; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Parent{" + "age=" + age + '}'; }}

Children.java

public class Children extends Parent implements Serializable{ public Children(int age) { super(age); } public void say() { System.out.println("Hello World " + age); } @Override public String toString() { return "Children{" + "} " + super.toString(); }}

然后运行 Test.java

error
控制台会打印出没有合适的构造方法。
序列化会让类变得不灵活。

继续改写上述代码(注意:我们上述代码没有重写 serialVersionUID),我们先将该 Children保存到文件中,然后再对 Children进行修改,修改之后,再从文件中把刚才保存的字节码反序列化,看看会出现什么问题。步骤:
运行 Test.java

改写 Children.java,新增字段 name,并改写 say()方法注释掉 Test.java
中的序列化方法,执行其中的反序列化

改写后的 Children.java

public class Children extends Parent {
 private String name; public String getName()
 { 
return name; 
} 
public void setName(String name) {
 this.name = name;
 } 
public Children(int age) { 
super(age);
 }
 public void say() { 
System.out.println("Hello World " + age + ",name: " + name); 
} 
@Override public String toString() {
 return "Children{" + "name='" + name + '\'' + "} " + super.toString(); }
}

控制台打印出:

serial_version_error

为了解决这个问题,我们需要在 Children.java中重写序列化 ID在 Children.java
加入 private static final long serialVersionUID = -1;
即可。
序列化只对对象的属性进行保存,而不会保存其方法。****序列化可以重构,存在安全隐患。

然后重新运行按照上诉步骤重新执行一遍,会发现不会报异常了,并且会发现当我们在运行时期改变 Children
中的 say()方法时,打印出的 say() 方法变了,而 Children中的属性 age
不会改变,且会发现 name默认为 null,这里在运行时我们重构了 Children
类,改变了一些属性及方法,这也就存在了安全隐患。当类中的实例变量引用了其他对象,那么在对该类进行序列化时,引用的对象也会被序列化。如果该引用的对象没有实例化,则不需要序列化。

创建一个 XiaoMing类, 改写 Children ,让 Children包含 XiaoMing的引用,并且在 Children的构造函数中初始化 XiaoMing,然后运行 Test.java。
控制台打印出:

序列化_第1张图片
xiaoming_error
表示 XiaoMing
没有实现序列化接口,这说明在序列化对象的时候,对象的引用对象也会被序列化。

你可能感兴趣的:(序列化)