Android序列化-Serializable接口

场景,有一件礼物Gift(对象)我要把它送给远在国外的女票,怎么办?只能把这件礼物放在包裹里邮寄,女票收到之后在打开包裹得到这个礼物。那么我的理解Serializeable就是干这件事的。

Serializable是Java里面所提供的一个序列化接口,它只是一个空接口,内部并没有任何方法,是一个典型的标识接口(注1)。为我们提供了标准的序列化与反序列化操作。Serializable的使用非常简单,只要把想实现序列化的对象Gift 实现该接口,并添加下面一行代码就可以private static final long serialVersionUID=8712312312312321312L。其实serialVersoinUID也不是必须的,如果没有手动添加,那么也会自动生成一个。不过会对反序列化产生影响。下面我们看一个Gift实例:

public  class Gift implements Serializable {
    public static final long serialVersionUID = 8098080809810941L;
    public String name;
    public int size;
    public float price;

    public Gift(String name, int size, float price) {
        this.name = name;
        this.size = size;
        this.price = price;
    }

    @Override
    public String toString() {
        return "Gift{" +
                "name='" + name + '\'' +
                ", size=" + size +
                ", price=" + price +
                '}';
    }
}



通过Serializable实现序列化非常简单,实现完Serializable接口的Gift就是一个可以进行序列化与反序列化的对象。那么如何进行序列化和反序列化呢?也非常简单:

  try {
            //序列化过程
            Gift giftSrc = new Gift("lv", 20, 10000f);
            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(Environment.getExternalStorageDirectory().toString() + "/gift.txt"));
            out.writeObject(giftSrc);
            out.close();
            //反序列化过程
            ObjectInputStream in = new ObjectInputStream(new FileInputStream(Environment.getExternalStorageDirectory().toString() + "/gift.txt"));
            Gift giftSerial = (Gift) in.readObject();
            in.close();
            Log.e(TAG, "反序列化:" + giftSerial.toString());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

 运行结果:

可以看到,和我们之前序列化的Gift对象内容一致。

上述序列化的过程:把实现了Serializable接口的Gift通过流写入到本地文件。

反序列的过程:通过流把本地文件中的内容读取出来,构建一个Gift对象,不过这时候的Gift对象与原来的Gift虽然内容一致,但是其本质并不是同一个对象。


serialVersionUID的作用:

serialVersionUID被称为序列化版本号,鉴别反序列时生成的类对象是不是之前序列化的类对象。

我们之前说过不手动生成serialVersonUID也是可以实现序列化的,只是对反序列化有些影响。那么我们就来详细的说一下手动和自动两种情况。

1.隐式:不手动生成,系统会根据类的结构自动生成其Hash值。而我们在序列化的时候会把生成serialVersionUID也一起写进本地文件。反序列化时系统会把之前写进去的serialVersionUID和之前写进去的做对比,如果一致,那么可以反序列化,如果不一致,那么反序列化失败Cash! 所以系统自动生成这种方式对对象的一致性要求很高,不能随意增删改。例如删除一个成员变量,那么系统会自动计算当前类的hash值并把它赋值给serialVersionUID,那么当前类的serialVersionUID就和反序列话中的不一致,造成反序列化失败。

2.显式:比如1L或者根据类名,接口名,成员方法以及属性等生成一个64为的hash并赋值给serialVersionUID,其余的参数我们可以进行修改,这种方式可以很大程度上避免反序列化的失败。

代码示例:在上面的基础上我们使用自动生成,序列化之后我们更改一个成员变量。

    public static class Gift implements Serializable {
        //        public static final long serialVersionUID = 8098080809810941L;
        public String name;
        public int size;
        public float price;
        public String place;

        public Gift(String name, int size, float price, String place) {
            this.name = name;
            this.size = size;
            this.price = price;
            this.place = place;
        }

        @Override
        public String toString() {
            return "Gift{" +
                    "name='" + name + '\'' +
                    ", size=" + size +
                    ", price=" + price +
                    ", place='" + place + '\'' +
                    '}';
        }
    }
反序列化运行结果:

很明显由于我们修改了Gift的成员变量,导致系统自动重新计算生成了一个新的serialVersionUID,其与反序列化中我们之前写进去的不同,所以不认为是同一个类,反序列化失败。

Tips:静态成员不属于类所以不参与序列化,transient关键字同样。

注1:标识接口是没有任何方法和属性的接口。标识接口不对实现它的类有任何语义上的要求,它仅仅表明实现它的类属于一个特定的类型。
标接口在Java语言中有一些很著名的应用,比如java.io.Serializable和java.rmi.Remote等接口便是标识接口。标识接口,当一个类实现了一个标识接口之后就像是给自己打了个标签。
打个比喻,不是很恰当。就像是一个人穿了件名牌衣服(实现了标识接口),别人一看他穿的衣服(标识接口)就知道他的品味、身份(特性)。



            
阅读更多
个人分类: Android进阶
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