Java序列化和反序列化、反射学习笔记

1.概述

序列化与反序列化:

Java序列化是指把Java对象转换为字节序列的过程;
Java反序列化是指把字节序列恢复为Java对象的过程。
序列化分为两大部分:序列化和反序列化。序列化是这个过程的第一部分,将数据分解成字节流,以便存储在文件中或在网络上传输。
反序列化就是打开字节流并重构对象。对象序列化不仅要将基本数据类型转换成字节表示,有时还要恢复数据。恢复数据要求有恢复数据的对象实例。

为什么需要序列化与反序列化?

我们知道,当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本、图片、音频、视频等, 而这些数据都会以二进制序列的形式在网络上传送。那么当两个Java进程进行通信时,能否实现进程间的对象传送呢?答案是可以的。如何做到呢?这就需要Java序列化与反序列化了。换句话说,一方面,发送方需要把这个Java对象转换为字节序列,然后在网络上传送;另一方面,接收方需要从字节序列中恢复出Java对象。

当我们明晰了为什么需要Java序列化和反序列化后,我们很自然地会想Java序列化的好处。其好处一是实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上(通常存放在文件里),二是,利用序列化实现远程通信,即在网络上传送对象的字节序列。

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

几种常见的序列化与反序列化协议:

XML & SOAP:

XML 是一种常用的序列化和反序列化协议,具有跨机器,跨语言等优点,SOAP(Simple Object Access protocol) 是一种被广泛应用的,基于 XML 为序列化和反序列化协议的结构化消息传递协议。

JSON(Javascript Object Notation)

Protobuf

2.序列化实现:

只有实现了Serializable或者Externalizable接口的类的对象才能被序列化为字节序列。(不是则会抛出异常) 。

Serializable 接口是Java提供的序列化接口,他是一个空接口:

public interface Serializable {
}

Serializable 用来标识当前类可以被ObjectOutputStream序列化,以及被ObjectInputStream反序列化。

Serializable 接口的特点

Serializable是Java提供的序列化接口,是一个空接口,为对象提供标准的序列化与反序列化操作。使用Serializable实现序列化过程相当简单,只需要在类声明的时候指定一个标识,便可以自动的实现默认的序列化过程。

private static final long serialVersionUID = 1L;

上面已经说明让对象实现序列化,只需要让当前类实现Serializable接口,并且声明一个serialVersionUID就可以了,非常的简单方便。实际上serialVersionUID都不是必须的,没有它同样可以正常的实现序列化操作。
User类就是一个实现了Serialzable的类,它是可以被序列化和反序列化的。

public class User implements Serializable {
 
    private static final long serialVersionUID = 1L;
 
    private String userId;
    private String userName;
 
}

通过Serializable实现对象的序列化过程非常的简单,无需任何操作,系统就为我们自动实现了。如何进行对象的序列化与反序列化操作也是非常的简单,只需要通ObjectOutputStream,ObjectInputStream进行操作就可以了。

 //序列化过程
    public void toSerial() {
        try {
            User user = new User("id", "user");
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("user.txt"));
            objectOutputStream.writeObject(user);
            objectOutputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
    //反序列化过程
    public void fromSerial(){
        try {
            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("user.txt"));
            User user = (User) objectInputStream.readObject();
            objectInputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

这个serialVersionUID是用来辅助对象的序列化与反序列化的,原则上序列化后的数据当中serialVersionUID与当前类当中的serialVersionUID一致,那么该对象才能被反序列化成功。这个serialVersionUID的详细的工作机制是:在序列化的时候系统将serialVersionUID写入到序列化的文件中去,当反序列化的时候系统会先去检测文件中的serialVersionUID是否跟当前的文件的serialVersionUID是否一致,如果一直则反序列化成功,否则就说明当前类跟序列化后的类发生了变化,比如是成员变量的数量或者是类型发生了变化,那么在反序列化时就会发生crash,并且回报出错误:
image.png

注意:

  • 一个实现 Serializable 接口的子类也是可以被序列化的。
  • 静态成员变量是不能被序列化
  • transient 标识的对象成员变量不参与序列化

Externalizable 接口:

继承自 Serializable接口,Externalizable接口定义了两个抽象方法:writeExternal()与readExternal(),通过这些方法指定序列化哪些属性不序列化哪些属性。注意:实现Externalizable接口的类可以不设置serialVersionUID常量,但必须要求序列化前后的两个类完全相同,为了编程更显灵活,推荐设置serialVersionUID常量。

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
 
public class UserInfo implements Externalizable {
 
	private int age;
	private String name;
 
	public int getAge() {
		return age;
	}
 
	public void setAge(int age) {
		this.age = age;
	}
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		this.name = name;
	}
 
	@Override
	public void writeExternal(ObjectOutput out) throws IOException {
		out.writeObject(name);
		out.writeInt(age);
	}
 
	@Override
	public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
		name = (String) in.readObject();
		age = in.readInt();
	}
 
	@Override
	public String toString() {
		return "name='" + name + '\'' + ", age=" + age;
	}
}
import java.io.*;
 
public class Test {
	
