Bootstrap

【命运只会眷顾努力的人】泛型与序列化

更新日期:2022/03/22 :第一版做成。
更新日期:2022/10/13 :更新泛型定义,更清晰易懂。
生命不息,奋斗不止!(送给也曾迷茫的你)

 


1. 泛型

Generics java 中 <> 表示使用泛型。泛型的本质是参数化类型(只能是引用类型),也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在接口方法的创建中,分别称为泛型类、泛型接口、泛型方法。泛型把类型明确的工作推迟到创建对象或调用方法的时候才去明确,好处是可以在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的。

  • 泛型中的标记符含义
    <? extends T> 通配符上限:表示该通配符所代表的类型是T类型的子类。
    <? super T> 通配符下限:表示该通配符所代表的类型是T类型的父类。
标记全拼含义
EElement集合中的元素
TTypejava 类型
KKey
VValue
NNumber数值类型
?-表示不确定的java类型
S-一个T不够用了,使用临近的字母表示任意类型
U-一个T不够用了,使用临近的字母表示任意类型
V-一个T不够用了,使用临近的字母表示任意类型
  • 为什么需要泛型
  1. 代码复用率更高,方法能接收更多类型的对象;
  2. 代码更加简洁不用强制转换;
  3. 程序更加健壮,只要编译时期没有警告,那么运行时期就不会出现 ClassCastException 异常;
  4. 可读性和稳定性,在编写集合的时候,就限定了类型。
  • 简单的泛型示例
    一个方法既可以对 list 进行排序,也可以对 map 进行排序。
*************************************************************************
public class GenericsTestController {
    public static void main(String[] args) {
        GenericsTestController gtc = new GenericsTestController();
        ArrayList<String> arrlist = new ArrayList<>();
        arrlist.add("IT");
        arrlist.add("God");
        arrlist.add("Road");
        gtc.GerericsTest(arrlist);
        Map<Integer, String> map = new HashMap<Integer, String>();
        map.put(2020, "猪年");
        map.put(2021, "鼠年");
        map.put(2022, "牛年");
        gtc.GerericsTest(map.entrySet());
    }
.........................................................................
    <E> void GerericsTest(Collection<? extends E> c) {
        @SuppressWarnings("unchecked")
        E[] array = (E[]) c.toArray();
        List<E> list = Arrays.asList(array);
        Collections.reverse(list);
        for (E e : list) {
            System.out.println(e);
        }
    }
}Console--------------------------------------------------------------
	Road
	God
	IT
	2022=牛年
	2021=鼠年
	2020=猪年
*************************************************************************

2. 序列化

Serialization 序列化是将对象的状态信息转换为可以存储或传输的过程(二进制 byte [ ])。目的是为了对象可以跨平台存储,和进行网络传输。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取反序列化对象的状态,重新创建该对象。简单的说,序列化就是把对象转化为可传输的字节序列,反序列化就是把字节序列还原为对象

序列化
反序列化
对象状态信息
二进制字节数组

3. 序列化版本号

serialVersionUID 字​面​意​思​上​是​序​列​化​的​版​本​号​,JAVA 序列化的机制就是通过判断类的 serialVersionUID 来验证版本是否一致的,在进行反序列化时,JVM 会把传来的字节流中的 serialVersionUID 于本地相应实体类的 serialVersionUID 进行比较。如果相同说明是一致的,可以进行反序列化,否则会出现反序列化版本一致的异常,即是InvalidCastException

凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量。如果没有加入serialVersionUID,就会出现以下警告提示:

在这里插入图片描述

  • serialVersionUID 显示的生成示例
    显示声明 serialVersionUID 可以避免对象不一致
	// 默认版本号1L
    private static final long serialVersionUID = 1L;
    // 根据包名,类名,继承关系,非私有的方法和属性,以及参数,返回值等诸多因子计算得出的,极度复杂生成的一个64位的哈希字段。
    private static final long serialVersionUID = -6691497298821111135L;
  • 如果我们不希望通过编译来强制划分软件版本,即实现序列化接口的实体能够兼容先前版本,就需要显示的定义一个 serialVersionUID,类型为 long 的变量。不修改这个变量值的序列化实体,都可以相互进行序列化和反序列化。

4. Java 中实现序列化与反序列化

Java 只有实现了 SerializableExternalizable 接口的类的对象才能被序列化。Externalizable 接口继承自 Serializable 接口,实现 Externalizable 接口的类完全由自身来控制序列化的行为,而仅实现 Serializable 接口的类采用默认的序列化方式 。
java.io.ObjectOutputStream 代表对象输出流,writeObject(Object obj) 方法可对参数指定的 obj 对象进行序列化,把得到的字节序列写到一个目标输出流中。
java.io.ObjectInputStream 代表对象输入流,readObject() 方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。

 

4.1 Serializable 对象序列化

1.创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;
2.通过对象输出流的 writeObject() 方法写对象。

*************************************************************************
public class TestSerializable {
    public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream("E:\\HashApple.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        HashApple hashApple = new HashApple("red", "circle", 200.86);
        oos.writeObject(hashApple);
        oos.flush();
        oos.close();
    }
}
*************************************************************************
class HashApple implements Serializable {
    private static final long serialVersionUID = 1L;
    private final String color;
    private final String shape;
    private final Double gram;
.........................................................................
    public HashApple(String color, String shape, Double gram) {
        this.color = color;
        this.shape = shape;
        this.gram = gram;
    }
.........................................................................
    // 重写toString方法
    @Override
    public String toString() {
        return color + "-" + shape + "-" + gram.toString();
    }
}
*************************************************************************
  • E:\HashApple.txt

在这里插入图片描述
 

4.2 Serializable 对象反序列化

