答复: Java获得泛型类型

本文深入探讨了Java泛型的特点,区分了泛型在声明与使用时的不同行为,并通过实例展示了如何在运行时获取泛型信息。此外,还介绍了泛型在编译过程中的擦除机制及其对程序设计的影响。
转自[url=http://www.iteye.com/topic/585900#1357097]Java获得泛型类型[/url]的回复,内容有细微调整。

Java泛型有这么一种规律:
位于声明一侧的,源码里写了什么到运行时就能看到什么;
位于使用一侧的,源码里写什么到运行时都没了。

什么意思呢?“声明一侧”包括泛型类型(泛型类与泛型接口)声明、带有泛型参数的方法和域的声明。注意局部变量的声明不算在内,那个属于“使用”一侧。
import java.util.List;
import java.util.Map;

public class GenericClass<T> { // 1
private List<T> list; // 2
private Map<String, T> map; // 3

public <U> U genericMethod(Map<T, U> m) { // 4
return null;
}
}

上面代码里,带有注释的行里的泛型信息在运行时都还能获取到,原则是源码里写了什么运行时就能得到什么。针对1的GenericClass<T>,运行时通过Class.getTypeParameters()方法得到的数组可以获取那个“T”;同理,2的T、3的java.lang.String与T、4的T与U都可以获得。源码文本里写的是什么运行时就能得到什么;像是T、U等在运行时的实际类型是获取不到的。

这是因为从Java 5开始class文件的格式有了调整,规定这些泛型信息要写到class文件中。以上面的map为例,通过javap来看它的元数据可以看到记录了这样的信息:
private java.util.Map map;
Signature: Ljava/util/Map;
Signature: length = 0x2
00 0A

乍一看,private java.util.Map map;不正好显示了它的泛型类型被擦除了么?
但仔细看会发现有两个Signature,下面的一个有两字节的数据,0x0A。到常量池找到0x0A对应的项,是:
const #10 = Asciz       Ljava/util/Map<Ljava/lang/String;TT;>;;

也就是内容为“Ljava/util/Map<Ljava/lang/String;TT;>;”的一个字符串。
根据Java 5开始的新class文件格式规范,方法与域的描述符增添了对泛型信息的记录,用一对尖括号包围泛型参数,其中普通的引用类型用“La/b/c/D;”的格式记录,未绑定值的泛型变量用“Txxx;”的格式记录,其中xxx就是源码中声明的泛型变量名。类型声明的泛型信息也以类似下面的方式记了下来:
public class GenericClass extends java.lang.Object
Signature: length = 0x2
00 12
// ...
const #18 = Asciz <T:Ljava/lang/Object;>Ljava/lang/Object;;

详细信息请参考官方文档:[url]http://java.sun.com/docs/books/jvms/second_edition/ClassFileFormat-Java5.pdf[/url]
该文档也将会被整合到JVM规范第三版中。可惜第三版现在只有草案,最终版本什么时候发布还遥遥无期。


相比之下,“使用一侧”的泛型信息则完全没有被保留下来,在Java源码编译到class文件后就确实丢失了。也就是说,在方法体内的泛型局部变量、泛型方法调用之类的泛型信息编译后都消失了。
import java.util.ArrayList;
import java.util.List;

public class TestClass {
public static void main(String[] args) {
List<String> list = null; // 1
list = new ArrayList<String>(); // 2
for (int i = 0; i < 10; i++) ;
}
}

上面代码中,1留下的痕迹是:main()方法的StackMapTable属性里可以看到:
StackMapTable: number_of_entries = 2
frame_type = 253 /* append */
offset_delta = 12
locals = [ class java/util/List, int ]
frame_type = 250 /* chop */
offset_delta = 11

但这里是没有留下泛型信息的。这段代码只所以写了个空的for循环就是为了迫使javac生成那个StackMapTable,让1多留个影。当整个方法只有一个基本块的时候javac就不会生成StackMapTable属性,就看不到这可爱的数据结构了。
如果main()里用到了list的方法,那么那些方法调用点上也会留下1的痕迹,例如如果调用list.add("");,则会留下“java/util/List.add:(Ljava/lang/Object;)Z”这种记录。
2留下的是“java/util/ArrayList."<init>":()V”,同样也丢失了泛型信息。

由上述讨论可知,想对带有未绑定的泛型变量的泛型类型获取其实际类型是不现实的,因为class文件里根本没记录实际类型的信息。觉得这句话太拗口的话用例子来理解:要想对java.util.List<E>获取E的实际类型是不现实的,因为List.class文件里只记录了E,却没记录使用List<E>时E的实际类型。
想对局部变量等“使用一侧”的已绑定的泛型类型获取其实际类型也不现实,同样是因为class文件中根本没记录这个信息。例子直接看上面讲“使用一侧”的就可以了。

知道了什么信息有记录,什么信息没有记录之后,也就可以省点力气不去纠结“拿不到T的实际类型”、“建不出T类型的数组”、“不能对T类型做instanceof”之类的问题了orz
@Override public List<Message> getNullableResult(ResultSet rs, String columnName) throws SQLException { System.out.println("getNullableResult 1"); byte[] bytes = rs.getBytes(columnName); if (bytes == null || bytes.length == 0) return null; try { String json = new String(bytes, StandardCharsets.UTF_8); return objectMapper.readValue(json, new TypeReference<List<Message>>() {}); } catch (Exception e) { throw new SQLException("Failed to deserialize JSON to List<Message>", e); } } Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Java 8 date/time type `java.time.LocalDateTime` not supported by default: add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling at [Source: (String)"[{"role": "assistant", "content": "你好!我是源宝,一名专注于浙江省自然资源交易领域的专家助手,我可以帮助您解答以下问题: 政策法规解读:包括土地出让规则、矿权招拍挂政策等。 市场动态快讯:提供近期成交、热门地块等信息。 供地计划查询:包括地块编号、供地时间、位置等。 自然资源交易流程:涵盖报名材料、费用等问题。 数据报告或信息整理任务:如生成简报、分析总结。 如果您有任何具体问题或需要帮助,请随时告诉我,我会尽力为您提供专业、准确、简明的答复!"}, {"id": "13c93068-221b-41f2-af1d-b9390c9bc2dc", "role": "user", "content": "你还会什么?"}, {"id": "13c93068-221b-41f2-af1d-b9390c9bc2dc", "role": "assistant", "content": "<think>\n好的,我现在需要处理用户的问题:“你还会什么?”。首先,根据角色设定,我是“源宝”,专注于浙江省自然资源交易领域的专家助手。用户可能属于竞买人、出让人或管理人员"[truncated 2446 chars]; line: 1, column: 1403] (through reference chain: java.util.ArrayList[2]->cn.induschain.tradeuniportal.bean.agent.Message["created_at"])
最新发布
08-16
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值