黑马程序员技术交流社区

标题: 反射与泛型的一点疑问,盼大神们深入解答 [打印本页]

作者: doitforyou    时间: 2013-12-29 17:23
标题: 反射与泛型的一点疑问,盼大神们深入解答
偶然发现一个小问题,但找不到答案,问题如下:
当我们使用泛型集合时,如果跳过反射,那么可以添加任意类型的Object,
这个是没错的,只是在获取的时候出现了类型转换的小问题,不是很明白,望解答。

        public static void main(String[] args) throws Exception{
                List<Integer> priList = new ArrayList<Integer>();
                priList.getClass().getMethod("add", Object.class).invoke(priList, "str");
//                当泛型为引用类型代表的基本类型时,如Integer、Character等等,此时,没有问题
                System.out.println(priList.get(0));
               
                List<String> refList = new ArrayList<String>();
                refList.getClass().getMethod("add", Object.class).invoke(refList, 1);
//                当泛型为引用类型,如String等Object类型时,此时获取会出现转换异常
                System.out.println(refList.get(0));
               
        }

错误提示:Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

作者: hurryup    时间: 2013-12-29 19:03
试了一下,参数类型是Object可以,不知道为什么String类型不能装int. 等高人回答
  1. List<Object> refList = new ArrayList<Object>();
  2.            refList.getClass().getMethod("add", Object.class).invoke(refList, Integer.parseInt("1"));
  3. //           当泛型为引用类型,如String等Object类型时,此时获取会出现转换异常
  4.            System.out.println(refList.get(0));
复制代码

作者: Kyle    时间: 2013-12-30 00:44
推敲这个问题涉及到底层代码。

首先你的代码有一个问题,就是当你在调用add方法的时候用的是反射,所以绕过了泛型
但是在使用get方法的时候你并没有使用反射,而是直接用变量名.get的方式。

然后你会问,为什么第一个输出没有问题,而第二个就有呢?

我们先分析第一个输出:
                 List<Integer> priList = new ArrayList<Integer>();
                 priList.getClass().getMethod("add", Object.class).invoke(priList, "str");
                 System.out.println(priList.get(0));
这里,priList.get(0)得到的是"str"这个字符串,打印就直接输出了这个字符串,所以没有问题

然后我们分析第二个:
                 List<String> refList = new ArrayList<String>();
                 refList.getClass().getMethod("add", Object.class).invoke(refList, 1);
                 System.out.println(refList.get(0));
这里,refList.get(0)得到的是一个Integer对象,而println打印非String类型时,会调用这个对象本身的toString()方法,转换成String再打印出来。 而这个时候就出问题了,因为得到的是Integer对象,而你所给的泛型是String,这时候类型就起了转换冲突,以至于异常了。
作者: Cheers_for_CRAZ    时间: 2013-12-30 10:47

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Demo{
          public static void main(String[]  args) throws Exception  
      {
                  List<String> priList = new ArrayList<String>();
          priList.getClass().getMethod("add", Object.class).invoke(priList,8);
          System.out.println(priList.size());//结果是1,说明已经将1存放进去了
         Iterator it = priList.iterator();
         System.out.println(it.next());//用Iterator迭代,发现也能找到。
         System.out.println(priList.get(0));//报错
      }
}
/*上面的运行结果
* 说明问题可能出现在S.O.P打印输出的结果上
* 也就是说S.O.P打印输出的时候会将其转换成字符串,调用toString()方法。
* 而Integer转换成字符串源代码:
* public String toString() {
        return String.valueOf(value);
    }
    而字符串转换成字符串源代码:
    public String toString() {
        return this;
    }
      也就是说Integer用return String.valueOf(value)将其转换成字符串,
                                                                      然后将字符串放到常量池中,等到用到的时候拿着引用值去找。
      而String返回它自己(引用值),等到用到的时候拿着它自己的引用值去到常量池找。
      
      由于你将集合的泛型定义成了Sting,所以它会直接拿着引用值去找。但是你在集合中存放的是Integer对象,
      这个对象调用的是toString是return this,返回的是堆中的引用值。
      但是拿着这个引用值去到方法区找,没找到--》报错。
      
*/
作者: doitforyou    时间: 2013-12-31 08:58
Cheers_for_CRAZ 发表于 2013-12-30 10:47
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