1.创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流;
2.通过对象输入流的 readObject() 方法读取对象。

*************************************************************************
public class TestSerializable {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream("E:\\HashApple.txt");
        ObjectInputStream ois = new ObjectInputStream(fis);
        HashApple hashApple = (HashApple) ois.readObject();
        System.out.println("HashApple=" + hashApple.toString());
    }
}
*************************************************************************
class HashApple implements Serializable {
    private static final long serialVersionUID = 1L;
    private final String color;
    private final String shape;
    private final Double gram;
.........................................................................
    public HashApple(String color, String shape, Double gram) {
        this.color = color;
        this.shape = shape;
        this.gram = gram;
    }
.........................................................................
    // 重写toString方法
    @Override
    public String toString() {
        return color + "-" + shape + "-" + gram.toString();
    }
}Console--------------------------------------------------------------
HashApple=red-circle-200.86
*************************************************************************

 

4.3 Externalizable 对象序列化

1.实现 Externalizable 接口的类必须要提供一个无参构造器,且它的访问权限为public,因为使用 Externalizable 接口进行序列化时,读取对象会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中;
2.必须重写 writeExternalreadExternal 方法,一个用于序列化,一个用于反序列化,这种方式是将属性序列化,注意这种方式 transient 修饰词将失去作用。

*************************************************************************
public class TestSerializable {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        FileOutputStream fos = new FileOutputStream("E:\\ExternalHashApple.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        ExternalHashApple externalHashApple = new ExternalHashApple("red", "circle", 200.86);
        oos.writeObject(externalHashApple);
        oos.flush();
        oos.close();
    }
}
*************************************************************************
class ExternalHashApple implements Externalizable {
    private String color;
    private String shape;
    private Double gram;

    public ExternalHashApple() {
        System.out.println("HashApple的无参构造器");
    }
.........................................................................
    public ExternalHashApple(String color, String shape, Double gram) {
        this.color = color;
        this.shape = shape;
        this.gram = gram;
    }
.........................................................................
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(shape);
        out.writeObject(color);
        // out.writeObject(gram); 不序列化重量
    }
.........................................................................
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        shape = (String) in.readObject();
        color = (String) in.readObject();
    }
.........................................................................
    // 重写toString方法
    @Override
    public String toString() {
        return color + "-" + shape;
    }
}
*************************************************************************
  • E:\ExternalHashApple.txt

在这里插入图片描述
 

4.4 Externalizable 对象反序列化

1.创建一个对象输入流,它可以包装一个其他类型的源输入流,如文件输入流;
2.通过对象输入流的 readObject() 方法读取对象。

*************************************************************************
public class TestSerializable {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream("E:\\ExternalHashApple.txt");
        ObjectInputStream ois = new ObjectInputStream(fis);
        ExternalHashApple externalHashApple = (ExternalHashApple) ois.readObject();
        System.out.println("ExternalHashApple=" + externalHashApple.toString());
    }
}
*************************************************************************
class ExternalHashApple implements Externalizable {
    private String color;
    private String shape;
    private Double gram;

    public ExternalHashApple() {
        System.out.println("HashApple的无参构造器");
    }
.........................................................................
    public ExternalHashApple(String color, String shape, Double gram) {
        this.color = color;
        this.shape = shape;
        this.gram = gram;
    }
.........................................................................
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(shape);
        out.writeObject(color);
        // out.writeObject(gram); 不序列化重量
    }
.........................................................................
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        shape = (String) in.readObject();
        color = (String) in.readObject();
    }
.........................................................................
    // 重写toString方法
    @Override
    public String toString() {
        return color + "-" + shape;
    }
}Console--------------------------------------------------------------
HashApple的无参构造器
ExternalHashApple=red-circle
*************************************************************************

5. 序列化的技术选择

  • 协议是否支持跨平台
    如果你们公司有好多种语言进行混合开发,那么就肯定不适合用有语言局限性的序列化协议,要不然你用 JDK 序列化出来的格式,其他语言并没法支持。
  • 序列化的速度
    如果序列化的频率非常高,那么选择序列化速度快的协议会为你的系统性能提升不少。
  • 序列化出来的大小
    如果频繁的在网络中传输的数据那就需要数据越小越好,小的数据传输快,也不占带宽,也能整体提升系统的性能。
序列化技术优点缺点
JDK二进制数组,传输快不支持跨语言,文件大,性能一般
JSON前后端交互,语言中立,能配合 JavaScript 使用无版本检查,缺乏命名空间,导致数据混合
XML支持几乎所有语言冗余标签太多,性能低
Hessian跨语言,自描述文件,不依赖接口定义,文件小对于复杂对象,可能会导致数据被覆盖
Thrift跨语言,可快速实现RPC开发环境,编译麻烦
Protostuff跨语言,可自定义数据结构,二进制数组,传输快对象冗余,字段很多
Avro支持少量语言,性能高,可快速实现RPC只支持Avro自己的序列化格式
MsgPack跨语言,兼容json,高性能对复杂的数据类型(List、Map)支持的不够

【每日一面】

Java 中实现序列化的两种方式 Serializable 接口和 Externalizable 接口怎么选择

Serializable:一个对象想要被序列化,那么它的类就要实现此接口,如果不想序列化某些属性,可以加上 transient 修饰(Transient 关键字只能用于修饰 Field,不可修饰 Java 程序中的其他成分)。
Externalizable:是 Serializable 接口的子类,不希望序列化所有属性,且不需要的属性比较多时,可以使用这个接口,这个接口的 writeExternal() 和 readExternal() 方法可以指定序列化哪些属性。
序列化的属性多时选择 Serializable,仅序列化部分属性选择 Externalizable

;