Java获得泛型类型
Java代码
/*
* Copyright 2010 Sandy Zhang
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
/**
*
*/
package org.javazone.jroi.test.reflect;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
/**
* @author Sandy Zhang
*/
public class Bean
{
public Map list = new HashMap();
public static void main(String[] args) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException
{
Field c = Bean.class.getField("list");
Field f = Field.class.getDeclaredField("signature");
f.setAccessible(true);
System.out.println(((String) f.get(c)));
}
}
这个只是我最近在写一个反射调用的东西想到的问题,所以很无奈才必须要这样得到东西下面是结果
Java代码
Ljava/util/Map;
字符串都有了,你怕什么呢?呵呵!后话:这个方法并不应该被推荐,因为java api下并未提供任何方法实现,也就是说我们必须在特定环境和版本下才能这样干,至少得是1.5之后的版本以后sun(现在是oracle了)准备怎么改,这个是他的说法。。。--------------------------------OK,这个是一个引子,从这里我们看到了好些个玩意,也就是java其实提供了获取的方案的那么我们要进行私有操作的,这样对我们写代码养成这样的习惯可不好,没事就去拿私有的东西,那是不是应该有公共的方法呢?我们试一下将main里的调用修改一下
Java代码
Field c = Bean.class.getField("list");
System.out.println(c.toGenericString());
Java代码
public java.util.Map org.javazone.jroi.test.reflect.Bean.list
看看,我们拿到了什么,不需要setAccess了,呵呵哦,对了,你或许要问如果我不是字段呢?是方法怎么办。。于是乎
Java代码
package org.javazone.jroi.test.reflect;
import java.util.HashMap;
import java.util.Map;
/**
* @author Sandy Zhang
*/
public class Bean
{
public Map list = new HashMap();
public Map getList()
{
return list;
}
public static void main(String[] args) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException, NoSuchMethodException
{
System.out.println(Bean.class.getMethod("getList").toGenericString());
}
}
Java代码
public java.util.Map org.javazone.jroi.test.reflect.Bean.getList()
---------------------------------字符串有了,我们考虑下,API里是不是应该也提供了方法直接获取泛型类型,参考下
Java代码
getGenericType
public Type getGenericType()返回一个 Type 对象,它表示此 Field 对象所表示字段的声明类型。
如果 Type 是一个参数化类型,则返回的 Type 对象必须准确地反映源代码中使用的实际类型参数。
如果底层字段的类型是一个类型变量或者是一个参数化类型,则创建它。否则将解析它。
返回:
返回表示此 Field 对象所表示字段的声明类型的 Type 对象
抛出:
GenericSignatureFormatError - 如果一般字段签名不符合 Java Virtual Machine Specification, 3rd edition 中指定的格式
TypeNotPresentException - 如果底层字段的一般类型签名引用了不存在的类型声明
MalformedParameterizedTypeException - 如果底层字段的一般签名引用了一个因某种原因而无法实例化的参数化类型
从以下版本开始:
1.5
由此可见,在1.5之后添加了众多以Generic为关键字的方法,这些方法就是用来获取泛型参数的有效途径,但是或许表面上看不出来,因为他们都是返回的Type接口,而非ParameterizedType接口,所以我困惑了很久。OK,下面我们看一下怎么实现吧
Java代码
public class GenericTest
{
public List list = new LinkedList();
public static void main(String[] args) throws SecurityException, NoSuchFieldException
{
ParameterizedType pt = (ParameterizedType) GenericTest.class.getField(
"list").getGenericType();
System.out.println(pt.getActualTypeArguments().length);
System.out.println(pt.getActualTypeArguments()[0]);
}
}
1class java.lang.String这里是结果
-___________________________________________________________
Java泛型有这么一种规律:位于声明一侧的,源码里写了什么到运行时就能看到什么;位于使用一侧的,源码里写什么到运行时都没了。什么意思呢?“声明一侧”包括泛型类型(泛型类与泛型接口)声明、带有泛型参数的方法和域的声明。注意局部变量的声明不算在内,那个属于“使用”一侧。
Java代码
import java.util.List;
import java.util.Map;
public class GenericClass { // 1
private List list; // 2
private Map map; // 3
public U genericMethod(Map m) { // 4
return null;
}
}
上面代码里,带有注释的行里的泛型信息在运行时都还能获取到,原则是源码里写了什么运行时就能得到什么。针对1的GenericClass,运行时通过Class.getTypeParameters()方法得到的数组可以获取那个“T”;同理,2的T、3的java.lang.String与T、4的T与U都可以获得。这是因为从Java 5开始class文件的格式有了调整,规定这些泛型信息要写到class文件中。以上面的map为例,通过javap来看它的元数据可以看到记录了这样的信息:
Javap代码
private java.util.Map map;
Signature: Ljava/util/Map;
Signature: length = 0x2
00 0A
乍一看,private java.util.Map map;不正好显示了它的泛型类型被擦除了么?但仔细看会发现有两个Signature,下面的一个有两字节的数据,0x0A。到常量池找到0x0A对应的项,是:
Javap代码
const #10 = Asciz Ljava/util/Map;;
也就是内容为“Ljava/util/Map;”的一个字符串。根据Java 5开始的新class文件格式规范,方法与域的描述符增添了对泛型信息的记录,用一对尖括号包围泛型参数,其中普通的引用类型用“La/b/c/D;”的格式记录,未绑定值的泛型变量用“Txxx;”的格式记录,其中xxx就是源码中声明的泛型变量名。类型声明的泛型信息也以类似下面的方式记了下来:
Javap代码
public class GenericClass extends java.lang.Object
Signature: length = 0x2
00 12
// ...
const #18 = Asciz Ljava/lang/Object;;
详细信息请参考官方文档:http://java.sun.com/docs/books/jvms/second_edition/ClassFileFormat-Java5.pdf相比之下,“使用一侧”的泛型信息则完全没有被保留下来,在Java源码编译到class文件后就确实丢失了。也就是说,在方法体内的泛型局部变量、泛型方法调用之类的泛型信息编译后都消失了。
Java代码
import java.util.ArrayList;
import java.util.List;
public class TestClass {
public static void main(String[] args) {
List list = null; // 1
list = new ArrayList(); // 2
for (int i = 0; i
}
}
上面代码中,1留下的痕迹是:main()方法的StackMapTable属性里可以看到:
Javap代码
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多留个影。如果main()里用到了list的方法,那么那些方法调用点上也会留下1的痕迹,例如如果调用list.add("");,则会留下“java/util/List.add:(Ljava/lang/Object;)Z”这种记录。2留下的是“java/util/ArrayList."":()V”,同样也丢失了泛型信息。由上述讨论可知,想对带有未绑定的泛型变量的泛型类型获取其实际类型是不现实的,因为class文件里根本没记录实际类型的信息。觉得这句话太拗口的话用例子来理解:要想对java.util.List获取E的实际类型是不现实的,因为List.class文件里只记录了E,却没记录使用List时E的实际类型。想对局部变量等“使用一侧”的已绑定的泛型类型获取其实际类型也不现实,同样是因为class文件中根本没记录这个信息。例子直接看上面讲“使用一侧”的就可以了。知道了什么信息有记录,什么信息没有记录之后,也就可以省点力气不去纠结“拿不到T的实际类型”、“建不出T类型的数组”之类的问题了orz