黑马程序员——————> 自定义序列化

在一些特殊的场景下,如果一个类里包含的某些实例变量是敏感信息,例如银行账户信息,这时不希望系统将该实例变量值进行实例化;或者某个实例变量的类型是不可序列化的,因此不希望对该实例变量进行递归实例化,以避免引发异常。

 

通过在实例变量前面使用transient关键字修饰,可以指定java序列化时无须理会该实例变量。如下Person类与前面的Person类几乎完全一样,只是它的age使用了transient关键字修饰。

 1 public class Person implements Serializable
 2 {
 3     private String name;
 4     //transient只能修饰实例变量,不可修饰java程序中的其他成分
 5     private transient int age;
 6
 7     //此处没有提供无参构造
 8     public Person(String name, int age)
 9     {
10         System.out.println("有参数的构造器");
11         this.name = name;
12         this.age = age;
13     }
14
15     public String getName() {
16         return name;
17     }
18
19     public void setName(String name) {
20         this.name = name;
21     }
22
23     public int getAge() {
24         return age;
25     }
26
27     public void setAge(int age) {
28         this.age = age;
29     }
30
31 }

下面程序先序列化一个Person对象,然后再反序列化该Person对象,得到反序列化的Person对象后程序输出该对象的age实例变量值。

 1 public class TransientTest
 2 {
 3     public static void main(String[] args)
 4     {
 5         try(
 6                 //创建一个ObjectOutputStream输出流
 7                 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("transient.txt"));
 8                 //创建一个ObjectInputStream输入流
 9                 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("transient.txt")))
10         {
11             Person per = new Person("孙悟空",500);
12             //系统将per对象转换成了字节序列输出
13             oos.writeObject(per);
14             Person p = (Person) ois.readObject();
15             System.out.println(p.getAge()+""+p.getName());
16         }
17         catch(Exception ex)
18         {
19             ex.printStackTrace();
20         }
21
22     }
23
24 }

上面程序分别为Person对象的两个实例变量指定了值。由于本程序中的Preson类的age实例变量使用transient关键字修饰,所以程序代码将输出0;

 

使用transient关键字修饰实例变量虽然简单方便,但被transient修饰的实例变量将被完全隔离在序列化机制之外,这样导致在反序列化回复java对象时无法取得该实例变量的值。java还提供了一种自定义序列化机制,通过这种自定义序列化机制可以让程序控制如何序列化各实例变量,甚至完全不序列化某些实例变量(与使用transient关键字的效果相同)。

在序列化和反序列化过程中需要特殊处理的类应该提供如下特殊签名的方法,这些特殊的方法用以实现自定义序列化。

private void writeObject(java.io.ObjectOutputStream out)throws IOException

private void readObject(java.io.ObjectInputStream in)throws IOException,ClassNotFoundException;

private void readObjectNoData() throws ObjectStreamException;

writeObject()方法负责写入特定类的实例状态,以便相应的readObject()方法可以恢复它。通过重写该方法,程序员可以完全获得对序列化机制的控制,可以自主决定哪些实例变量需要序列化,需要怎样序列化。在默认情况下,该方法会调用out.defaultWriteObject来保存java 对象的各实例变量,从而可以实现序列化java对象状态的目的。

 

readObject()方法负责从流中读取并恢复对象的实例变量,通过重写该方法,程序员可以完全获得对反序列化机制的控制,可以自主决定需要反序列化哪些实例变量,以及如何进行反序列化。在默认情况下,该方法会调用in.defaultReadObject来恢复java对象的非瞬态实例变量。在通常情况下,readObject()方法与writeObject()方法对应,如果writeObject()方法中对java对象的实例变量进行了一些处理,则应该在readObject()方法中对其实例变量进行相应的反处理,以便正确恢复该对象。

 

当序列化流不完整时,readObjectNoData()方法可以用来正确的初始化反序列化的对象。例如,接收方使用的反序列化类的版本不同于发送方,或者接收方版本扩展的类不是发送方版本扩展的类,或者序列化流被篡改时,系统都会调用readObjectNoData()方法来初始化反序列化对象。

 

