java.io.Serializable浅析

本文深入探讨了Java对象序列化的概念、实现步骤及注意事项。重点介绍了如何通过序列化将对象状态保存到文件或网络中,以及在序列化过程中需要关注的安全性和资源分配问题。此外,文章还通过示例代码展示了实现序列化的基本步骤,并强调了序列化过程中的关键要素。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

src=http://www.cnblogs.com/gw811/archive/2012/10/10/2718331.html

Java API中java.io.Serializable接口源码:

1 public interface Serializable {
2 }

  类通过实现java.io.Serializable接口可以启用其序列化功能。未实现次接口的类无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。

  Java的"对象序列化"能让你将一个实现了Serializable接口的对象转换成byte流,这样日后要用这个对象时候,你就能把这些byte数据恢复出来,并据此重新构建那个对象了。

  要想序列化对象,你必须先创建一个OutputStream,然后把它嵌进ObjectOutputStream。这时,你就能用writeObject()方法把对象写入OutputStream了。

  writeObject()方法负责写入特定类的对象的状态,以便相应的 readObject()方法可以还原它。通过调用 out.defaultWriteObject 可以调用保存 Object 的字段的默认机制。该方法本身不需要涉及属于其超类或子类的状态。状态是通过使用 writeObject 方法或使用 DataOutput 支持的用于基本数据类型的方法将各个字段写入 ObjectOutputStream 来保存的。

  读的时候,你得把InputStream嵌到ObjectInputStream里面,然后再调用readObject()方法。不过这样读出来的,只是一个Object的reference,因此在用之前,还得先下传。readObject() 方法负责从流中读取并还原类字段。它可以调用 in.defaultReadObject 来调用默认机制,以还原对象的非静态和非瞬态字段。  defaultReadObject()方法使用流中的信息来分配流中通过当前对象中相应命名字段保存的对象的字段。这用于处理类发展后需要添加新字段的情形。该方法本身不需要涉及属于其超类或子类的状态。状态是通过使用 writeObject 方法或使用 DataOutput 支持的用于基本数据类型的方法将各个字段写入 ObjectOutputStream 来保存的。

  在序列化时,有几点要注意的:

  1:当一个对象被序列化时,只保存对象的非静态成员变量(包括声明为private的变量),不能保存任何的成员方法和静态的成员变量。

  2:如果一个对象的成员变量是一个对象,那么这个对象的数据成员也会被序列化。

  3:如果一个可序列化的对象包含对某个不可序列化的对象的引用,那么整个序列化操作将会失败,并且会抛出一个NotSerializableException。我们可以将这个引用标记为transient,那么对象仍然可以序列化。

 

  1、序列化是干什么的?

  简单说就是为了保存在内存中的各种对象的状态,并且可以把保存的对象状态再读出来。虽然你可以用你自己的各种各样的方法来保存Object States,但是Java给你提供一种应该比你自己好的保存对象状态的机制,那就是序列化。

  2、什么情况下需要序列化

  a)当你想把的内存中的对象保存到一个文件中或者数据库中时候;
  b)当你想用套接字在网络上传送对象的时候;
  c)当你想通过RMI传输对象的时候;

  3、当对一个对象实现序列化时,究竟发生了什么?

  在没有序列化前,每个保存在堆(Heap)中的对象都有相应的状态(state),即实例变量(instance ariable)比如:

1 Foo myFoo = new Foo();
2 myFoo .setWidth(37);
3 myFoo.setHeight(70);

  当通过下面的代码序列化之后,MyFoo对象中的width和Height实例变量的值(37,70)都被保存到foo.ser文件中,这样以后又可以把它从文件中读出来,重新在堆中创建原来的对象。当然保存时候不仅仅是保存对象的实例变量的值,JVM还要保存一些小量信息,比如类的类型等以便恢复原来的对象。

1 FileOutputStream fs = new FileOutputStream("foo.ser");
2 ObjectOutputStream os = new ObjectOutputStream(fs);
3 os.writeObject(myFoo);

  4、实现序列化(保存到一个文件)的步骤

  a)Make a FileOutputStream

  java 代码
  FileOutputStream fs = new FileOutputStream("foo.ser");

  b)Make a ObjectOutputStream

  java 代码
  ObjectOutputStream os = new ObjectOutputStream(fs);

  c)write the object

  java 代码
  os.writeObject(myObject1);
  os.writeObject(myObject2);
  os.writeObject(myObject3);

  d) close the ObjectOutputStream

  java 代码
  os.close();

  5、举例说明

 

