Java基础知识强化104:Serializable接口 与 Parcelable接口

1. 什么是 序列化 和 反序列化 ?

    序列化 :序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。 

  反序列化 :是指把这种二进制流数据还原成对象。

什么时候使用序列化:

  1):Java对象序列化可以实现分布式对象。主要应用例如:RMI要利用对象序列化运行远程主机上的服务,就像在本地机上运行对象时一样。
  2):Java对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据。可以将整个对象层次写入字节流中,可以保存在文件中或在网络连接上传递。利用对象序列化可以进行对象的"深复制",即复制对象本身及引用的对象本身。序列化一个对象可能得到整个对象序列

2. 实现序列化方式:

一是:实现Serializable接口(是JavaSE本身就支持的);

二是:实现Parcelable接口(是Android特有功能,效率比实现Serializable接口高效,可用于Intent数据传递,也可以用于进程间通信(IPC))。

实现Serializable接口非常简单,声明一下就可以了,而实现Parcelable接口稍微复杂一些,但效率更高,推荐用这种方法提高性能。

注:

  Android中Intent传递对象有两种方法:一是Bundle.putSerializable(Key,Object),另一种是Bundle.putParcelable(Key,Object)。当然这些Object是有一定的条件的,前者是实现了Serializable接口,而后者是实现了Parcelable接口。

3. Serializable接口Parcelable接口 的区别

(1)Parcelable使用起来稍复杂点,而后者使用起来非常简单。

(2)Parcelable效率比Serializable高,支持Intent数据传递,也支持进程间通信(IPC)。

(3)Parcelable使用时要用到一个Parcel,可以简单将其看为一个容器,序列化时将数据写入Parcel,反序列化时从中取出。

(4)在使用内存的时候,Parcelable比Serializable性能高,所以推荐使用Parcelable。Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC。Parcelable不能使用在要将数据存储在磁盘上的情况,因为Parcelable在外界有变化的情况下不能很好的保证数据的持续性。尽管Serializable效率低点,但此时还是建议使用Serializable 。

 

4. Parcelable 和 Serializable都能实现序列化并且都可用于Intent间传递数据,那么两者之间如何选取呢?

解析:

  Serializable是Java中序列化接口,其使用起来很方便但是开销很大,序列化和反序列化过程中需要大量的I/0操作。而Parcelable是Android中序列化方式,因此更适合在Android平台上,它缺点是使用起来稍微有点麻烦,但是它的效率很高,这是Android推荐的序列化方式,因此在Android开发中首选序列化Parcelable。Parcelable主要用在内存序列化上,通过Parcelable将对象序列化到存储设备中或者将对象序列化后通过网络传输也都是可以的,但是这个过程会稍微复杂一点,因此在这两种情况下建议大家使用Serializable。

5. Serializable接口:

(1)使用Serializable接口步骤:

  1)将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的;

  2)然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。

(2)使用Serializable的Demo:

  1)新建一个Java工程,如下:

  2)同时 UserSerializableTest ,如下:

User:

 1 package com.himi.serializable;
 2 
 3 import java.io.Serializable;
 4 
 5 public class User implements Serializable{
 6     /**
 7      * serialVersionUID:
 8      * Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,
 9      * JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,
10      * 如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。
11      */
12     private static final long serialVersionUID = 1L;
13     private String name;
14     private int age;
15     private String email;
16     
17     
18     public User(String name, int age, String email) {
19         this.name = name;
20         this.age = age;
21         this.email = email;
22     }
23     
24     public String getName() {
25         return name;
26     }
27     
28     @Override
29     public String toString() {
30         return "User [name=" + name + ", age=" + age + ", email=" + email + "]";
31     }
32     
33     
34     public void setName(String name) {
35         this.name = name;
36     }
37     public int getAge() {
38         return age;
39     }
40     public void setAge(int age) {
41         this.age = age;
42     }
43     public String getEmail() {
44         return email;
45     }
46     public void setEmail(String email) {
47         this.email = email;
48     }
49 
50 }

 SerializableTest :

 1 package com.himi.serializable;
 2 
 3 import java.io.FileInputStream;
 4 import java.io.FileNotFoundException;
 5 import java.io.FileOutputStream;
 6 import java.io.IOException;
 7 import java.io.ObjectInputStream;
 8 import java.io.ObjectOutputStream;
 9 