下面的Person类提供了writeObject()和readObject()两个方法,其中writeObject()方法在保存Person对象时将其name实例变量包装成StringBuffer,并将其字符序列反转后写入;在readObject()方法中处理name的策略与此对应,先将读取的数据强制类型转换成StringBuffer,再将其反转后赋给name实例。

 1 public class Person implements Serializable
 2 {
 3     private String name;
 4     private int age;
 5     //此处没有提供无参构造
 6     public Person(String name,int age)
 7     {
 8         System.out.println("有参数的构造器");
 9         this.name = name;
10         this.age = age;
11     }
12     public String getName() {
13         return name;
14     }
15     public void setName(String name) {
16         this.name = name;
17     }
18     public int getAge() {
19         return age;
20     }
21     public void setAge(int age) {
22         this.age = age;
23     }
24
25
26
27     private void writeObject(java.io.ObjectOutputStream out) throws IOException
28     {
29         //将name实例变量值反转后写入二进制流
30         out.writeObject(new StringBuffer(name).reverse());
31         out.writeInt(age);
32     }
33
34     private void readObject(java.io.ObjectInputStream in) throws Exception
35     {
36         //将读取的字符串反转后赋给name变量
37         this.name = ((StringBuffer)in.readObject()).reverse().toString();
38         this.age = in.readInt();
39     }
40 }

上面程序中的方法用以实现自定义序列化,对于这个Preson类而言,序列化,反序列化Preson实例并没有什么区别,去别在于序列化后的对象流,即使有Cracker截获到Person对象流,他看到的name也是加密后的name值,这样就提高了序列化的安全性。

还有一种更彻底的自定义机制,它甚至可以在序列化对象时将该对象替换成其他对象。如果需要实现序列化某个对象时替换该对象,则应为序列化类提供如下特殊方法。

ANY-ACCESS-MODIFIER Object writeReplace()

此writeReplace()方法将由序列化机制调用,只要该方法存在。因为该方法可以拥有私有(private),受保护的(protected),和包私有(package-private)等访问权限,所以其子类有可能获得该方法。例如下面的Person类提供了writeReplace()方法,这样可以在写入Person对象时将该对象替换成ArrayList.

 1 public class Person implements Serializable
 2 {
 3     private String name;
 4     private int age;
 5     //注意此处没有提供无参构造
 6     public Person(String name, int age)
 7     {
 8         System.out.println("带参构造器");
 9         this.name = name;
10         this.age = age;
11     }
12     public String getName() {
13         return name;
14     }
15     public void setName(String name) {
16         this.name = name;
17     }
18     public int getAge() {
19         return age;
20     }
21     public void setAge(int age) {
22         this.age = age;
23     }
24
25     //重写writeReplace方法,程序在序列化该对象之前,先调用该方法
26     private Object writeReplace()
27     {
28         ArrayList<Object> list = new ArrayList<Object>();
29         list.add(name);
30         list.add(age);
31         return list;
32
33     }
34
35 }

java的序列化机制保证在序列化某个对象之前,先调用该对象的writeReplace()方法,如果该方法返回另一个java对象,则系统转为序列化另一个对象。如下程序表面上是序列化Preson对象,但实际上序列化的是ArrayList.

 1 public class ReplaceTest
 2 {
 3     public static void main(String[] args)
 4     {
 5         try(
 6             //创建一个ObjectOutputStream输出流
 7             ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("replace.txt"));
 8             //创建一个ObjectInputStream输入流
 9             ObjectInputStream ois = new ObjectInputStream(new FileInputStream("replace.txt")))
10         {
11             Person per = new Person("段亚东",25);
12             //系统将per对象转换成字节序列输出
13             oos.writeObject(per);
14             //反序列化读取到的是ArrayList
15             ArrayList list = (ArrayList)ois.readObject();
16             System.out.println(list);
17         }
18         catch(Exception ex)
19         {
20             ex.printStackTrace();
21         }
22
23     }
24
25 }

 上面程序使用writeObject()写入了一个Person对象,但第二行代码使用readObject()方法返回的实际上是一个ArrayList对象,这是因为Person类的writeReplace()方法返回了一个ArrayList对象,所以序列化机制在序列化Person对象时,实际上是转为序列化ArrayList对象。

根据上面的介绍,可以知道系统在序列化某个对象之前,会先调用该对象的writeReplace()和writeObject()两个方法,系统总是先调用被序列化对象的writeReplace()方法,如果该方法返回另一个对象,系统将再次调用另一个对象的writeReplace()方法,直到该方法不再返回另一个对象为止,程序最后将调用该对象的writeObject()方法来保存该对象的状态。

