java序列化、反序列化

Java 序列化是 JDK 1.1 时引入的一组开创性的特性,用于将 Java 对象转换为字节数组,便于存储或传输。此后,仍然可以将字节数组转换回 Java 对象原有的状态。

序列化的思想是“冻结”对象状态,然后写到磁盘或者在网络中传输;反序列化的思想是“解冻”对象状态,重新获得可用的 Java 对象。

在java中,实现Serializbale 接口的对象,都可以序列化、反序列化。Serializbale的定义:

public interface Serializable {
}

1、Serializbale就一个空的接口,竟然能够保证实现了它的“类的对象”被序列化和反序列化?

我们先看一个例子:

1)Person类:

public class Person {
	private Long id;
	private String name;
    //set get 方法

}

 2)序列化、反序列:

private static void test1() {
		Person p = new Person();
		p.setId(1L);
		p.setName("test");
		System.out.println(p);
		
		// 把对象写到文件中
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D://person"));){
            oos.writeObject(p);
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        // 从文件中读出对象
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("D://person")));){
            Person p1 = (Person) ois.readObject();
            System.out.println(p1);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
	}

由于Person类没有实现Serializbale接口,会报如下错误:

java.io.NotSerializableException: serialize.Person
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at serialize.Test.test1(Test.java:46)
	at serialize.Test.main(Test.java:13)
java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: serialize.Person
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1575)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
	at serialize.Test.test1(Test.java:54)
	at serialize.Test.main(Test.java:13)
Caused by: java.io.NotSerializableException: serialize.Person
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at serialize.Test.test1(Test.java:46)
	... 1 more

顺着报错信息,我们来看一下 ObjectOutputStream 的 writeObject0() 方法。其部分源码如下:

if (obj instanceof String) {
    writeString((String) obj, unshared);
} else if (cl.isArray()) {
    writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
    writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
    writeOrdinaryObject(obj, desc, unshared);
} else {
    if (extendedDebugInfo) {
        throw new NotSerializableException(
            cl.getName() + "\n" + debugInfoStack.toString());
    } else {
        throw new NotSerializableException(cl.getName());
    }
}

也就是说,ObjectOutputStream 在序列化的时候,会判断被序列化的对象是哪一种类型,字符串?数组?枚举?还是 Serializable,如果全都不是的话,抛出 NotSerializableException。

如果Person类实现了Serializbale接口,那么就可以顺利序列化、反序列化操作了。如此可见:

Serializable 接口之所以定义为空,是因为它只起到了一个标识的作用,告诉程序实现了它的对象是可以被序列化的,但真正序列化和反序列化的操作并不需要它来完成。

常见的这类标识接口还有:

  • java.lang.Cloneable:表明Object.clone()方法可以合法地对该类实例进行按字段复
  • java.util.RandomAccess:用来表明其支持快速(通常是固定时间)随机访问
  • java.rmi.Remote:Remote 接口用于标识其方法可以从非本地虚拟机上调用的接口

:对于嵌套的类,外层类实现了Serializbale接口,内层类如果没有实现,在对外层类进行序列化时会报错。

2、关于serialVersionUID :

当一个类实现了Serializable 接口后,就会提醒该类最好产生一个序列化 ID,就像下面这样:

 serialVersionUID 被称为序列化 ID,它是决定 Java 对象能否反序列化成功的重要因子。在反序列化时,Java 虚拟机会把字节流中的 serialVersionUID 与被序列化类中的 serialVersionUID 进行比较,如果相同则可以进行反序列化,否则就会抛出序列化版本不一致的异常

从上面提示可以看到,serialVersionUID 的生成有三种方式:

1)添加一个默认版本的序列化 ID:

private static final long serialVersionUID = 1L;

如果没有特殊需求,采用默认的序列化 ID(1L)就可以,这样可以确保代码一致时反序列化成功。

2)添加一个随机生成的不重复的序列化 ID:

private static final long serialVersionUID = -2095916884810199532L;

 3)添加 @SuppressWarnings 注解:

@SuppressWarnings("serial")

使用 @SuppressWarnings("serial") 注解时,该注解会为被序列化类自动生成一个随机的序列化 ID

3、static 和 transient 修饰的字段是不会被序列化的

我们看一个例子:

1)Person类:

public class Person implements Serializable {
	private static final long serialVersionUID = 1L;
	private Long id;
	private String name;
	
	public static String preName;
	private transient String lasName;

 2)序列化、反序列化类:

private static void test1() {
		Person p = new Person();
		p.setId(1L);
		p.setName("test");
		p.setLasName("李斯");
		Person.preName = "张三";
		System.out.println(p);
		
		// 把对象写到文件中
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D://person"));){
            oos.writeObject(p);
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        Person.preName = "张三2";
        // 从文件中读出对象
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("D://person")));){
            Person p1 = (Person) ois.readObject();
            System.out.println(p1);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
	}

输出:

Person [id=1, name=test,preName=张三, lasName=李斯]
Person [id=1, name=test,preName=张三2, lasName=null]

  • static属性:因为序列化保存的是对象的状态,而static 修饰的字段属于类的状态,因此序列化并不保存 static 修饰的属性是合理的,反序列化后其属性值始是终保存在方法区中值
  • transient属性:它可以阻止属性被序列化到文件中,在被反序列化后,transient属性的值被设为初始值,比如 int 型的初始值为 0,对象型的初始值为 null。

深究源码,可以在 ObjectStreamClass 中发现下面这样的代码:

private static ObjectStreamField[] getDefaultSerialFields(Class<?> cl) {
    Field[] clFields = cl.getDeclaredFields();
    ArrayList<ObjectStreamField> list = new ArrayList<>();
    int mask = Modifier.STATIC | Modifier.TRANSIENT;

    int size = list.size();
    return (size == 0) ? NO_FIELDS :
        list.toArray(new ObjectStreamField[size]);
}

4、Externalizable接口:

除了 Serializable 之外,Java 还提供了一个序列化接口 Externalizable。实现Externalizable接口进行序列化、反序列化时要注意一下两点不同:

1)实现 Externalizable 接口必须重写writeExternal() 和 readExternal()方法:

public class Student implements Externalizable {
	private Long id;
	private String name;
	
	@Override
	public void writeExternal(ObjectOutput out) throws IOException {
		out.writeLong(id);
		out.writeObject(name);
	}
	@Override
	public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException {
		id = in.readLong();
		name = (String)in.readObject();
	}

 2)被序列化的类必须要有默认构造方法:

使用 Externalizable 进行反序列化的时候,会调用被序列化类的无参构造方法去创建一个新的对象,然后再将被保存对象的字段值复制过去。如果类没有默认构造方法(无参),会抛出以下异常:  

java.io.InvalidClassException: serialize.Student; no valid constructor
	at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:157)
	at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:862)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2041)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1571)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
	at serialize.Test.test3(Test.java:30)
	at serialize.Test.main(Test.java:13)

参考:

https://mp.weixin.qq.com/s/mJOk8j505M2MfIS4fvd3ng?article_exclude_marked=d1e7dd5bf8621aaccee119c0a8bdcf29

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赶路人儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值