恩,谢谢。不过我反而更疑惑了,按照你说的如果泛型定义成String类型的话,源代码中直接返回this引用,
就是直接返回他的引用值,那么应该所有类型都不该报错啊,如果定义成非String类型的话,由源代码可知,
反而需要转换后返回,这样才会报错。
麻烦你再具体解释下,本人较愚钝,灰常感谢。。。
作者: Cheers_for_CRAZ    时间: 2013-12-31 13:15
doitforyou 发表于 2013-12-31 08:58
恩,谢谢。不过我反而更疑惑了,按照你说的如果泛型定义成String类型的话,源代码中直接返回this引用,
...

priList.get(0)这句话返回一个和集合中的泛型相同类型的元素E,然后这个元素E会去找他在本类中的toString方法,然后再打印输出。对吧?
如果你在泛型为String集合里面放了Integer对象,这时候我调用 的却是String里面的toString,返回了引用值,那么我是不是应该直接拿着引用值去字符串常量池中去找呢?但是我这时候Integer对象是放在堆中的呀,自然也就找不到了!
但是如果我再泛型为Integer的集合里面放了字符串,那就不一样了。虽然里面是字符串,但是这时候调用的toString却是Integer里面的toString,Integer里面的toString是将其转换成字符串放到常量池中,然后再返回给他。这时候拿着集合里面存放的字符串去找Integer里面的toString,也就是将字符串再转换成字符串放到常量池中,这时候打印就到常量池中找,自然能找到了!
总而言之:字符串是放在常量池中的,对象是放在堆中的。关键是调用了哪个类中的toString

作者: doitforyou    时间: 2013-12-31 14:42
Cheers_for_CRAZ 发表于 2013-12-31 13:15
priList.get(0)这句话返回一个和集合中的泛型相同类型的元素E,然后这个元素E会去找他在本类中的toString ...

恩,谢谢,明白了很多。再探讨下,谢谢啊:
我用代码试了下,泛型为Integer时,priList.get(0)正常打印输出,不过当它调用priList.get(0).toString()的时候运行就报类型转换异常了,代码如下,真心不懂这块,快晕了,你不吝赐教啊
System.out.println(priList.get(0));// 正常打印,没有转换异常
System.out.println(priList.get(0).toString());//  wrong,类型转换异常
如果它调用的是toString方法,为什么显式调用运行会出错,隐式调用则OK呢?

还有我在学泛型限定的时候也有一个小疑惑:
泛型限定在参数中传递还OK,我能理解,但是当它调用到方法的时候有个小问题,代码如下:
HashMap<? extends Person,String> hm = new HashMap<Student,String>();//ok
hm.put(key,value);//此时就不ok了,不论是传递person体系中的哪个类
但是在下限时,有这样一个现象:
HashMap<? super Student,String> hm = new HashMap<Student,String>();//ok
hm.put(new Student(),"itcast");//这时候使用Student类型也ok,我就跪了,但只能使用Student类型
辛苦下,给解解惑。。。泛型真心虐我。。。

作者: 邂逅    时间: 2013-12-31 17:22
HashMap<? extends Person,String> hm=new HashMap<Strudent,String>使用了通配符,他起的作用只是能够接受一个在他限定范围内的引用,也就是你可以将一个符合他限定的参数化类型的对象赋值给他,就好像你后面的那个对象的key位置的参数化类型Student符合他的限定你就可以将这个引用赋值给他所声明的对象。但是你不能调用它声明的对象的使用到跟通配符有关系的方法,add(K,V)这个方法就需要根据参数化类型来执行所以不能调用,像size()返回对象是int跟参数化类型无关所以就可以用。
如果通配符限定下限,那么调用的方法跟通配符有关的参数只能是那个下限的类型,使用其他任何类型的方法都报错。
{:soso_e128:}
作者: 邂逅    时间: 2013-12-31 17:25
我刚看了你的突然就怕我忘记了相关知识就赶紧把它写下来了吼吼,你可以参考下
http://blog.csdn.net/u011424451/article/details/17715893
作者: Cheers_for_CRAZ    时间: 2013-12-31 19:01
doitforyou 发表于 2013-12-31 14:42
恩,谢谢,明白了很多。再探讨下,谢谢啊:
我用代码试了下,泛型为Integer时,priList.get(0)正常打印输 ...

我其实也就装装b,没想到还有意外收获。其他问题回答不了,能力有限,这次装过头了!:loveliness:
作者: doitforyou    时间: 2013-12-31 19:16
Cheers_for_CRAZ 发表于 2013-12-31 19:01
我其实也就装装b,没想到还有意外收获。其他问题回答不了,能力有限,这次装过头了! ...