	/**
	 * 序列化
	 * 
	 * @author GaoHuanjie
	 */
	public static void serialize(){
		UserInfo userInfo = new UserInfo();
		userInfo.setAge(23);
		userInfo.setName("Tom");
		System.out.println(userInfo);
		ObjectOutput objectOutput = null;
		try {
			objectOutput = new ObjectOutputStream(new FileOutputStream("D:\\user_info.ser"));
			objectOutput.writeObject(userInfo);
			objectOutput.flush();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if (objectOutput!=null) {
				try {
					objectOutput.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
 
	/**
	 * 反序列化
	 * 
	 * @author GaoHuanjie
	 */
	public static void deserialize(){
		ObjectInput objectInput = null;
		try {
			objectInput = new ObjectInputStream(new FileInputStream("D:\\user_info.ser"));
			UserInfo userInfo = (UserInfo) objectInput.readObject();
			System.out.println(userInfo);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (objectInput!=null) {
				try {
					objectInput.close();
				} catch (IOException e) {
					e.printStackTrace();
				} 
			}
		}
	}
 
	public static void main(String[] args) {
		serialize();//序列化
		deserialize();//反序列化
	}
}

Serializable接口与Externalizable接口区别:

  • Serializable反序列化时不会调用默认构造方法,而Externalizable反序列化时会调用默认构造器方法;
  • Serializable反序列化时构造方法可以是任意访问权限的控制符,而Externalizable反序列化时只能是public;
  • Serializable序列化时,static或transient修饰的属性不会被序列化,而Externalizable序列化时只能通过writeExternal()和readExternal()方法指定序列化哪些属性不序列化哪些属性,不能使用transient修饰符

序列化ID:

我们在进行序列化时,加了一个serialVersionUID字段,这便是序列化ID。

 private static final long serialVersionUID = 1L;

它决定着是否能够成功反序列化!java的序列化机制是通过判断运行时类的serialVersionUID来验证版本一致性的,在进行反序列化时,JVM会把传进来的字节流中的serialVersionUID与本地实体类中的serialVersionUID进行比较,如果相同则认为是一致的,便可以进行反序列化,否则就会报序列化版本不一致的异常。
序列化ID是为了保证成功进行反序列化。

如何生成serialVersionUID?

  1. 使用 AS plugin 插件就可以生成
  2. 在JDK中,可以利用 JDK 的 bin 目录下的 serialver 工具产生这个serialVersionUID,对于 Student.class,执行命令:serialver com.example.seriable.Student
➜  classes git:(master)/Library/Java/JavaVirtualMachines/jdk1.8.0_111.jdk/Contents/Home/bin/serialver com.example.seriable.Student 
com.example.seriable.Student:    private static final long serialVersionUID = -6840182814363029482L;//这个就是工具生成的 SerialVersionUID 值了

serialVersionUID 发生改变有三种情况:

  1. 手动去修改导致当前的 serialVersionUID 与序列化前的不一样。
  2. 我们根本就没有手动去写这个 serialVersionUID 常量,那么 JVM 内部会根据类结构去计算得到这个 serialVersionUID 值,在类结构发生改变时(属性增加,删除或者类型修改了)这种也是会导致 serialVersionUID 发生变化。
  3. 假如类结构没有发生改变,并且没有定义 serialVersionUID ,但是反序列和序列化操作的虚拟机不一样也可能导致计算出来的 serialVersionUID 不一样。

默认序列化ID:

当我们一个实体类中没有显式的定义一个名为“serialVersionUID”、类型为long的变量时,Java序列化机制会根据编译时的class自动生成一个serialVersionUID作为序列化版本比较,这种情况下,只有同一次编译生成的class才会生成相同的serialVersionUID。譬如,当我们编写一个类时,随着时间的推移,我们因为需求改动,需要在本地类中添加其他的字段,这个时候再反序列化时便会出现serialVersionUID不一致,导致反序列化失败。

3.Java 反射

1.反射机制有什么用?

通过java语言中的反射机制可以操作字节码文件(可以读和修改字节码文件。)
通过反射机制可以操作代码片段。(class文件。)

2.反射机制所在包:

java.lang.reflect.*;

3.反射机制有关的类:

含义
java.lang.Class代表整个字节码。代表一个类型,代表整个类。
java.lang.reflect.Method代表字节码中的方法字节码。代表类中的方法。
java.lang.reflect.Constructor代表字节码中的构造方法字节码。代表类中的构造方法。
java.lang.reflect.Field代表字节码中的属性字节码。代表类中的成员变量(静态变量+实例变量)。

必须先获得Class才能获取Method、Constructor、Field

java.lang.Class:
public class User{
	// Field
	int no;
	
	// Constructor
	public User(){
	
	}
	public User(int no){
		this.no = no;
	}
	
	// Method
	public void setNo(int no){
		this.no = no;
	}
	public int getNo(){
		return no;
	}
}

4.获取Class的三种方式:

要操作一个类的字节码,需要首先获取到这个类的字节码,怎么获取java.lang.Class实例?

方式备注
Class.forName(“完整类名带包名”)静态方法
对象.getClass()
任何类型.class

5.通过反射实例化对象

对象.newInstance()

newInstance()方法内部实际上调用了无参数构造方法,必须保证无参构造存在才可以。否则会抛出java.lang.InstantiationException异常。

class ReflectTest02{
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        // 下面这段代码是以反射机制的方式创建对象。

        // 通过反射机制,获取Class,通过Class来实例化对象
        Class c = Class.forName("javase.reflectBean.User");
        // newInstance() 这个方法会调用User这个类的无参数构造方法,完成对象的创建。
        // 重点是:newInstance()调用的是无参构造,必须保证无参构造是存在的!
        Object obj = c.newInstance();
        System.out.println(obj);
    }
}

6.Class.forName导致类加载

如果你只是希望一个类的静态代码块执行,其它代码一律不执行,可以使用:

Class.forName("完整类名");

这个方法的执行会导致类加载,类加载时,静态代码块执行。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小秋LY

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

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

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

打赏作者

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

抵扣说明:

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

余额充值