10 public class SerializableTest {
11 
12     public static void main(String[] args) {
13         //序列化过程
14         User user = new User("hebao",24,"123123123");
15         try {
16             ObjectOutputStream out = new ObjectOutputStream(
17                     new FileOutputStream("cache.txt") );
18             out.writeObject(user);
19             out.close();
20             
21             System.out.println("------序列化完毕------");
22             
23         } catch (FileNotFoundException e) {
24             // TODO Auto-generated catch block
25             e.printStackTrace();
26         } catch (IOException e) {
27             // TODO Auto-generated catch block
28             e.printStackTrace();
29         }
30         
31         //反序列化过程
32         try {
33             ObjectInputStream in = new ObjectInputStream(
34                     new FileInputStream("cache.txt"));
35             User newUser = (User) in.readObject();
36             in.close();
37             
38             System.out.println("------反序列化完毕------");
39             System.out.println("newUser name:"+newUser.getName());
40             System.out.println("newUser age:"+newUser.getAge());
41             System.out.println("newUser email:"+newUser.getEmail());
42         } catch (FileNotFoundException e) {
43             // TODO Auto-generated catch block
44             e.printStackTrace();
45         } catch (ClassNotFoundException e) {
46             // TODO Auto-generated catch block
47             e.printStackTrace();
48         } catch (IOException e) {
49             // TODO Auto-generated catch block
50             e.printStackTrace();
51         }
52 
53     }
54 
55 }

运行程序,出现Console,如下:

上面中提到的serialVersionUID后面那一串数字又是什么含义

  我们要明白系统既然指定了serialVersionUID,那么它必须是有用的。这个serialVersionUID是用来辅助序列化和反序列化过程的,原则上序列化后的数据中的 serialVersionUID 只有和当前类的serialVersionUID相同才能够正常地被反序列化。

  serialVersionUID的详细工作机制:序列化的时候系统会把当前类的serialVersionUID写入序列化的文件中(或者其他中介),当反序列化的时候系统会检测文件中的serialVersionUID,看它是否和当前类的serialVersionUID是一致的,如果一致就说明序列化的类的版本和当前类的版本是相同的,这个时候就可以成功反序列化;否则就说明当前类和序列化的类相比发生了某些变换,比如成员变量的数量,类型可能发生了改变,这个时候是无法之正常反序列化的,因此会报一下错误:

java.io.InvalidClassException: com.tbynet.admin.AdminBean; local class incompatible: stream classdesc serialVersionUID = 7479061626399129990, local class serialVersionUID = -5738694578196915867

  一般来说,我们应该手动指定serialVersionUID的值,比如1L,也可以让Eclipse根据当前类的结构自动去生成它的hash值,这样序列化和反序列化时两者的serialVersionUID是相同的,因此可以正常进行反序列化。如果不手动指定serialVersionUID,反序列化时当前类有所改变,比如增加或者删除了某些成员变量,那么系统就会重新计算当前类的hash值并把它赋给serialVersionUID,这个时候当前类的serialVersionUID就和序列化的数据中的serialVersionUID不一致,于是反序列化失败,程序就会失败crash。所以,我们可以明显感觉到serialVersionUID的作用,当我们手动指定它以后,就可以在很大程度上避免反序列化过程的失败。比如当前版本升级之后,我们可能删除了某个成员变量也可能增加了一些新的成员变量,这个时候我们的反序列化过程中仍然能够成功,程序仍然能够最大限度恢复数据,相反,如果不指定serialVersionUID的话,程序就会挂掉。当然我们还要考虑另外一种情况,如果类结构发生了非常规改变了,比如修改了类名,修改了成员变量的类型,这个时候尽管serialVersionUID验证通过了,但是反序列化过程还是会失败,因为类结构有了毁灭性的改变,根本无法从来老版本的数据中还原一个新的类结构的对象。

  我们从上面知道,给serialVerisonUID指定为1L或者采用Eclipse根据当前类结构去生成hash值,这两者并没有本质区别,效果完全一样。

  以下两点需要注意

  (1)首先静态成员变量属于类不属于对象,所以不会参与序列化过程。static代表类的状态

  (2)其次用transient关键字标记的成员变量不参与序列化过程。 transient代表对象的临时数据

 