别啊,低调不好,兄弟,你再加加油,我对这个实在是无能为力,想不通啊。。。
你准备什么时候去黑马啊
作者: doitforyou    时间: 2013-12-31 19:20
邂逅 发表于 2013-12-31 17:25
我刚看了你的突然就怕我忘记了相关知识就赶紧把它写下来了吼吼,你可以参考下
http://blog.csdn.net/u01142 ...

我看了你写的,问题没这么简单,如果泛型不是String类型,是Object类型,还是会存在同样的问题,反正越研究越晕,我准备放弃了,去学校问老师吧。。。
作者: Cheers_for_CRAZ    时间: 2013-12-31 19:20
doitforyou 发表于 2013-12-31 19:16
别啊,低调不好,兄弟,你再加加油,我对这个实在是无能为力,想不通啊。。。
你准备什么时候去黑马啊 ...

你想不通我就想通了?我现在都怀疑我以前的思路了。这种问题还是别问了,太祸害人了。等到黑马问大神吧!
以后不能装了,会被雷劈的。
作者: doitforyou    时间: 2013-12-31 19:22
Cheers_for_CRAZ 发表于 2013-12-31 19:20
你想不通我就想通了?我现在都怀疑我以前的思路了。这种问题还是别问了,太祸害人了。等到黑马问大神吧! ...

唉,我也决定放弃了,主要是我每次看泛型就会想到这个问题,难受。
你准备哪一期去啊
作者: Cheers_for_CRAZ    时间: 2013-12-31 19:24
doitforyou 发表于 2013-12-31 19:22
唉,我也决定放弃了,主要是我每次看泛型就会想到这个问题,难受。
你准备哪一期去啊  ...

29吧!博客还没写!你呢?
作者: doitforyou    时间: 2013-12-31 19:29
Cheers_for_CRAZ 发表于 2013-12-31 19:24
29吧!博客还没写!你呢?

不是29就是30,博客写了一部分,准备这个月把流程走完,
感觉在职学习太累了。
一起加油啊,大神。
作者: Cheers_for_CRAZ    时间: 2013-12-31 19:31
doitforyou 发表于 2013-12-31 19:29
不是29就是30,博客写了一部分,准备这个月把流程走完,
感觉在职学习太累了。
一起加油啊,大神。 ...

把那个大改成二还是比较合适的。一起加油吧!
作者: doitforyou    时间: 2013-12-31 19:33
Cheers_for_CRAZ 发表于 2013-12-31 19:31
把那个大改成二还是比较合适的。一起加油吧!

别啊,现在不是以后也是,先封上这个名号,没有退路,哈哈。

作者: Cheers_for_CRAZ    时间: 2013-12-31 19:36
doitforyou 发表于 2013-12-31 19:33
别啊,现在不是以后也是,先封上这个名号,没有退路,哈哈。

毛,先泡个妞才是正道。
作者: doitforyou    时间: 2013-12-31 19:38
Cheers_for_CRAZ 发表于 2013-12-31 19:36
毛,先泡个妞才是正道。

我擦,这个还是不要的好。我有女朋友,没自由啊,唉。
学习都没法专心,你考虑好了啊,小屁孩
作者: Cheers_for_CRAZ    时间: 2013-12-31 19:41
doitforyou 发表于 2013-12-31 19:38
我擦,这个还是不要的好。我有女朋友,没自由啊,唉。
学习都没法专心,你考虑好了啊,小屁孩 ...

你个站着说话不腰疼的主,你有,我没有呀!大神管个屁用,有女朋友才是王道!妈的,长了那么大,连女生的手都没碰过,你是不急。。。:curse:
作者: doitforyou    时间: 2013-12-31 19:47
Cheers_for_CRAZ 发表于 2013-12-31 19:41
你个站着说话不腰疼的主,你有,我没有呀!大神管个屁用,有女朋友才是王道!妈的,长了那么大,连女生的 ...

好吧,恭喜你以后不要后悔啊,哈哈,你的票子会没的,自由会没的,
如果最后没有结婚,还相当于替别人养老婆,嘎嘎嘎嘎,你多多保重。
作者: Cheers_for_CRAZ    时间: 2013-12-31 19:55
不说了,再说版主就封号了。死版主,上次服务器爆了,还欠我5分呢,也不给我加上。你也自求多福吧!
作者: doitforyou    时间: 2013-12-31 19:59
Cheers_for_CRAZ 发表于 2013-12-31 19:55
不说了,再说版主就封号了。死版主,上次服务器爆了,还欠我5分呢,也不给我加上。你也自求多福吧! ...

我靠,你什么RP啊,还会封号,好吧,25分都够了,要技术分没用了,下面学雷锋就好了。
不说了,万一封号我还得重新来过,就悲剧了




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2