一、泛型
1. 泛型概念
①Java 泛型(Generics) 是 JDK 5 中引入的一个新特性。
③定义类或者接口 时可以使用泛型,通过继承或者实现,减少了冗余代码,提高了代码的复用性。
④Java 不能创建具体类型的泛型数组
List[] li2 = new ArrayList[];
- 类型形参: 定义参数时不指定具体的类型,使用泛型通配符
- 类型实参: 在调用时传入具体的类型(类型实参)
2. 特性
泛型只在编译阶段有效。javac 编译器在把java文件编译成计算机识别的二进制字节码文件时, 会首先进行类型检查,检查通过后进行类型擦除。JVM并不知道泛型的存在,因为泛型在 编译阶段就已经被处理成普通的类和方法;
3. 泛型的作用
- 类型安全
编译时的强类型检查,对集合等容器限制存储元素的类型,编译时检查存储的元素是否 是容器允许存储的类型。把运行时期的问题提前到了编译期间。
- 消除强制类型转换
没有泛型时可以存任意类型,取值时就需要强制类型转换。加了泛型,统一了容器中的元素类型,消除代码中强转,避免了强转时可能会出行的类型转换异常
- 更好的代码复用性:
在框架设计时,类可以使用泛型类,通过继承或者实现,实现了所有的公共方法或者定 义接口公共响应类时使用泛型类,减少了冗余代码,提高了代码的复用性。
- 潜在的性能收益
泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,Java系统开发人员会指定这些强制类型转换)插入生成的字节码中。但是更多类 型信息可用于编译器这一事实,为未来版本的JVM的优化带来可能。
4. 泛型使用方式
泛型一般有三种使用方式:泛型类、泛型接口、泛型方法。
- 泛型类
//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型 //在实例化泛型类时,必须指定T的具体类型 public class Generic<T>{ private T key; public Generic(T key) { this.key = key; } public T getKey(){ return key; } }
如何实例化泛型类:
Generic<Integer> genericInteger = new Generic<Integer>(123456);
- 泛型接口
public interface Generator<T> { public T method(); }
实现泛型接口,不指定类型:
class GeneratorImpl<T> implements Generator<T>{ @Override public T method() { return null; } }
实现泛型接口,指定类型:
class GeneratorImpl implements Generator<String> { @Override public String method() { return "hello"; } } ------ 著作权归JavaGuide(javaguide.cn)所有 基于MIT协议 原文链接:https://javaguide.cn/java/basis/java-basic-questions-03.html
- 泛型方法
public static < E > void printArray( E[] inputArray ) { for ( E element : inputArray ){ System.out.printf( "%s ", element ); } System.out.println(); }
使用:
// 创建不同类型数组:Integer, Double 和 Character Integer[] intArray = { 1, 2, 3 }; String[] stringArray = { "Hello", "World" }; printArray( intArray ); printArray( stringArray );
注意:
public static < E > void printArray( E[] inputArray )
一般被称为静态泛型方法;在 java 中泛型只是一个占位符,必须在传递类型后才能使用。类在实例化时才能真正的传递类型参数,由于静态方法的加载先于类的实例化,也就是说类中的泛型还没有传递真正的类型参数,静态的方法的加载就已经完成了,所以静态泛型方法是没有办法使用类上声明的泛型的。只能使用自己声明的<E>
二、序列化和反序列化
1. 序列化和反序列化简介
- 序列化:将数据结构或对象转换成可以存储或传输的形式,通常是二进制字节流,也可以是 JSON, XML 等文本格式
- 反序列化:将在序列化过程中所生成的数据转换为原始数据结构或者对象的过程
- 对于 Java 这种面向对象编程语言来说,我们序列化的都是对象(Object)也就是实例化后的类(Class)
下面是序列化和反序列化常见应用场景:
- 对象在进行网络传输(比如远程方法调用 RPC 的时候)之前需要先被序列化,接收到序列化的对象之后需要再进行反序列化;
- 将对象存储到文件之前需要进行序列化,将对象从文件中读取出来需要进行反序列化;
- 将对象存储到数据库(如 Redis)之前需要用到序列化,将对象从缓存数据库中读取出来需要反序列化;
- 将对象存储到内存之前需要进行序列化,从内存中读取出来之后需要进行反序列化。
2. 常见序列化协议
JDK 自带的序列化方式一般不会用 ,因为序列化效率低并且存在安全问题。比较常用的序列化协议有 Hessian、Kryo、Protobuf、ProtoStuff,这些都是基于二进制的序列化协议。
像 JSON 和 XML 这种属于文本类序列化方式。虽然可读性比较好,但是性能较差,一般不会选择。
3. 为什么不推荐使用 JDK 自带的序列化?
我们很少或者说几乎不会直接使用 JDK 自带的序列化方式,主要原因有下面这些原因:
- 不支持跨语言调用 : 如果调用的是其他语言开发的服务的时候就不支持了。
- 性能差:相比于其他序列化框架性能更低,主要原因是序列化之后的字节数组体积较大,导致传输成本加大。
- 存在安全问题:序列化和反序列化本身并不存在问题。但当输入的反序列化的数据可被用户控制,那么攻击者即可通过构造恶意输入,让反序列化产生非预期的对象,在此过程中执行构造的任意代码。相关阅读:应用安全:JAVA 反序列化漏洞之殇 。