时间: 2024-10-13 12:15:43

黑马程序员——————> 自定义序列化的相关文章

黑马程序员——自定义类加载器

自定义类加载器是在是血的不怎明白 这里只学会了一个简单的加密解密方法 //定义一个加密方法 public static void md(InputStream inputStream,OutputStream outputStream) throws Exception{  int b = 0;  while((b = inputStream.read())!=-1){   //使用^异或,相同的为0,不同的为1,由于0xff是255,后八位都是1,   //这样就将原来的1变成了0,0变成了1

黑马程序员——IO篇

------- android培训.java培训.期待与您交流! ---------- IO(Input Output)流 1.IO流用来处理设备之间的数据传输 2.Java对数据的操作是通过流的方式 3.Java用于操作流的对象都在IO包中 4.流按操作数据分为两种:字节流与字符流 . 字符流的数据字节流其实都能处理,因为无论什么在计算机上最后都是以字节存在的,但是其中有一部分数据它是以文本形式存在的,所以为了方便有了字符流,字符流里融合了编码表,而只有文字用到了编码表,所以字符流用来处理文本

黑马程序员_IO流2

File类 1.File类 不属于流对象.作用:将机器上的路径和目录封装成一个File对象,提供了很多的方法和成员变量,让我们操作目录和路径   目录就是文件夹.路径,指由文件夹组成的到达文件夹的通道. 2.File类的静态成员变量   一共提供的4个变量,掌握的只有2个,跨平台 static String separator  结果\ Windows目录分隔符  Linux下 / static String pathSeparator 结果 ; Windows下的路径与路径之间的分隔符 Lin

黑马程序员-常见的一些流和编码表

对象的序列化(对象要实现Serializable序列化接口,类似实现comparable ) 1.操作对象的流:ObjectOutputStream ObjectInputStream ObjectOutputStream : 将 Java对象的基本数据类型和图形写入 OutputStream. 通过在流中使用文件可以实现对象的持久存储. 构造函数:ObjectOutputStream(OutputStream out)//初始化时要有目的 方法: writeObject(Object obj)

黑马程序员——网络编程篇

------- android培训.java培训.期待与您交流! ---------- 概述   1.网络模型        (1).OSI参考模型        (2).TCP/IP参考模型   2.网络通讯要素         (1).IP地址        (2).端口号         (3).传输协议    3.过程        1,找到对方IP. 2,数据要发送到对方指定的应用程序上.为了标识这些应用程序,所以给这些网络应用程序都用数字进行标识. 为了方便称呼这个数据,叫做端口(逻

黑马程序员------IO(五)

黑马程序员------IO(五) 1.1  操作对象(示例1)ObjectInputStream与ObjectOutputStream 被操作的对象需要实现Serializable. Serializable:用于给被序列化的类加入ID号,用于判断类和对象是否是同一个版本 类通过实现java.io.Serializable接口以启用序列化功能,Serializable只是一个标记接口. 1 示例1: 2 import java.io.*; 3 4 class ObjectStreamDemo 5

黑马程序员_API

------- android培训.java培训.期待与您交流! ---------- ======================API========================================= 看api的步骤: 1.看类的说明.其所属的包以及出现的版本. 2.看其构造函数. 3.看普通的方法.看时注意参数,和返回值类型. Object类 1. private static native void registerNatives (); 见到本地关键字修饰的方法,这个方

黑马程序员_集合

集合1.集合和对象数组的区别: 数组的长度不可变,集合的可变: 数组可以存储基本数据类型和对象,集合只能存储对象. 集合的框架图 集合派系的顶层接口Collection1.Collection集合存储对象的方法: add(E e)将元素存储到集合中 addAll(Collection c)将一个集合添加到另外的集合中2.Collection集合提取对象的方法: 通过迭代器iterator中的方法:hasNext()和next()来取出 Iterator it=new iterator(); wh

黑马程序员-单例模式

单例设计模式 单例模式是一种常用的软件设计模式.在它的核心结构中只包含一个被称为单例类的特殊类.通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源.如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案.显然单例模式的要点有三个:一是某个类只能有一个实例:二是它必须自行创建这个实例:三是它必须自行向整个系统提供这个实例. 解决的问题:保证一个类在内存中的对象唯一性. 比如:多程序读取一个配置文件时,建议配置文件封装成对象.会方便