|
| 1 | +# serialVersionUID 有什么作用?该如何使用? |
| 2 | + |
| 3 | +当一个对象实现 Serializable 接口时,多数 ide 会提示声明一个静态常量 serialVersionUID(版本标识),那 serialVersionUID 到底有什么作用呢?应该如何 serialVersionUID ? |
| 4 | + |
| 5 | +serialVersionUID 是实现 Serializable 接口而来的,而 Serializable 则是应用于Java 对象序列化/反序列化。对象的序列化主要有两种用途: |
| 6 | + |
| 7 | +- 把对象序列化成字节码,保存到指定介质上(如磁盘等) |
| 8 | +- 用于网络传输 |
| 9 | + |
| 10 | +现在反过来说就是,serialVersionUID 会影响到上述所提到的两种行为。那到底会造成什么影响呢? |
| 11 | + |
| 12 | +[java.io.Serializable](http://docs.oracle.com/javase/7/docs/api/java/io/Serializable.html) doc 文档,给出了一个相对详细解释: |
| 13 | + serialVersionUID 是 Java 为每个序列化类产生的版本标识,可用来保证在反序列时,发送方发送的和接受方接收的是可兼容的对象。如果接收方接收的类的 serialVersionUID 与发送方发送的 serialVersionUID 不一致,进行反序列时会抛出 InvalidClassException。序列化的类可显式声明 serialVersionUID 的值,如下: |
| 14 | + ``` |
| 15 | + ANY-ACCESS-MODIFIER static final long serialVersionUID = 1L; |
| 16 | + ``` |
| 17 | + |
| 18 | +文档上还建议 serialVersionUID 置为 private。 |
| 19 | + |
| 20 | +举例说明如下: |
| 21 | +现在尝试从将一个类 Person 序列化到磁盘和反序列化来说明 serialVersionUID 的作用: Person 两日如下: |
| 22 | +``` |
| 23 | +public class Person implements Serializable { |
| 24 | +
|
| 25 | + private static final long serialVersionUID = 1L; |
| 26 | +
|
| 27 | + private String name; |
| 28 | + private Integer age; |
| 29 | + private String address; |
| 30 | +
|
| 31 | + public Person() { |
| 32 | + } |
| 33 | +
|
| 34 | + public Person(String name, Integer age, String address) { |
| 35 | + this.name = name; |
| 36 | + this.age = age; |
| 37 | + this.address = address; |
| 38 | + } |
| 39 | +
|
| 40 | +
|
| 41 | + @Override |
| 42 | + public String toString() { |
| 43 | + return "Person{" + |
| 44 | + "name='" + name + '\'' + |
| 45 | + ", age=" + age + |
| 46 | + ", address='" + address + '\'' + |
| 47 | + '}'; |
| 48 | + } |
| 49 | +} |
| 50 | +``` |
| 51 | + |
| 52 | +简单的测试一下: |
| 53 | +``` |
| 54 | +@Test |
| 55 | +public void testversion1L() throws Exception { |
| 56 | + File file = new File("person.out"); |
| 57 | + // 序列化 |
| 58 | + ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(file)); |
| 59 | + Person person = new Person("John", 21, "广州"); |
| 60 | + oout.writeObject(person); |
| 61 | + oout.close(); |
| 62 | + // 反序列化 |
| 63 | + ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file)); |
| 64 | + Object newPerson = oin.readObject(); |
| 65 | + oin.close(); |
| 66 | + System.out.println(newPerson); |
| 67 | +} |
| 68 | +``` |
| 69 | + |
| 70 | +测试发现没有什么问题。有一天,因发展需要, 需要在 Person 中增加了一个字段 email,如下: |
| 71 | +``` |
| 72 | +public class Person implements Serializable { |
| 73 | +
|
| 74 | + private static final long serialVersionUID = 1L; |
| 75 | +
|
| 76 | + private String name; |
| 77 | + private Integer age; |
| 78 | + private String address; |
| 79 | + private String email; |
| 80 | +
|
| 81 | + public Person() { |
| 82 | + } |
| 83 | +
|
| 84 | + public Person(String name, Integer age, String address) { |
| 85 | + this.name = name; |
| 86 | + this.age = age; |
| 87 | + this.address = address; |
| 88 | + } |
| 89 | +
|
| 90 | + public Person(String name, Integer age, String address,String email) { |
| 91 | + this.name = name; |
| 92 | + this.age = age; |
| 93 | + this.address = address; |
| 94 | + this.email = email; |
| 95 | + } |
| 96 | +
|
| 97 | + @Override |
| 98 | + public String toString() { |
| 99 | + return "Person{" + |
| 100 | + "name='" + name + '\'' + |
| 101 | + ", age=" + age + |
| 102 | + ", address='" + address + '\'' + |
| 103 | + ", email='" + email + '\'' + |
| 104 | + '}'; |
| 105 | + } |
| 106 | +} |
| 107 | +``` |
| 108 | + |
| 109 | +这时我们假设和之前序列化到磁盘的 Person 类是兼容的,便不修改版本标识 serialVersionUID。再次测试如下 |
| 110 | +``` |
| 111 | +@Test |
| 112 | +public void testversion1LWithExtraEmail() throws Exception { |
| 113 | + File file = new File("person.out"); |
| 114 | + ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file)); |
| 115 | + Object newPerson = oin.readObject(); |
| 116 | + oin.close(); |
| 117 | + System.out.println(newPerson); |
| 118 | +} |
| 119 | +``` |
| 120 | +将以前序列化到磁盘的旧 Person 反序列化到新 Person 类时,没有任何问题。 |
| 121 | + |
| 122 | +可当我们增加 email 字段后,不作向后兼容。即放弃原来序列化到磁盘的 Person 类,这时我们可以将版本标识提高,如下: |
| 123 | +``` |
| 124 | +private static final long serialVersionUID = 2L; |
| 125 | +``` |
| 126 | + |
| 127 | +再次进行序列化,则会报错,如下: |
| 128 | +``` |
| 129 | +java.io.InvalidClassException:Person local class incompatible: stream classdesc serialVersionUID = 1, local class serialVersionUID = 2 |
| 130 | +``` |
| 131 | + |
| 132 | +谈到这里,我们大概可以清楚,serialVersionUID 就是控制版本是否兼容的,若我们认为修改的 Person 是向后兼容的,则不修改 serialVersionUID;反之,则提高 serialVersionUID的值。再回到一开始的问题,为什么 ide 会提示声明 serialVersionUID 的值呢? |
| 133 | + |
| 134 | +因为若不显式定义 serialVersionUID 的值,Java 会根据类的细节自动生成 serialVersionUID 的值,如果对类的源代码作了修改,再重新编译,新生成的类文件的serialVersionUID的取值有可能也会发生变化。类的serialVersionUID的默认值完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,也有可能会导致不同的serialVersionUID。所以 ide 才会提示声明 serialVersionUID 的值。 |
| 135 | + |
| 136 | +附录拓展: |
| 137 | + |
| 138 | +- [深入理解 Java 对象序列化](http://developer.51cto.com/art/201202/317181.htm) |
| 139 | +- [对象的序列化和反序列化](http://www.blogjava.net/lingy/archive/2008/10/10/233630.html) |
| 140 | + |
| 141 | +stackoverflow原址:http://stackoverflow.com/questions/285793/what-is-a-serialversionuid-and-why-should-i-use-it |
0 commit comments