- 浏览: 20538 次
- 性别:
- 来自: 重庆
最新评论
-
agapple:
以前用的获取泛型的一个方法,可恶意处理多级泛型和泛型数组。
...
Java获得泛型类型 -
Relucent:
如果是方法 可以用 getGenericParameterTy ...
Java获得泛型类型 -
zcy860511:
iamlotus 写道<div class=&q ...
Java获得泛型类型 -
zcy860511:
yidao620c 写道递归再递归咋没说、、、、
只是一个递归 ...
在递归再递归之后,终于解决了泛型嵌套问题。。。 -
yidao620c:
递归再递归咋没说、、、、
在递归再递归之后,终于解决了泛型嵌套问题。。。
最近要想获得泛型类型,总结多方意见,再通过实践,最终获得了结果。
当然也被许多文章给误导过……
下面我们看一个例子,这个例子是我自己写的
这个只是我最近在写一个反射调用的东西想到的问题,所以很无奈才必须要这样得到东西
下面是结果
字符串都有了,你怕什么呢?呵呵!
后话:这个方法并不应该被推荐,因为java api下并未提供任何方法实现,也就是说我们必须在特定环境和版本下才能这样干,至少得是1.5之后的版本以后sun(现在是oracle了)准备怎么改,这个是他的说法。。。
--------------------------------------------------------------------
OK,这个是一个引子,从这里我们看到了好些个玩意,也就是java其实提供了获取的方案的
那么我们要进行私有操作的,这样对我们写代码养成这样的习惯可不好,没事就去拿私有的东西,那是不是应该有公共的方法呢?我们试一下
将main里的调用修改一下
看看,我们拿到了什么,不需要setAccess了,呵呵
哦,对了,你或许要问如果我不是字段呢?是方法怎么办。。于是乎
---------------------------------------------------------------------
字符串有了,我们考虑下,API里是不是应该也提供了方法直接获取泛型类型,参考下
由此可见,在1.5之后添加了众多以Generic为关键字的方法,这些方法就是用来获取泛型参数的有效途径,但是或许表面上看不出来,因为他们都是返回的Type接口,而非ParameterizedType接口,所以我困惑了很久。OK,下面我们看一下怎么实现吧
1
class java.lang.String
这里是结果
---------------------------------------------------------------------
于是乎,所有的问题都迎刃而解了。
似乎文章很长,我通过Debug的方式发现了代码的运行过程,最终找到了核心字符串,说明即使是檫除式的泛型java也会记录下这些东西,没道理拿不到
这个功能我找了很久,貌似无法实现。
这个确实是不行。如果不是静态方法而是成员方法的话还有点可能,好歹能拿到“this”
话说这让我想起以前别人提出“为什么不弄个'static virtual method'呢?”,不过现实是Java里没这玩儿
这个功能我找了很久,貌似无法实现。
自己写不出来,就说人家抄袭,真卑鄙!真无耻!真龌龊!
楼主的这篇文章实在是太好了,让我如醍醐灌顶,茅塞顿开,解决了我多年来的疑惑!感谢楼主!
文明用语,别给中国人丢脸!
很简单,首先看语言对泛型语义的要求,然后看实现方式。
从实现看,编译器中同一泛型参数被绑定为不同类型后生成的类型都被认为是一种独立的、新的类型;除非像Java一样在语言规范中规定已绑定参数的泛型类型与“raw type”之间有转换关系,否则新生成的类型之间就是不可转换的。具有膨胀法泛型语义的C++与C#中没有“raw type”的概念,一个模板(C++)或泛型类型(C#)必须等到其类型参数都绑定了之后才会得到类型的实例化,而只有实例化后的类型才可能有对象实例。即便是具有擦除法泛型语义的Java,要编译下面这段代码也是通不过的:
要通过已绑定参数的泛型类型与raw type之间的转换关系来强制实现转换:
这里想说明的是,即便是Java,编译器中也有泛型类型实例化的概念,只不过根据语言定义的转换允许泛型类型间的间接转换而已。
接着看C++:
这里,vector不是一个类型,而是一个有待实例化的模板。其声明类似:
这里,T是必须赋值的类型参数,Allocator是有默认值的类型参数。“vector”自身不是完成类型,无法拥有对于的对象实例。假如让T与string绑定,形成vector<string>,这就是一个实例化了的模板类,或者说完成类型;它可以拥有对象实例,如上面代码中的vec。
形象点说,“vector”更像是一个工厂函数,自身并不是类型而可以生产出类型来。也可以看成是相对安全一些的宏,要把模板中的类型变量都替换为实际类型才能用于对象实例化。
由于C++的类型系统中模板类没有协变/逆变的概念,两个完成类型间无论如何都无法转换,除非动用reinterpret_cast<>。
这个转换通不过编译:
而这个可以:
但得知道自己到底在做什么就是了。
C++对RTTI的实现普遍比较弱,这里就不讨论运行时查询模板参数的问题了。
那么C++里为什么可以在模板里写“new T”?因为模板要实例化为完成类型才能用,此时T是已知的实际类型了;模板的实例化是在编译器中完成的。
然后看C#。
与C++类似,C#的泛型有膨胀式语义;泛型类型需要实例化为完成类型后才可以用于创建对象。上面的代码中,System.Collections.Generic.IList不存在,System.Collections.Generic.IList<>是未完成的泛型接口,System.Collections.Generic.IList<string>是一个完成的泛型接口。同样,System.Collections.Generic.List不存在,System.Collections.Generic.List<>是未完成的泛型类,System.Collections.Generic.List<string>是一个完成的泛型类。于是我们无法创建一个类型为System.Collections.Generic.List的对象实例,甚至不能写typeof(List)因为List不存在。List<>只能用在typeof()运算符当中,用于获取未完成泛型类型的反射信息。List<string>则是一个完成类型,可以用在任何普通类型可以用的地方,包括typeof(),包括new,等等。
在实现中,C#的编译器内部也会记录下泛型类型的类型实例化,并在生成的程序集(assembly)中保留相应的元数据(符号信息)。在运行时,CLI会读取这些元数据,在运行时对泛型类型做类型实例化,生成合适的RuntimeType来对应到每个实例化的泛型类型。通过反射可以在运行时获取任意对象的任意类型信息。上面的例子中可以在运行时通过反射查询到list指向的对象实例的类型是List<string>,通过该类型又可以查询到List<>与IList<string>、IList<>、string等类型。
与C++类似,C#中的泛型类型之间也无法随意转换,也没有“raw type”的概念。C# 4支持泛型的协变/逆变,允许诸如
的转换。
回到楼主的问题,如果有C#代码:
那么list变量的类型是什么呢?是IList<object>,其泛型参数就是object,不是string也不是int。IList<object>、IList<string>、IList<int>之间没有转换关系,要转只有通过所有对象与object间的转换关系来间接强制转换,并在运行时得到无法转换的异常。
list所指向的对象实例是List<object>类型的,它的泛型参数也是object,而不是string也不是int。
自己写不出来,就说人家抄袭,真卑鄙!真无耻!真龌龊!
楼主的这篇文章实在是太好了,让我如醍醐灌顶,茅塞顿开,解决了我多年来的疑惑!感谢楼主!
为什么说我是抄袭?抄袭总要给个理由吧,你能在网上找到另外一篇么?至少最近不行,别人抄不抄就不知道了
当然也被许多文章给误导过……
下面我们看一个例子,这个例子是我自己写的
/* * 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<String, ListBean> list = new HashMap<String, ListBean>(); 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))); } }
这个只是我最近在写一个反射调用的东西想到的问题,所以很无奈才必须要这样得到东西
下面是结果
Ljava/util/Map<Ljava/lang/String;Lorg/javazone/jroi/test/reflect/ListBean;>;
字符串都有了,你怕什么呢?呵呵!
后话:这个方法并不应该被推荐,因为java api下并未提供任何方法实现,也就是说我们必须在特定环境和版本下才能这样干,至少得是1.5之后的版本以后sun(现在是oracle了)准备怎么改,这个是他的说法。。。
--------------------------------------------------------------------
OK,这个是一个引子,从这里我们看到了好些个玩意,也就是java其实提供了获取的方案的
那么我们要进行私有操作的,这样对我们写代码养成这样的习惯可不好,没事就去拿私有的东西,那是不是应该有公共的方法呢?我们试一下
将main里的调用修改一下
Field c = Bean.class.getField("list"); System.out.println(c.toGenericString());
public java.util.Map<java.lang.String, org.javazone.jroi.test.reflect.ListBean> org.javazone.jroi.test.reflect.Bean.list
看看,我们拿到了什么,不需要setAccess了,呵呵
哦,对了,你或许要问如果我不是字段呢?是方法怎么办。。于是乎
package org.javazone.jroi.test.reflect; import java.util.HashMap; import java.util.Map; /** * @author Sandy Zhang */ public class Bean { public Map<String, ListBean> list = new HashMap<String, ListBean>(); public Map<String, ListBean> getList() { return list; } public static void main(String[] args) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException, NoSuchMethodException { System.out.println(Bean.class.getMethod("getList").toGenericString()); } }
public java.util.Map<java.lang.String, org.javazone.jroi.test.reflect.ListBean> org.javazone.jroi.test.reflect.Bean.getList()
---------------------------------------------------------------------
字符串有了,我们考虑下,API里是不是应该也提供了方法直接获取泛型类型,参考下
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,下面我们看一下怎么实现吧
public class GenericTest { public List<String> list = new LinkedList<String>(); 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]); } }
1
class java.lang.String
这里是结果
---------------------------------------------------------------------
于是乎,所有的问题都迎刃而解了。
似乎文章很长,我通过Debug的方式发现了代码的运行过程,最终找到了核心字符串,说明即使是檫除式的泛型java也会记录下这些东西,没道理拿不到
评论
12 楼
RednaxelaFX
2010-02-03
bonny 写道
class Domain{ public static Class getCurrentDomainClass(){ //获取运行时类型 } } class User() extends Domain{ } class Test { @Test public void test(){ User.getCurrentDomainClass();//。。。。无法获取User.class } }
这个功能我找了很久,貌似无法实现。
这个确实是不行。如果不是静态方法而是成员方法的话还有点可能,好歹能拿到“this”
话说这让我想起以前别人提出“为什么不弄个'static virtual method'呢?”,不过现实是Java里没这玩儿
11 楼
bonny
2010-02-03
class Domain{ public static Class getCurrentDomainClass(){ //获取运行时类型 } } class User() extends Domain{ } class Test { @Test public void test(){ User.getCurrentDomainClass();//。。。。无法获取User.class } }
这个功能我找了很久,貌似无法实现。
10 楼
tigerlchen
2010-02-03
zcy860511 写道
最近要想获得泛型类型,总结多方意见,再通过实践,最终获得了结果。
当然也被许多文章给误导过……
下面我们看一个例子,这个例子是我自己写的
这个只是我最近在写一个反射调用的东西想到的问题,所以很无奈才必须要这样得到东西
下面是结果
字符串都有了,你怕什么呢?呵呵!
后话:这个方法并不应该被推荐,因为java api下并未提供任何方法实现,也就是说我们必须在特定环境和版本下才能这样干,至少得是1.5之后的版本以后sun(现在是oracle了)准备怎么改,这个是他的说法。。。
--------------------------------------------------------------------
OK,这个是一个引子,从这里我们看到了好些个玩意,也就是java其实提供了获取的方案的
那么我们要进行私有操作的,这样对我们写代码养成这样的习惯可不好,没事就去拿私有的东西,那是不是应该有公共的方法呢?我们试一下
将main里的调用修改一下
看看,我们拿到了什么,不需要setAccess了,呵呵
哦,对了,你或许要问如果我不是字段呢?是方法怎么办。。于是乎
---------------------------------------------------------------------
字符串有了,我们考虑下,API里是不是应该也提供了方法直接获取泛型类型,参考下
由此可见,在1.5之后添加了众多以Generic为关键字的方法,这些方法就是用来获取泛型参数的有效途径,但是或许表面上看不出来,因为他们都是返回的Type接口,而非ParameterizedType接口,所以我困惑了很久。OK,下面我们看一下怎么实现吧
1
class java.lang.String
这里是结果
---------------------------------------------------------------------
于是乎,所有的问题都迎刃而解了。
似乎文章很长,我通过Debug的方式发现了代码的运行过程,最终找到了核心字符串,说明即使是檫除式的泛型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<String, ListBean> list = new HashMap<String, ListBean>(); 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))); } }
这个只是我最近在写一个反射调用的东西想到的问题,所以很无奈才必须要这样得到东西
下面是结果
Ljava/util/Map<Ljava/lang/String;Lorg/javazone/jroi/test/reflect/ListBean;>;
字符串都有了,你怕什么呢?呵呵!
后话:这个方法并不应该被推荐,因为java api下并未提供任何方法实现,也就是说我们必须在特定环境和版本下才能这样干,至少得是1.5之后的版本以后sun(现在是oracle了)准备怎么改,这个是他的说法。。。
--------------------------------------------------------------------
OK,这个是一个引子,从这里我们看到了好些个玩意,也就是java其实提供了获取的方案的
那么我们要进行私有操作的,这样对我们写代码养成这样的习惯可不好,没事就去拿私有的东西,那是不是应该有公共的方法呢?我们试一下
将main里的调用修改一下
Field c = Bean.class.getField("list"); System.out.println(c.toGenericString());
public java.util.Map<java.lang.String, org.javazone.jroi.test.reflect.ListBean> org.javazone.jroi.test.reflect.Bean.list
看看,我们拿到了什么,不需要setAccess了,呵呵
哦,对了,你或许要问如果我不是字段呢?是方法怎么办。。于是乎
package org.javazone.jroi.test.reflect; import java.util.HashMap; import java.util.Map; /** * @author Sandy Zhang */ public class Bean { public Map<String, ListBean> list = new HashMap<String, ListBean>(); public Map<String, ListBean> getList() { return list; } public static void main(String[] args) throws SecurityException, NoSuchFieldException, IllegalArgumentException, IllegalAccessException, NoSuchMethodException { System.out.println(Bean.class.getMethod("getList").toGenericString()); } }
public java.util.Map<java.lang.String, org.javazone.jroi.test.reflect.ListBean> org.javazone.jroi.test.reflect.Bean.getList()
---------------------------------------------------------------------
字符串有了,我们考虑下,API里是不是应该也提供了方法直接获取泛型类型,参考下
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,下面我们看一下怎么实现吧
public class GenericTest { public List<String> list = new LinkedList<String>(); 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]); } }
1
class java.lang.String
这里是结果
---------------------------------------------------------------------
于是乎,所有的问题都迎刃而解了。
似乎文章很长,我通过Debug的方式发现了代码的运行过程,最终找到了核心字符串,说明即使是檫除式的泛型java也会记录下这些东西,没道理拿不到
9 楼
gwpking8419
2010-02-03
andot 写道
askyuan 写道
抄袭的吧?呵呵
自己写不出来,就说人家抄袭,真卑鄙!真无耻!真龌龊!
楼主的这篇文章实在是太好了,让我如醍醐灌顶,茅塞顿开,解决了我多年来的疑惑!感谢楼主!
文明用语,别给中国人丢脸!
8 楼
gwpking8419
2010-02-03
文明用语,别给中国人丢脸!
7 楼
RednaxelaFX
2010-02-03
zcy860511 写道
不论是否为擦出式的,我们思考这样一个问题
这样的定义,你怎么确定list里的类型?是Integer还是String?
这是一个问题,不知道C#里怎么实现的
static List<Object> list = new ArrayList<Object>(); public static void main(String[] args) { list.add("asasdasd"); list.add(123); }
这样的定义,你怎么确定list里的类型?是Integer还是String?
这是一个问题,不知道C#里怎么实现的
很简单,首先看语言对泛型语义的要求,然后看实现方式。
从实现看,编译器中同一泛型参数被绑定为不同类型后生成的类型都被认为是一种独立的、新的类型;除非像Java一样在语言规范中规定已绑定参数的泛型类型与“raw type”之间有转换关系,否则新生成的类型之间就是不可转换的。具有膨胀法泛型语义的C++与C#中没有“raw type”的概念,一个模板(C++)或泛型类型(C#)必须等到其类型参数都绑定了之后才会得到类型的实例化,而只有实例化后的类型才可能有对象实例。即便是具有擦除法泛型语义的Java,要编译下面这段代码也是通不过的:
List<Exception> exceptions = new ArrayList<Exception>(); List<Error> errors = exceptions;
不兼容的类型 找到: java.util.List<java.lang.Exception> 需要: java.util.List<java.lang.Error> List<Error> errors = exceptions; ^
要通过已绑定参数的泛型类型与raw type之间的转换关系来强制实现转换:
List<Exception> exceptions = new ArrayList<Exception>(); List<Error> errors = (List) exceptions;
这里想说明的是,即便是Java,编译器中也有泛型类型实例化的概念,只不过根据语言定义的转换允许泛型类型间的间接转换而已。
接着看C++:
vector<string> vec; int main(int argc, char* argv[]) { vec.push_back("abcd"); // ok vec.push_back(123); // compilation error }
这里,vector不是一个类型,而是一个有待实例化的模板。其声明类似:
template < class T, class Allocator = allocator<T> > class vector;
这里,T是必须赋值的类型参数,Allocator是有默认值的类型参数。“vector”自身不是完成类型,无法拥有对于的对象实例。假如让T与string绑定,形成vector<string>,这就是一个实例化了的模板类,或者说完成类型;它可以拥有对象实例,如上面代码中的vec。
形象点说,“vector”更像是一个工厂函数,自身并不是类型而可以生产出类型来。也可以看成是相对安全一些的宏,要把模板中的类型变量都替换为实际类型才能用于对象实例化。
由于C++的类型系统中模板类没有协变/逆变的概念,两个完成类型间无论如何都无法转换,除非动用reinterpret_cast<>。
这个转换通不过编译:
#include <vector> using namespace std; typedef long long int64; int main(int argc, char* argv[]) { vector<double> ds; vector<int64>* ls = &ds; }
而这个可以:
#include <vector> using namespace std; typedef long long int64; int main(int argc, char* argv[]) { vector<double> ds; vector<int64>* ls = reinterpret_cast<vector<int64>*>(&ds); }
但得知道自己到底在做什么就是了。
C++对RTTI的实现普遍比较弱,这里就不讨论运行时查询模板参数的问题了。
那么C++里为什么可以在模板里写“new T”?因为模板要实例化为完成类型才能用,此时T是已知的实际类型了;模板的实例化是在编译器中完成的。
然后看C#。
using System.Collections.Generic; static class Program { static void Main(string[] args) { IList<string> list = new List<string>(); list.Add("abc"); // ok list.Add(123); // compilation error } }
与C++类似,C#的泛型有膨胀式语义;泛型类型需要实例化为完成类型后才可以用于创建对象。上面的代码中,System.Collections.Generic.IList不存在,System.Collections.Generic.IList<>是未完成的泛型接口,System.Collections.Generic.IList<string>是一个完成的泛型接口。同样,System.Collections.Generic.List不存在,System.Collections.Generic.List<>是未完成的泛型类,System.Collections.Generic.List<string>是一个完成的泛型类。于是我们无法创建一个类型为System.Collections.Generic.List的对象实例,甚至不能写typeof(List)因为List不存在。List<>只能用在typeof()运算符当中,用于获取未完成泛型类型的反射信息。List<string>则是一个完成类型,可以用在任何普通类型可以用的地方,包括typeof(),包括new,等等。
在实现中,C#的编译器内部也会记录下泛型类型的类型实例化,并在生成的程序集(assembly)中保留相应的元数据(符号信息)。在运行时,CLI会读取这些元数据,在运行时对泛型类型做类型实例化,生成合适的RuntimeType来对应到每个实例化的泛型类型。通过反射可以在运行时获取任意对象的任意类型信息。上面的例子中可以在运行时通过反射查询到list指向的对象实例的类型是List<string>,通过该类型又可以查询到List<>与IList<string>、IList<>、string等类型。
与C++类似,C#中的泛型类型之间也无法随意转换,也没有“raw type”的概念。C# 4支持泛型的协变/逆变,允许诸如
IEnumerable<Apple> apples = GetApples(); IEnumerable<Fruit> fruits = apples;
的转换。
回到楼主的问题,如果有C#代码:
using System.Collections.Generic; static class Program { static void Main(string[] args) { IList<object> list = new List<object>(); list.Add("abc"); // ok list.Add(123); // ok, int boxed } }
那么list变量的类型是什么呢?是IList<object>,其泛型参数就是object,不是string也不是int。IList<object>、IList<string>、IList<int>之间没有转换关系,要转只有通过所有对象与object间的转换关系来间接强制转换,并在运行时得到无法转换的异常。
list所指向的对象实例是List<object>类型的,它的泛型参数也是object,而不是string也不是int。
6 楼
zcy860511
2010-02-03
不论是否为擦出式的,我们思考这样一个问题
这样的定义,你怎么确定list里的类型?是Integer还是String?
这是一个问题,不知道C#里怎么实现的
static List<Object> list = new ArrayList<Object>(); public static void main(String[] args) { list.add("asasdasd"); list.add(123); }
这样的定义,你怎么确定list里的类型?是Integer还是String?
这是一个问题,不知道C#里怎么实现的
5 楼
RednaxelaFX
2010-02-03
Java泛型有这么一种规律:
位于声明一侧的,源码里写了什么到运行时就能看到什么;
位于使用一侧的,源码里写什么到运行时都没了。
什么意思呢?“声明一侧”包括泛型类型(泛型类与泛型接口)声明、带有泛型参数的方法和域的声明。注意局部变量的声明不算在内,那个属于“使用”一侧。
上面代码里,带有注释的行里的泛型信息在运行时都还能获取到,原则是源码里写了什么运行时就能得到什么。针对1的GenericClass<T>,运行时通过Class.getTypeParameters()方法得到的数组可以获取那个“T”;同理,2的T、3的java.lang.String与T、4的T与U都可以获得。
这是因为从Java 5开始class文件的格式有了调整,规定这些泛型信息要写到class文件中。以上面的map为例,通过javap来看它的元数据可以看到记录了这样的信息:
乍一看,private java.util.Map map;不正好显示了它的泛型类型被擦除了么?
但仔细看会发现有两个Signature,下面的一个有两字节的数据,0x0A。到常量池找到0x0A对应的项,是:
也就是内容为“Ljava/util/Map<Ljava/lang/String;TT;>;”的一个字符串。
根据Java 5开始的新class文件格式规范,方法与域的描述符增添了对泛型信息的记录,用一对尖括号包围泛型参数,其中普通的引用类型用“La/b/c/D;”的格式记录,未绑定值的泛型变量用“Txxx;”的格式记录,其中xxx就是源码中声明的泛型变量名。类型声明的泛型信息也以类似下面的方式记了下来:
详细信息请参考官方文档:http://java.sun.com/docs/books/jvms/second_edition/ClassFileFormat-Java5.pdf
相比之下,“使用一侧”的泛型信息则完全没有被保留下来,在Java源码编译到class文件后就确实丢失了。也就是说,在方法体内的泛型局部变量、泛型方法调用之类的泛型信息编译后都消失了。
上面代码中,1留下的痕迹是:main()方法的StackMapTable属性里可以看到:
但这里是没有留下泛型信息的。这段代码只所以写了个空的for循环就是为了迫使javac生成那个StackMapTable,让1多留个影。
如果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类型的数组”之类的问题了orz
位于声明一侧的,源码里写了什么到运行时就能看到什么;
位于使用一侧的,源码里写什么到运行时都没了。
什么意思呢?“声明一侧”包括泛型类型(泛型类与泛型接口)声明、带有泛型参数的方法和域的声明。注意局部变量的声明不算在内,那个属于“使用”一侧。
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都可以获得。
这是因为从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;;
详细信息请参考官方文档:http://java.sun.com/docs/books/jvms/second_edition/ClassFileFormat-Java5.pdf
相比之下,“使用一侧”的泛型信息则完全没有被保留下来,在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多留个影。
如果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类型的数组”之类的问题了orz
4 楼
andot
2010-02-02
askyuan 写道
抄袭的吧?呵呵
自己写不出来,就说人家抄袭,真卑鄙!真无耻!真龌龊!
楼主的这篇文章实在是太好了,让我如醍醐灌顶,茅塞顿开,解决了我多年来的疑惑!感谢楼主!
3 楼
zcy860511
2010-02-02
askyuan 写道
抄袭的吧?呵呵
为什么说我是抄袭?抄袭总要给个理由吧,你能在网上找到另外一篇么?至少最近不行,别人抄不抄就不知道了
2 楼
askyuan
2010-02-02
抄袭的吧?呵呵
1 楼
zcy860511
2010-02-02
额。。。怎么发到这里来了
发表评论
-
Javascript Remote Object Invoker - Beta1
2010-02-09 22:20 1289第一个测试版本,可以 ... -
第一版,可以理解为Preview
2010-02-08 23:44 993基于前两天写的一个Coderhttp://zcy860511. ... -
在递归再递归之后,终于解决了泛型嵌套问题。。。
2010-02-07 01:25 4204总算给折腾出来了,只要定义泛型就可以完成Bean的转换 下面是 ... -
自己写了一个JsonLib
2010-02-04 05:42 2269自己写了一个Json的编码器和解码器 下面的地址是对比用的js ...
相关推荐
综上所述,虽然Java泛型在编译后会进行类型擦除,但通过上述技巧,我们仍然能够在运行时获得关于泛型类实例化类型的一些信息。在实际开发中,这些方法可以帮助我们编写更加灵活和安全的代码。在示例文件`GenericRTTI...
Java泛型是Java编程语言中的一种重要特性,它允许开发者在编写代码时指定类型参数,从而提高代码的灵活性和可读性。本文将详细介绍Java泛型的用法 及T.class的获取过程解析。 一、泛型的基本概念 泛型是Java 5中...
因此,虽然在编译期间我们能获得类型检查的好处,但在运行时,泛型接口和类的行为与无参数类型版本基本相同。 5. **通配符** 在某些情况下,我们可能不关心类型参数的具体类型,而只关心它是某个类的子类或者实现...
3. **类型擦除**:Java的泛型在编译后会执行类型擦除,这意味着在运行时,所有的泛型类型信息都会丢失。因此,虽然在编译时提供了类型检查,但在运行时,泛型对象仍然是Object类型。这就是为什么你仍然可以将任何...
Java泛型技术的发展不仅标志着编程语言对类型安全和代码复用的重视,也反映了软件工程领域对模块化、复用性和维护性的不断追求。自JDK1.4以来,泛型技术已成为Java开发不可或缺的一部分,极大地提升了开发效率和代码...
- **泛型擦除的影响**:运行时不能获取类型参数的具体类型,也无法实例化泛型类型参数。 - **类型参数的限制**:类型参数不能用于静态上下文中,因为静态成员与类关联而非对象关联。 #### 结语 通过本文的介绍,...
例如,以下代码在Java 7中是不合法的,因为编译器无法从上下文中推断出`ArrayList`的泛型类型: ```java List<String> list = new ArrayList(); list.add("A"); list.addAll(new ArrayList()); ``` 在Java 8中,...
在Java中,泛型是一种允许开发者在类、接口和方法中使用类型参数的功能。通过使用泛型,可以在编写代码时指定一个或多个类型参数,从而使得编写的代码更加灵活且重用性更高。这种机制在Java 5中被引入,并在集合框架...
泛型是Java SE 5版本引入的一个新特性,它的主要目的是允许在使用类、接口和方法时能够引用到任何类型的对象,同时在编译期间就能进行类型检查,提供更安全的代码。泛型类和泛型方法可以提高代码的复用性,并减少...
虽然Java编译器在编译期间检查泛型类型的有效性,但在生成字节码时会进行所谓的“类型擦除”过程。这意味着在运行时,泛型类型信息会被抹去,所有泛型类型都会转换成其对应的原始类型。 例如,`List<String>` 和 `...
8. 泛型集合:泛型引入后,Java 集合框架得到了极大的增强。使用泛型集合,可以避免在添加、删除和访问元素时出现 ClassCastException。 9. 泛型和多态:泛型可以与多态结合使用,使代码更加灵活。例如,一个接受 `...
这意味着所有泛型类型最终都会被转换为非泛型类型。例如,`List<String>`和`List<Integer>`在运行时实际上是相同的类型,都被擦除成了`List`。 ##### 类型擦除的影响 - **静态成员问题**:泛型类中的静态成员无法...
Java 泛型是Java 5引入的一个重要特性,它允许在类、接口和方法中使用类型参数,从而提高了代码的重用性和安全性。泛型的主要目的是在编译时检查类型安全,并且允许程序员以更方便的方式操作集合。 1. **无泛型的...
例如,类型擦除(Erasure)是Java泛型的一个重要概念,意味着泛型信息在编译后将被擦除,使得在运行时无法获得泛型类型的详细信息。因此,不能使用instanceof检查泛型类型的参数化形式,只能检查其擦除形式,例如...
例如,如果泛型类中的方法接受或返回泛型类型,那么应该考虑使用类型参数来定义这些方法,以保持类型安全性。 7. 通配符的使用场景: 通配符在读取时使用非常方便,因为读者不需要关心具体的类型。但在写入时就有...
3. 一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。 4. 类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。 5. 泛型方法体的声明和其他方法一样。 注意...