更新日期:2022/03/22 :第一版做成。
更新日期:2022/10/13 :更新泛型定义,更清晰易懂。
生命不息,奋斗不止!(送给也曾迷茫的你)
目録
1. 泛型
Generics java 中
<>
表示使用泛型。泛型的本质是参数化类型(只能是引用类型),也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。泛型把类型明确的工作推迟到创建对象或调用方法的时候才去明确,好处是可以在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的。
- 泛型中的标记符含义
<? extends T>
通配符上限:表示该通配符所代表的类型是T类型的子类。
<? super T>
通配符下限:表示该通配符所代表的类型是T类型的父类。
标记 | 全拼 | 含义 |
---|---|---|
E | Element | 集合中的元素 |
T | Type | java 类型 |
K | Key | 键 |
V | Value | 值 |
N | Number | 数值类型 |
? | - | 表示不确定的java类型 |
S | - | 一个T不够用了,使用临近的字母表示任意类型 |
U | - | 一个T不够用了,使用临近的字母表示任意类型 |
V | - | 一个T不够用了,使用临近的字母表示任意类型 |
- 为什么需要泛型
- 代码复用率更高,方法能接收更多类型的对象;
- 代码更加简洁不用强制转换;
- 程序更加健壮,只要编译时期没有警告,那么运行时期就不会出现
ClassCastException
异常; - 可读性和稳定性,在编写集合的时候,就限定了类型。
- 简单的泛型示例
一个方法既可以对 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 只有实现了 Serializable 或 Externalizable 接口的类的对象才能被序列化。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.必须重写 writeExternal
和 readExternal
方法,一个用于序列化,一个用于反序列化,这种方式是将属性序列化,注意这种方式 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