对象流
对象流的介绍
- ObjectInputStream和ObjectOutputStream
- 用于存储和读取基本数据类型数据和对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
- 序列化:用ObjectOutputStream类保存基本数据类型数据或对象的机制
- 反序列化:用ObjectOutputStream类读取基本数据类型数据或对象的机制
- ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量
对象流的序列化
- 对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点(序列化)。当其他程序获取了这种二进制流,就可以恢复成原来的Java对象(反序列化)。
- 序列化的好处在于可以将任何实现了Serializable接口的对象转化为字节数据,使其在保存或者传输时可被还原
- 序列化是RMI(Remote Method Invoke - 远程方法调用)过程的参数和返回值都必须实现的机制,而RMI是JavaEE的基础。因此序列化是JavaEE平台的基础
- 如果需要让某个对象支持序列化机制,则必须让对象所属的类及其属性是可序列化的,为了让某个类是可序列化的,该类必须实现如下两个接口之一。否则,会抛出NoSerializableException异常
- Serializable
- Externalizable
对象流的使用
-
对象的序列化需要满足如下需求,方可序列化:
- 对象需要实现接口:Serializable
- 当前类提供一个全局常量:serialVersionUID
- 凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:
- private static final long serialVersionUID
- serialVersionUID用来表明类的不同版本间的兼容性。简而言之,其目的是对序列化对象进行版本控制,有关各版本反序列化时是否兼容
- 如果类没有显示定义这个静态变量,它的值是Java运行时环境根据类的内部细节自动生成的。若类的实例变量做了修改,serialVersionUID可能发生变化。故建议,显示声明
- 简单的来说,Java的序列化机制是通过运行时判断类的serialVersionUID来验证版本的一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常(InvalidCastException)
- serialVersionUID适用于Java序列化机制,Java序列化机制通过判断类的serialVersionUID来验证版本是否一致。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID和本地相应实体类的serialVersionUID进行比较。如果相同说明是一致的,可以进行反序列化,否则会出现反序列化版本不一致的异常InvalidCastException
- 具体的序列化过程:序列化操作时会把系统当前类的serialVersionUID写入序列化文件中,当反序列化时系统会自动检测文件中的serialVersionUID,判断它是否与当前类中的serialVersionUID一致。如果一致说明传输过来的二进制流中的类对象与当前系统中的类的版本是一致的,可以序列化成功,否则就失败
- 凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:
- 除了当前类需要实现Serializable接口之外,还必须保证类的内部所有属性也必须是可序列化的。(默认情况下,基本数据类型可序列化)
-
简单的例子
-
package com.jl.java.base.stream; import org.junit.Test; import java.io.*; /** * 对象流的使用 * ObjectInputStream * ObjectOutputStream * @author jiangl * @version 1.0 * @date 2021/4/28 18:34 */ public class ObjectInputOutputStreamTest { /** * 序列化过程,将内存中的Java对象保存到磁盘中或者通过网络传输 */ @Test public void testObjectInputStream(){ ObjectOutputStream oos = null; try { oos = new ObjectOutputStream(new FileOutputStream("object.txt")); oos.writeObject(new String("测试序列化")); oos.flush(); } catch (IOException e) { e.printStackTrace(); } finally { if(oos != null){ try { oos.close(); } catch (IOException e) { e.printStackTrace(); } } } } /** * 反序列化过程,从磁盘文件中读取Java对象到内存中 */ @Test public void ObjectInputStreamTest(){ ObjectInputStream ois = null; try { ois = new ObjectInputStream(new FileInputStream(new File("object.txt"))); String s = (String) ois.readObject(); System.out.println(s); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { if(ois != null){ try { ois.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
-
-
自定义类使用ObjectOutputStream进行序列化,使用ObjectInputStream进行反序列化
-
/** * 如果类不实现java.io.Serializable接口,此时使用ObjectOutputStream进行序列化操作时,会抛出异常java.io.NotSerializableException * 如果类中的属性的类没有序列化实现java.io.Serializable接口,也会报错java.io.NotSerializableException * 如果不显示声明serialVersionUID,jvm会根据属性生成一个 * @author jiangl * @version 1.0 * @date 2021/4/28 18:44 */ public class Person implements Serializable { private static final long serialVersionUID = 7246350247175365830L; private String name; private int age; private Address address; public Person(String name, int age) { this.name = name; this.age = age; } public Person(String name, int age, Address address) { this.name = name; this.age = age; this.address = address; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", address=" + address + '}'; } } class Address implements Serializable{ private static final long serialVersionUID = 8246350247175365830L; private String road; private String house; public Address(String road, String house) { this.road = road; this.house = house; } public String getRoad() { return road; } public void setRoad(String road) { this.road = road; } public String getHouse() { return house; } public void setHouse(String house) { this.house = house; } @Override public String toString() { return "Address{" + "road='" + road + '\'' + ", house='" + house + '\'' + '}'; } } /** * 序列化过程,将内存中的Java对象保存到磁盘中或者通过网络传输 */ @Test public void testObjectInputStream2(){ ObjectOutputStream oos = null; try { oos = new ObjectOutputStream(new FileOutputStream("object.java")); oos.writeObject(new Person("测试序列化",123,new Address("路","小区"))); oos.flush(); } catch (IOException e) { e.printStackTrace(); } finally { if(oos != null){ try { oos.close(); } catch (IOException e) { e.printStackTrace(); } } } } /** * 反序列化过程,从磁盘文件中读取还原为Java对象到内存中 * 使用ObjectInputStream实现 */ @Test public void ObjectInputStreamTest2(){ ObjectInputStream ois = null; try { ois = new ObjectInputStream(new FileInputStream(new File("object.java"))); Object o = ois.readObject(); if(o instanceof Person){ Person s = (Person) o; System.out.println(s); } } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { if(ois != null){ try { ois.close(); } catch (IOException e) { e.printStackTrace(); } } } }
-
-
场景分析:
-
场景一:Person类序列化之后,从A端传到B端,在B端进行反序列化,如果两处的serialVersionUID不一致,结果会怎么样
- 会报错InvalidCastException
- java.io.InvalidClassException: com.jl.java.base.stream.Person; local class incompatible: stream classdesc serialVersionUID = 7246350247175365830, local class serialVersionUID = 7346350247175365830
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:560)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1599)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1494)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1748)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1327)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:349)
at com.jl.java.base.stream.ObjectInputOutputStreamTest.ObjectInputStreamTest2(ObjectInputOutputStreamTest.java from InputFileObject:103)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner 3. e v a l u a t e ( P a r e n t R u n n e r . j a v a : 306 ) a t o r g . j u n i t . r u n n e r s . P a r e n t R u n n e r . r u n ( P a r e n t R u n n e r . j a v a : 413 ) a t o r g . j u n i t . r u n n e r . J U n i t C o r e . r u n ( J U n i t C o r e . j a v a : 137 ) a t c o m . i n t e l l i j . j u n i t 4. J U n i t 4 I d e a T e s t R u n n e r . s t a r t R u n n e r W i t h A r g s ( J U n i t 4 I d e a T e s t R u n n e r . j a v a : 69 ) a t c o m . i n t e l l i j . r t . j u n i t . I d e a T e s t R u n n e r 3.evaluate(ParentRunner.java:306) at org.junit.runners.ParentRunner.run(ParentRunner.java:413) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69) at com.intellij.rt.junit.IdeaTestRunner 3.evaluate(ParentRunner.java:306)atorg.junit.runners.ParentRunner.run(ParentRunner.java:413)atorg.junit.runner.JUnitCore.run(JUnitCore.java:137)atcom.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)atcom.intellij.rt.junit.IdeaTestRunnerRepeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)
-
场景二:如果两边的serialVersionUID一致,A端新增了一个属性,然后序列化。B端不变,正常反序列化,结果会怎么样
-
public class Person implements Serializable { private static final long serialVersionUID = 7346350247175365830L; private String name; private int age; private int sex; private Address address; public Person(String name, int age) { this.name = name; this.age = age; } public Person(String name, int age, Address address) { this.name = name; this.age = age; this.address = address; } public Person(String name, int age, int sex, Address address) { this.name = name; this.age = age; this.sex = sex; this.address = address; } public int getSex() { return sex; } public void setSex(int sex) { this.sex = sex; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", sex=" + sex + ", address=" + address + '}'; } }
-
结果:反序列化正常,A端新增的字段在B端反序列化之后不显示(被忽略)。
- Person{name=‘测试序列化’, age=123, address=Address{road=‘路’, house=‘小区’}}
-
-
场景三:两边的serialVersionUID一致,A端不变,正常序列化,在B端增加新的属性,然后反序列化,最后结果如何
-
public class Person implements Serializable { private static final long serialVersionUID = 7346350247175365830L; private String name; private int age; private int sex; private Address address; public Person(String name, int age) { this.name = name; this.age = age; } public Person(String name, int age, Address address) { this.name = name; this.age = age; this.address = address; } public Person(String name, int age, int sex, Address address) { this.name = name; this.age = age; this.sex = sex; this.address = address; } public int getSex() { return sex; } public void setSex(int sex) { this.sex = sex; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", sex=" + sex + ", address=" + address + '}'; } }
-
结果:
- Person{name=‘测试序列化’, age=123, sex=0, address=Address{road=‘路’, house=‘小区’}}
-
序列化、反序列化正常,B端反序列化之后,新增的属性被赋予了默认值
-
-
序列化不能操作static和transient修饰的成员变量
-
序列化不能操作被static修饰的变量
-
public class TestSerialStatic implements Serializable { private static final long serialVersionUID = -4662715330378065625L; public static int ver = 123; public transient String aa; public static void main(String[] args) { ObjectOutputStream oos = null; ObjectInputStream ois = null; try { //写数据 oos = new ObjectOutputStream(new FileOutputStream(new File("ts.java"))); TestSerialStatic testSerialStatic = new TestSerialStatic(); System.out.println(testSerialStatic.getVer()); testSerialStatic.setAa("dfadfasdf"); System.out.println(testSerialStatic); oos.writeObject(testSerialStatic); testSerialStatic.setVer(12333); //读取数据 ois = new ObjectInputStream(new FileInputStream("ts.java")); Object o = ois.readObject(); if(o instanceof TestSerialStatic){ testSerialStatic = (TestSerialStatic) o; System.out.println(testSerialStatic.getVer()); System.out.println(testSerialStatic); } } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { if(oos != null){ try { oos.close(); } catch (IOException e) { e.printStackTrace(); } } if(ois != null){ try { ois.close(); } catch (IOException e) { e.printStackTrace(); } } } } public int getVer() { return ver; } public void setVer(int ver) { TestSerialStatic.ver = ver; } public String getAa() { return aa; } public void setAa(String aa) { this.aa = aa; } @Override public String toString() { return "TestSerialStatic{" + "ver=" + ver + ", aa='" + aa + '\'' + '}'; } }
-
结果为
- 123
TestSerialStatic{ver=123, aa=‘dfadfasdf’}
12333
TestSerialStatic{ver=12333, aa=‘null’}
- 123
-
解析:因为序列化不操作静态属性,所以ver静态变量不是从对象中获取的,也就是不从二进制流中获取,而是直接从类中获取。可以理解为序列化保存的是对象的状态,而静态变量是类的状态,所以序列化不保存静态变量。
-
-
transient关键字作用是控制属性不序列化,在类的属性前加上transient关键字,可以阻止该变量被序列化到文件中,在被反序列化之后,被transient修饰的属性的值被设置成初始值,(如int为0,对象型为null)