Java系列(五)| 序列化、泛型、注解、反射
序列化
什么是序列化?什么是反序列化?
什么是序列化,序列化就是把 Java 对象转为二进制流,方便存储和传输。
所以反序列化就是把二进制流恢复成对象。
类比我们生活中一些大件物品的运输,运输的时候把它拆了打包,用的时候再拆包组装。
Serializable
接口有什么用?
这个接口只是一个标记,没有具体的作用,但是如果不实现这个接口,在有些序列化场景会报错,所以一般建议,创建的 JavaBean
类都实现 Serializable
。
serialVersionUID
又有什么用?
serialVersionUID
就是起验证作用。
private static final long serialVersionUID = 1L;
我们经常会看到这样的代码,这个 ID
其实就是用来验证序列化的对象和反序列化对应的对象 ID 是否一致。
这个 ID
的数字其实不重要,无论是 1L 还是 IDE
自动生成的,只要序列化时候对象的 serialVersionUID
和反序列化时候对象的 serialVersionUID
一致的话就行。
如果没有显示指定 serialVersionUID
,则编译器会根据类的相关信息自动生成一个,可以认为是一个指纹。
所以如果你没有定义一个 serialVersionUID
, 结果序列化一个对象之后,在反序列化之前把对象的类的结构改了,比如增加了一个成员变量,则此时的反序列化会失败。
因为类的结构变了,所以 serialVersionUID
就不一致。
Java 序列化不包含静态变量?
序列化的时候是不包含静态变量的。
如果有些变量不想序列化,怎么办?
对于不想进行序列化的变量,使用 transient
关键字修饰。
transient
关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient
修饰的变量值不会被持久化和恢复。transient
只能修饰变量,不能修饰类和方法。
说说有几种序列化方式?
Java 序列化方式有很多,常见的有三种:
Java
对象流列化 :Java
原生序列化方法即通过 Java 原生流 (InputStream
和OutputStream
之间的转化) 的方式进行转化,一般是对象输出流ObjectOutputStream
和对象输入流ObjectI叩utStream
。Json
序列化:这个可能是我们最常用的序列化方式,Json
序列化的选择很多,一般会使用jackson
包,通过ObjectMapper
类来进行一些操作,比如将对象转化为byte
数组或者将json
串转化为对象。ProtoBuff
序列化:ProtocolBuffer
是一种轻便高效的结构化数据存储格式,ProtoBuff
序列化对象可以很大程度上将其压缩,可以大大减少数据传输大小,提高系统性能。
泛型
Java 泛型了解么?什么是类型擦除?介绍一下常用的通配符?
什么是泛型?
Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
List<Integer> list = new ArrayList<>();
list.add(12);
//这里直接添加会报错
list.add("a");
Class<? extends List> clazz = list.getClass();
Method add = clazz.getDeclaredMethod("add", Object.class);
//但是通过反射添加,是可以的
add.invoke(list, "kl");
System.out.println(list);
泛型一般有三种使用方式: 泛型类、泛型接口、泛型方法。
1. 泛型类:
//此处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);
2. 泛型接口 :
class GeneratorImpl<T> implements Generator<T>{
@Override
public T method() {
return null;
}
}
实现泛型接口,指定类型:
class GeneratorImpl<T> implements Generator<String>{
@Override
public String method() {
return "hello";
}
}
3. 泛型方法 :
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 );
泛型常用的通配符有哪些?
常用的通配符为:T,E,K,V,?
- ?表示不确定的 java 类型
- T (type) 表示具体的一个 java 类型
- K V (key value) 分别代表 java 键值中的 Key Value
- E (element) 代表 Element
什么是泛型擦除?
所谓的泛型擦除,官方名叫 “类型擦除”。
Java 的泛型是伪泛型,这是因为 Java 在编译期间,所有的类型信息都会被擦掉。
也就是说,在运行的时候是没有泛型的。
例如这段代码,往一群猫里放条狗:
LinkedList<Cat> cats = new LinkedList<Cat>();
LinkedList list = cats; // 注意我在这里把范型去掉了,但是list和cats是同一个链表!
list.add(new Dog()); // 完全没问题!
因为 Java 的范型只存在于源码里,编译的时候给你静态地检查一下范型类型是否正确,而到了运行时就不检查了。上面这段代码在 JRE(Java 运行环境)看来和下面这段没区别:
LinkedList cats = new LinkedList(); // 注意:没有范型!
LinkedList list = cats;
list.add(new Dog());
为什么要类型擦除呢?
主要是为了向下兼容,因为 JDK5 之前是没有泛型的,为了让 JVM 保持向下兼容,就出了类型擦除这个策略。
注解
说一下你对注解的理解?
Java 注解本质上是一个标记,可以理解成生活中的一个人的一些小装扮,比如戴什么什么帽子,戴什么眼镜。
注解可以标记在类上、方法上、属性上等,标记自身也可以设置一些值,比如帽子颜色是绿色。
有了标记之后,我们就可以在编译或者运行阶段去识别这些标记,然后搞一些事情,这就是注解的用处。
例如我们常见的 AOP
,使用注解作为切点就是运行期注解的应用;比如 lombok
,就是注解在编译期的运行。
注解生命周期有三大类,分别是:
RetentionPolicy.SOURCE
:给编译器用的,不会写入class
文件RetentionPolicy.CLASS
:会写入class
文件,在类加载阶段丢弃,也就是运行的时候就没这个信息了RetentionPolicy.RUNTIME
:会写入class
文件,永久保存,可以通过反射获取注解信息
所以我上文写的是解析的时候,没写具体是解析啥,因为不同的生命周期的解析动作是不同的。
像常见的:
就是给编译器用的,编译器编译的时候检查没问题就 over
了,class
文件里面不会有 Override
这个标记。
再比如 Spring
常见的 Autowired
,就是 RUNTIME
的,所以在运行的时候可以通过反射得到注解的信息,还能拿到标记的值 required
。
反射
什么是反射?应用?原理?
什么是反射?
我们通常都是利用 new
方式来创建对象实例,这可以说就是一种 “正射”,这种方式在编译时候就确定了类型信息。
而如果,我们想在时候动态地获取类信息、创建类实例、调用类方法这时候就要用到反射。
通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。
反射最核心的四个类:
反射的应用场景?
一般我们平时都是在在写业务代码,很少会接触到直接使用反射机制的场景。
但是,这并不代表反射没有用。相反,正是因为反射,你才能这么轻松地使用各种框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。
像 Spring
里的很多 注解 ,它真正的功能实现就是利用反射。
就像为什么我们使用 Spring 的时候 ,一个 @Component
注解就声明了一个类为 Spring Bean 呢?为什么通过一个 @Value
注解就读取到配置文件中的值呢?究竟是怎么起作用的呢?
这些都是因为我们可以基于反射操作类,然后获取到类 / 属性 / 方法 / 方法的参数上的注解,注解这里就有两个作用,一是标记,我们对注解标记的类 / 属性 / 方法进行对应的处理;二是注解本身有一些信息,可以参与到处理的逻辑中。
反射的原理?
我们都知道 Java
程序的执行分为编译和运行两步,编译之后会生成字节码 (.class) 文件,JVM 进行类加载的时候,会加载字节码文件,将类型相关的所有信息加载进方法区,反射就是去获取这些信息,然后进行各种操作。
v1.5.2