复制代码
 1 public class Box implements Serializable {
 2     private static final long serialVersionUID = -3450064362986273896L;
 3     
 4     private int width;
 5     private int height;
 6     
 7     public static void main(String[] args) {
 8         Box myBox=new Box();
 9         myBox.setWidth(50);
10         myBox.setHeight(30);
11         try {
12             FileOutputStream fs=new FileOutputStream("F:\\foo.ser");
13             ObjectOutputStream os=new ObjectOutputStream(fs);
14             os.writeObject(myBox);
15             os.close();
16             FileInputStream fi=new FileInputStream("F:\\foo.ser");
17             ObjectInputStream oi=new ObjectInputStream(fi);
18             Box box=(Box)oi.readObject();
19             oi.close();
20             System.out.println(box.height+","+box.width);
21         } catch (Exception e) {
22             e.printStackTrace();
23         }
24     }
25     
26     public int getWidth() {
27         return width;
28     }
29     public void setWidth(int width) {
30         this.width = width;
31     }
32     public int getHeight() {
33         return height;
34     }
35     public void setHeight(int height) {
36         this.height = height;
37     }
38 }
复制代码

  6、相关注意事项

  a)当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;
  b)当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;
  c)并非所有的对象都可以序列化,至于为什么不可以,有很多原因了,比如:

  1.安全方面的原因,比如一个对象拥有private,public等field,对于一个要传输的对象,比如写到文件,或者进行rmi传输 等等,在序列化进行传输的过程中,这个对象的private等域是不受保护的。
  2. 资源分配方面的原因,比如socket,thread类,如果可以序列化,进行传输或者保存,也无法对他们进行重新的资源分配,而且,也是没有必要这样实现。

 

  serialVersionUID

  序列化运行时使用一个称为 serialVersionUID 的版本号与每个可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。如果接收者加载的该对象的类的 serialVersionUID 与对应的发送者的类的版本号不同,则反序列化将会导致  InvalidClassException。可序列化类可以通过声明名为 "serialVersionUID" 的字段(该字段必须是静态 (static)、最终 (final) 的 long 型字段)显式声明其自己的 serialVersionUID:

 ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;

  如果可序列化类未显式声明 serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID 值,如“Java(TM) 对象序列化规范”中所述。不过,强烈建议 所有可序列化类都显式声明 serialVersionUID 值,原因是计算默认的 serialVersionUID 对类的详细信息具有较高的敏感性,根据编译器实现的不同可能千差万别,这样在反序列化过程中可能会导致意外的 InvalidClassException。因此,为保证 serialVersionUID 值跨不同 java 编译器实现的一致性,序列化类必须声明一个明确的 serialVersionUID 值。还强烈建议使用 private 修饰符显示声明 serialVersionUID(如果可能),原因是这种声明仅应用于直接声明类 -- serialVersionUID 字段作为继承成员没有用处。数组类不能声明一个明确的 serialVersionUID,因此它们总是具有默认的计算值,但是数组类没有匹配 serialVersionUID 值的要求。


### Java `Serializable` 接口的作用 `Serializable` 是一个标记接口,用于指示某个类的对象可以通过标准机制进行序列化操作[^1]。当一个类实现了 `java.io.Serializable` 接口时,意味着它的实例可以被转换成字节流形式以便于在网络上传输或存储到文件中。 #### 主要作用 - **对象持久化**:允许将对象的状态保存至磁盘或其他外部介质上,并能够在稍后的时刻恢复其状态[^3]。 - **跨进程通信**:支持分布式环境中不同 JVM 之间传递复杂数据结构(如 RMI 或者消息队列中的消息体),前提是这些数据能够被序列化并反序列化回来[^4]。 --- ### 使用场景分析 以下是几个典型的使用案例: 1. **远程过程调用 (RPC)** 当应用程序需要通过网络发送对象作为参数或者返回值时,通常要求这些对象可序列化。例如,在微服务架构下客户端和服务端交互过程中经常涉及此类需求。 2. **缓存系统** 如果希望把某些临时性的计算结果暂存在内存以外的地方,则可能需要用到序列化技术来完成这一目标。 3. **数据库存储** 对象可以直接写入二进制格式的数据表字段里再读取出来重建原样副本[^5]. --- ### 如何实现 Serializable 接口? 由于它只是一个标志性质的空接口,因此只需要简单声明即可: ```java import java.io.Serializable; public class Person implements Serializable { private static final long serialVersionUID = 1L; // 可选但推荐设置 String name; transient int age; // 不会被序列化的成员变量前加上transient关键字修饰 public Person(String n, int a){ this.name=n; this.age=a; } } ``` 上述代码片段展示了如何创建一个基本的支持序列化的类。注意这里还包含了两个重要概念: - `serialVersionUID`: 这是用来验证版本一致性的唯一标识符。如果不显式指定,默认情况下编译器会基于类的内容生成一个哈希值。但是这样可能导致即使只是修改注释也会改变ID从而引发兼容性问题所以最好手动设定固定数值。 - `transient` 关键字标注那些不想参与序列化进程里的属性。 另外还可以重载默认行为来自定义更复杂的逻辑处理方式比如覆盖特殊的方法像 `writeObject()` 和 `readObject()`, 它们会在执行相应动作之前自动调用[^2]: ```java private void writeObject(java.io.ObjectOutputStream out) throws IOException{ out.defaultWriteObject(); } private void readObject(java.io.ObjectInputStream in )throws IOException , ClassNotFoundException{ in .defaultReadObject(); } ``` 以上例子演示了怎样扩展基础功能以满足特定业务的需求。 --- ### 注意事项 尽管方便易用,但在实际开发工作中应谨慎考虑是否真的有必要让某类型具备此特性因为这可能会带来额外负担包括但不限于性能损耗、维护成本上升等问题。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值