反射与泛型 java_深入理解Java的反射和泛型

Java的反射

Java源代码编译后生成class文件,下图是一个class文件的结构图,截自《Java虚拟机规范》。

0cbf148c79c6

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虚拟机规范》:

0cbf148c79c6

可见,类型擦取仅是指方法编译后的字节码中不携带类型参数,但在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();

}

}

泛型方法中的类型参数可以根据调用参数、返回值指定。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值