6. Parcelable接口:

Parcelable也是一个接口,只要实现这个接口,一个类的对象就可以实现序列化并可以通过 IntentBinder传递。

(1)Parcelable接口的定义:

public interface Parcelable 
{
    //内容描述接口,基本不用管
    public int describeContents();
    //写入接口函数,打包
    public void writeToParcel(Parcel dest, int flags);
    //读取接口,目的是要从Parcel中构造一个实现了Parcelable的类的实例处理。因为实现类在这里还是不可知的,所以需要用到模板的方式,继承类名通过模板参数传入
    //为了能够实现模板参数的传入,这里定义Creator嵌入接口,内含两个接口函数分别返回单个和多个继承类实例
    public interface Creator<T> 
    {
           public T createFromParcel(Parcel source);
           public T[] newArray(int size);
    }
}

(2)Parcelable的方法说明:

系统已经为我们提供许多实现了Parcelabe接口的类,它们都是可以直接进行序列化的,比如IntentBundleBitmap等等,同时ListMap也是可以序列化的,前提是它们里面的每个元素都是可以序列化的。

(3)使用Parcelable接口步骤:

  1)implements Parcelable

  2)重写writeToParcel方法,将你的对象序列化为一个Parcel对象,即:将类的数据写入外部提供的Parcel中,打包需要传递的数据到Parcel容器保存,以便从 Parcel容器获取数据

  3)重写describeContents方法,内容接口描述,默认返回0就可以

  4)实例化静态内部对象CREATOR实现接口Parcelable.Creator

public static final Parcelable.Creator<T> CREATOR

注:其中public static final一个都不能少,内部对象CREATOR的名称也不能改变,必须全部大写。需重写本接口中的两个方法:createFromParcel(Parcel in) 实现从Parcel容器中读取传递数据值,封装成Parcelable对象返回逻辑层,newArray(int size) 创建一个类型为T,长度为size的数组,仅一句话即可(return new T[size]),供外部类反序列化本类数组使用。

简而言之:通过writeToParcel将你的对象映射成Parcel对象,再通过createFromParcel将Parcel对象映射成你的对象。也可以将Parcel看成是一个流,通过writeToParcel把对象写到流里面,在通过createFromParcel从流里读取对象,只不过这个过程需要你来实现,因此写的顺序和读的顺序必须一致。

(4)实现Parcelable接口实体类内部分析:

