Java的反射
Java源代码编译后生成class文件,下图是一个class文件的结构图,截自《Java虚拟机规范》。
Java程序运行过程中,ClassLoader会根据需要加载这些class文件。每一个class文件加载完成后,虚拟机都会为其生成一个Class对象,可以通过类名.class或者实例引用.getClass()获取该对象。通过Class对象,我们就可以获取class文件中绝大部分信息,这就是Java反射。
Java的泛型
类型参数
相对于类型参数,我们应该更熟悉方法参数。当我们定义一个方法的时候,都会定义参数列表(形参),方法执行过程中,根据传参(实参)就能完成差异化逻辑。这就是封装方法的意义。
在Java中,相对于方法,类型是更高一个维度的概念,它也需要参数来完成更好的封装。例如:Java web的开发中,前后端交互的所有接口,都应该有统一的数据格式
public class Response {
private Object data;
public Object getData() {return data;}
public void setData(Object data) {this.data = data;}
}
然后接口代码:
public Response listUser() {
User user = ...;
Response resp = new Response();
resp.setData(user);
return resp;
}
上面的代码运行起来没有任何的问题,Response类也很通用。但这种通用是弱化了编译器静态检查功能换来的,listUser方法隐含了将User类型的值设置到Response.data的约束,但是编译器无法获知这一点,也就无法为你做这个检查。使用类型参数,我们可以将隐含的含义告知编译器,这样编译器就能为我们做编译检查了,如:
public class Response {
private T data;
public T getData() {return data;}
public void setData(T data) {this.data = data;}
}
接口代码:
public Response listUser() {
User user = ...;
Response resp = new Response<>();
resp.setData(user);
return resp;
}
类型参数,就是Java泛型,泛型的本质就是参数化类型。
类型擦除
Java泛型是Java 1.5之后才引入的。为了兼容历史版本,Java将泛型实现在Java语言层面(语法糖),也就是类型擦除。代码示例:
public static void main(String[] args) {
Response r1 = new Response<>();
Response r2 = new Response<>();
System.out.println(r1.getClass() == r2.getClass()); // 输出:true
}
输出true,说明Response和Response是同一种类型,程序在运行过程中是不携带类型实参的。通过Javap反汇编上面的代码:
0: new #2 // class com/yangqingbang/Response
3: dup
4: invokespecial #3 // Method com/yangqingbang/Response."":()V
7: astore_1
8: new #2 // class com/yangqingbang/Response
11: dup
12: invokespecial #3 // Method com/yangqingbang/Response."":()V
15: astore_2
可以看到,两次都是new com/yangqingbang/Response(),说明编译后的字节码中就没携带类型实参。
反射获取类型参数
前文说到,Java的泛型是类型擦除的,那么为什么反射又能获取到泛型相关信息呢?这个得从前面的class文件结构图说起,重点关注attribute_info域,也就是属性表。请注意,类、字段、方法均包含属性表,分别位于各自的结构内。属性表中包含一个Signature属性,截自《Java虚拟机规范》:
可见,类型擦取仅是指方法编译后的字节码中不携带类型参数,但在class文件结构中,还是携带了类型参数的。看下面两个例子:
public class Response {}
// javap反汇编
javap -v Response.class
# 省略无关信息,可见Signature中携带了类型参数
Signature: #14 // Ljava/lang/Object;
SourceFile: "Response.java"
public class UserResponse extends Response {}
// javap反汇编
javap -v UserResponse.class
# 省略无关信息,可见Signature中携带了类型参数
Signature: #12 // Lcom/yangqingbang/Response;
SourceFile: "UserResponse.java"
泛型的使用
很多资料把泛型的使用方式划分为3种:泛型类、泛型接口、泛型方法。在我看来,泛型接口在泛型的使用上是泛型类的一个子集,这里不进行细分了。
泛型类
public class UserService {
private E default;
public List list(E param) {return null;}
}
泛型类中的类型参数V,E在类被继承或类实例化时候被指定。
泛型方法
public class BeanFactory {
public T createInstance(Class clazz) throws Exception {
return clazz.getConstructor().newInstance();
}
}
泛型方法中的类型参数可以根据调用参数、返回值指定。