User:

 1 package com.himi.parcelabledemo.bean;
 2 
 3 import android.os.Parcel;
 4 import android.os.Parcelable;
 5 
 6 public class User implements Parcelable
 7 {
 8     private String name;
 9     private int age;
10     private String email;
11 
12     public User(String name, int age, String email) {
13         this.name = name;
14         this.age = age;
15         this.email = email;
16     }
17 
18     public User(Parcel source) {
19         name = source.readString();
20         age = source.readInt();
21         email = source.readString();
22     }
23 
24     public String getName() {
25         return name;
26     }
27 
28     @Override
29     public String toString() {
30         return "User [name=" + name + ", age=" + age + ", email=" + email + "]";
31     }
32 
33     public void setName(String name) {
34         this.name = name;
35     }
36 
37     public int getAge() {
38         return age;
39     }
40 
41     public void setAge(int age) {
42         this.age = age;
43     }
44 
45     public String getEmail() {
46         return email;
47     }
48 
49     public void setEmail(String email) {
50         this.email = email;
51     }
52 
53     /**
54      * 内容描述
55      */
56     @Override
57     public int describeContents() {
58         // TODO Auto-generated method stub
59         return 0;
60     }
61 
62     /**
63      * 序列化过程:必须按成员变量声明的顺序进行封装
64      */
65     @Override
66     public void writeToParcel(Parcel dest, int flags) {
67         // TODO Auto-generated method stub
68         dest.writeString(name);
69         dest.writeInt(age);
70         dest.writeString(email);
71         
72     }
73     
74     /**
75      * 反序列过程:必须实现Parcelable.Creator接口,并且对象名必须为CREATOR
76      * 读取Parcel里面数据时必须按照成员变量声明的顺序,Parcel数据来源上面writeToParcel方法,
77      * 读出来的数据供逻辑层使用.
78      */
79     public static final Parcelable.Creator<User> CREATOR = new Parcelable
80             .Creator<User>() {
81         
82         @Override
83         public User[] newArray(int size) {
84             // TODO Auto-generated method stub
85             return new User[size];
86         }
87         
88         @Override
89         public User createFromParcel(Parcel source) {
90             // TODO Auto-generated method stub
91             return new User(source);
92         }
93     };
94             
95 
96 }

这里先说明一下Parcel,Parcel内部包装了可序列化的数据,可以在Binder中自由传输。

上面代码可以看出:在让实体类User序列化的过程中,需要实现的功能有序列化反序列化内容描述

  • 序列化功能:由writeToParcel方法,最终是通过Parcel中一系列write方法来完成。
  • 反序列化功能:由CREATOR来完成,其内部标明了如何创建序列化对象和数组,并通过Parcel的一系列read方法来完成反序列化过程。
  • 内容描述功能:由describeContents方法来完成,几乎在所有的情况下这个方法都是应该返回 0,仅当当前对象中存在文件描述符时候,才返回 1

(5)使用Parcelable的Demo:

  1)新建一个Android工程,如下:

  2)来到主布局文件activity_main.xml如下:

 1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     xmlns:tools="http://schemas.android.com/tools"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:paddingBottom="@dimen/activity_vertical_margin"
 6     android:paddingLeft="@dimen/activity_horizontal_margin"
 7     android:paddingRight="@dimen/activity_horizontal_margin"
 8     android:paddingTop="@dimen/activity_vertical_margin"
 9     tools:context="com.himi.parcelabledemo.MainActivity" >
10 
11     <Button
12         android:id="@+id/button"
13         android:layout_width="wrap_content"
14         android:layout_height="wrap_content"
15         android:layout_alignParentTop="true"
16         android:layout_centerHorizontal="true"
17         android:layout_marginTop="20dp"
18         android:text="Button" />
19 
20 </RelativeLayout>

  3)新建一个包:com.himi.parcelabledemo.bean,这里新建一个bean实体类User,如下:

 1 package com.himi.parcelabledemo.bean;
 2 
 3 import android.os.Parcel;
 4 import android.os.Parcelable;
 5 
 6 public class User implements Parcelable
 7 {
 8     private String name;
 9     private int age;
10     private String email;
11 
12     public User(String name, int age, String email) {
13         this.name = name;
14         this.age = age;
15         this.email = email;
16     }
17 
18     public User(Parcel source) {
19         name = source.readString();
20         age = source.readInt();
21         email = source.readString();
22     }
23 
24     public String getName() {
25         return name;
26     }
27 
28     @Override
29     public String toString() {
30         return "User [name=" + name + ", age=" + age + ", email=" + email + "]";
31     }
32 
33     public void setName(String name) {
34         this.name = name;
35     }
36 
37     public int getAge() {
38         return age;
39     }
40 
41     public void setAge(int age) {
42         this.age = age;
43     }
44 
45     public String getEmail() {
46         return email;
47     }
48 
49     public void setEmail(String email) {
50         this.email = email;
51     }
52 
53     @Override
54     public int describeContents() {
55         // TODO Auto-generated method stub
56         return 0;
57     }
58 
59     /**
60      * 序列化过程:必须按成员变量声明的顺序进行封装
61      */
62     @Override
63     public void writeToParcel(Parcel dest, int flags) {
64         // TODO Auto-generated method stub
65         dest.writeString(name);
66         dest.writeInt(age);
67         dest.writeString(email);
68         
69     }
70     
71     /**
72      * 反序列过程:必须实现Parcelable.Creator接口,并且对象名必须为CREATOR
73      * 读取Parcel里面数据时必须按照成员变量声明的顺序,Parcel数据来源上面writeToParcel方法,
74      * 读出来的数据供逻辑层使用.
75      */
76     public static final Parcelable.Creator<User> CREATOR = new Parcelable
77             .Creator<User>() {
78         
79         @Override
80         public User[] newArray(int size) {
81             // TODO Auto-generated method stub
82             return new User[size];
83         }
84         
85         @Override
86         public User createFromParcel(Parcel source) {
87             // TODO Auto-generated method stub
88             return new User(source);
89         }
90     };
91             
92 
93 }

  4)接下来,我们来到MainActivity,如下:

 1 package com.himi.parcelabledemo;
 2 
 3 import com.himi.parcelabledemo.bean.User;
 4 
 5 import android.app.Activity;
 6 import android.content.Intent;
 7 import android.os.Bundle;
 8 import android.view.View;
 9 import android.view.View.OnClickListener;
10 import android.widget.Button;
11 import android.widget.TextView;
12 
13 public class MainActivity extends Activity {
14 
15     private Button button;
16     private TextView textview;
17 
18     @Override
19     protected void onCreate(Bundle savedInstanceState) {
20         super.onCreate(savedInstanceState);
21         setContentView(R.layout.activity_main);
22 
23         button = (Button) findViewById(R.id.button);
24         textview = (TextView) findViewById(R.id.textView);
25 
26         button.setOnClickListener(new OnClickListener() {
27 
28             @Override
29             public void onClick(View v) {
30 
31                 Intent in = new Intent(MainActivity.this, SecondActivity.class);
32                 in.putExtra("user", new User("步惊云",23,"1102133@163.com" ));
33                 startActivity(in);
34             }
35         });
36     }
37 
38 }

上面的MainActivity通过Intent跳转到SecondActivity,其中SecondActivity,如下:

 1 package com.himi.parcelabledemo;
 2 
 3 import com.himi.parcelabledemo.bean.User;
 4 
 5 import android.app.Activity;
 6 import android.os.Bundle;
 7 import android.widget.TextView;
 8 
 9 public class SecondActivity extends Activity {
10     private TextView textview;
11     
12     @Override
13     protected void onCreate(Bundle savedInstanceState) {
14         // TODO Auto-generated method stub
15         super.onCreate(savedInstanceState);
16         setContentView(R.layout.second);
17         
18         textview = (TextView) findViewById(R.id.textView);
19         
20         User user = getIntent().getParcelableExtra("user");
21         textview.setText("user name:"+user.getName()+"
"
22                 +"user age:"+user.getAge()+"
"
23                 +"user email:"+user.getEmail());
24         
25     }
26 
27 }

与SecondActivity相对应的布局文件second.xml,如下:

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:orientation="vertical" 
 6     android:gravity="center">
 7 
 8     <TextView
 9         android:id="@+id/textView"
10         android:layout_width="wrap_content"
11         android:layout_height="wrap_content"
12         android:text="TextView" />
13 
14 </LinearLayout>

  5)不要忘了这里我们新建一个SecondActivity,需要在AndroidManifest.xml文件中注册一下

  6)布署程序到模拟器,如下:

原文地址:https://www.cnblogs.com/hebao0514/p/5446234.html