`
悲剧了
  • 浏览: 143819 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

泛型理解上的一个问题

 
阅读更多
请帮忙解释下这个泛型问题,具体逻辑我都下在代码注释里面了
public class Test01 {
	public static void main(String[] args) throws Exception{
		ArrayList<Integer> arr1=new ArrayList<Integer>();
		ArrayList<String> arr2=new ArrayList<String>();
		//下面打印出来true,证明泛型只是编译器级别的一个东西,加载到内存还是一样的
		System.out.println(arr1.getClass()==arr2.getClass());
		//那么可以用跳过编译器用反射直接加入不通类型的东西,测试打印出"dodo"字符串
		arr1.add(55);
		arr1.getClass().getMethod("add", Object.class).invoke(arr1, "dodo");
		System.out.println(arr1.get(1));
		//既然如此那么下面这个也应该能正确打印,但是报异常异常为: java.lang.ClassCastException
		arr2.add("why");
		arr2.getClass().getMethod("add", Object.class).invoke(arr2, 33);
		System.out.println(arr2.get(1));
	}

}

分享到:
评论
38 楼 mtnt2008 2010-11-09  
yunzhiyifeng 写道
这应该是一个println重载的问题,并不是反射引起的classcastexception,
System.out.println(arr1.get(1));   //此时调用的是println(Object x)的方法,看代码:
public void println(Object x) {
        String s = String.valueOf(x);
        synchronized (this) {
            print(s);
            newLine();
},这里会显示的调用valueOf()方法,所以在print的时候,都会调用该对象自身的toString,不会出异常。而System.out.println(arr2.get(1));此时arr2.get(1)的编译类型是String,所以调用的是println(String x),此时就不会做toString的操作,直接作为了String对象,所以编译器会产生checkcast语句,看arr2.get(1)能否转换为String,而此时arr2.get(1)是Integer,做类型转换的时候便会报错。


+1

范型是在编译的时候确定的,所以调用的是println(String x)方法,抛出classcastexception
37 楼 xiangdefei 2010-11-08  
擦除。。。。
36 楼 fengchong719 2010-11-08  
麻烦大家去看一看。
ArrayList<String> arr1 = new ArrayList<String>();
ArrayList<Integer> arr2 = new ArrayList<Integer>();
System.out.println(arr1.getClass());
System.out.println(arr2.getClass());
返回的是什么?
这个问题,我想不用我多说了。
35 楼 悲剧了 2010-11-08  
coffeesweet 写道
悲剧了 写道
请帮忙解释下这个泛型问题,具体逻辑我都下在代码注释里面了
public class Test01 {
	public static void main(String[] args) throws Exception{
		ArrayList<Integer> arr1=new ArrayList<Integer>();
		ArrayList<String> arr2=new ArrayList<String>();
		//下面打印出来true,证明泛型只是编译器级别的一个东西,加载到内存还是一样的
		System.out.println(arr1.getClass()==arr2.getClass());
		//那么可以用跳过编译器用反射直接加入不通类型的东西,测试打印出"dodo"字符串
		arr1.add(55);
		arr1.getClass().getMethod("add", Object.class).invoke(arr1, "dodo");
		System.out.println(arr1.get(1));
		//既然如此那么下面这个也应该能正确打印,但是报异常异常为: java.lang.ClassCastException
		arr2.add("why");
		arr2.getClass().getMethod("add", Object.class).invoke(arr2, 33);
		System.out.println(arr2.get(1));
	}

}



给你个终结贴吧:

请查看如下我修改的代码:

public class Test01 {   
    public static void main(String[] args) throws Exception{   
        ArrayList<Integer> arr1=new ArrayList<Integer>();   
        ArrayList<String> arr2=new ArrayList<String>();   
        //下面打印出来true,证明泛型只是编译器级别的一个东西,加载到内存还是一样的   
        System.out.println(arr1.getClass()==arr2.getClass());   
        //那么可以用跳过编译器用反射直接加入不通类型的东西,测试打印出"dodo"字符串   
        arr1.add(55);   
        arr1.getClass().getMethod("add", Object.class).invoke(arr1, "dodo");   
        System.out.println(arr1.get(1));   
        //既然如此那么下面这个也应该能正确打印,但是报异常异常为: java.lang.ClassCastException   
        arr2.add("why");
        arr2.getClass().getMethod("add", Object.class).invoke(arr2, 33);   
//        System.out.println(arr2.get(1));//这里其实和add的道理一样,还是没有绕过编译时泛型的检查
        //如果你add的时候用反射绕过了,get的时候为什么不绕呢??
        
        //arr1之所以可以是因为编译器自动将Integer看作Object自动调用了println(Object obj)方法,其实还是没有
        //绕过编译时的泛型定义,只是有对应的println方法罢了,还是检查了<Integer>了的。pringln的时候会
        //直接调用println(Object obj)
        //arr2同样会检查泛型类型,一看是<String>,那么就会找println(String s)方法,因为存在这个方法,所以
        //直接调用了println(String s)该方法,这就是为什么arr2的out.pringln会调用打印String的方法,而不是
        //打印Object的方法了,而调用String的pringln的方法必然会作(String)强转了。
        
        //下面是get的时候也通过反射来绕过编译时泛型检查,肯定是没问题的。
        //呵呵,没有任何矛盾,不明白请跟帖,我回回复。
        Object obj1=arr2.getClass().getMethod("get", int.class).invoke(arr2, 1);
        System.out.println(obj1);
        
    }   
  
}  



//呵呵,没有任何矛盾,不明白请跟帖,我回回复。

谢谢,分析得很好很清晰!
34 楼 fengchong719 2010-11-08  
只能说你们的回答太肤浅了。
ArrayList<Integer> arr1 =new ArrayList<Integer>();
		arr1.add(11);
		System.out.println(arr1.getClass());

多说无益,自己看看这个之后就知道是怎么回事了。
望大虾们,回贴时注意份量,不要影响我们这些菜鸟。谢谢
33 楼 coffeesweet 2010-11-08  
悲剧了 写道
请帮忙解释下这个泛型问题,具体逻辑我都下在代码注释里面了
public class Test01 {
	public static void main(String[] args) throws Exception{
		ArrayList<Integer> arr1=new ArrayList<Integer>();
		ArrayList<String> arr2=new ArrayList<String>();
		//下面打印出来true,证明泛型只是编译器级别的一个东西,加载到内存还是一样的
		System.out.println(arr1.getClass()==arr2.getClass());
		//那么可以用跳过编译器用反射直接加入不通类型的东西,测试打印出"dodo"字符串
		arr1.add(55);
		arr1.getClass().getMethod("add", Object.class).invoke(arr1, "dodo");
		System.out.println(arr1.get(1));
		//既然如此那么下面这个也应该能正确打印,但是报异常异常为: java.lang.ClassCastException
		arr2.add("why");
		arr2.getClass().getMethod("add", Object.class).invoke(arr2, 33);
		System.out.println(arr2.get(1));
	}

}



给你个终结贴吧:

请查看如下我修改的代码:

public class Test01 {   
    public static void main(String[] args) throws Exception{   
        ArrayList<Integer> arr1=new ArrayList<Integer>();   
        ArrayList<String> arr2=new ArrayList<String>();   
        //下面打印出来true,证明泛型只是编译器级别的一个东西,加载到内存还是一样的   
        System.out.println(arr1.getClass()==arr2.getClass());   
        //那么可以用跳过编译器用反射直接加入不通类型的东西,测试打印出"dodo"字符串   
        arr1.add(55);   
        arr1.getClass().getMethod("add", Object.class).invoke(arr1, "dodo");   
        System.out.println(arr1.get(1));   
        //既然如此那么下面这个也应该能正确打印,但是报异常异常为: java.lang.ClassCastException   
        arr2.add("why");
        arr2.getClass().getMethod("add", Object.class).invoke(arr2, 33);   
//        System.out.println(arr2.get(1));//这里其实和add的道理一样,还是没有绕过编译时泛型的检查
        //如果你add的时候用反射绕过了,get的时候为什么不绕呢??
        
        //arr1之所以可以是因为编译器自动将Integer看作Object自动调用了println(Object obj)方法,其实还是没有
        //绕过编译时的泛型定义,只是有对应的println方法罢了,还是检查了<Integer>了的。pringln的时候会
        //直接调用println(Object obj)
        //arr2同样会检查泛型类型,一看是<String>,那么就会找println(String s)方法,因为存在这个方法,所以
        //直接调用了println(String s)该方法,这就是为什么arr2的out.pringln会调用打印String的方法,而不是
        //打印Object的方法了,而调用String的pringln的方法必然会作(String)强转了。
        
        //下面是get的时候也通过反射来绕过编译时泛型检查,肯定是没问题的。
        //呵呵,没有任何矛盾,不明白请跟帖,我回回复。
        Object obj1=arr2.getClass().getMethod("get", int.class).invoke(arr2, 1);
        System.out.println(obj1);
        
    }   
  
}  



//呵呵,没有任何矛盾,不明白请跟帖,我回回复。
32 楼 liusu 2010-11-08  
// Decompiled by DJ v3.6.6.79 Copyright 2004 Atanas Neshkov  Date: 2010-11-08 9:27:39
// Home Page : http://members.fortunecity.com/neshkov/dj.html  - Check often for new version!
// Decompiler options: packimports(3) 
// Source File Name:   Test.java

package com.paic;

import java.io.PrintStream;
import java.lang.reflect.Method;
import java.util.ArrayList;

public class Test
{

    public Test()
    {
    }

    public static void main(String args[])
        throws Exception
    {
        ArrayList arr1 = new ArrayList();
        ArrayList arr2 = new ArrayList();
        System.out.println(arr1.getClass() == arr2.getClass());
        arr1.add(Integer.valueOf(55));
        arr1.getClass().getMethod("add", new Class[] {
            java/lang/Object
        }).invoke(arr1, new Object[] {
            "dodo"
        });
        System.out.println(arr1.get(1));
        arr2.add("why");
        arr2.getClass().getMethod("add", new Class[] {
            java/lang/Object
        }).invoke(arr2, new Object[] {
            Integer.valueOf(33)
        });
        System.out.println((String)arr2.get(1)); //看这一行
    }
}


这怎么能是System.out.println()的错误呢?
之前朋友给的反编译的代码我觉得就可以完全解释了。 编译后的代码是:
System.out.println((String)arr2.get(1)); //看这一行
这句话Run的时候肯定就ClassCastException了。

虽然你在源码里面是写 arr2.get(1), 但是因为泛型的关系,编译后的代码给做了强制类型转换了。。。
31 楼 wjjxf 2010-11-08  
原来是System.out.println();造成的,以后遇到问题,一步步调试很是很必要的
再说,ArrayList里存放数据用的Object数组不是T数组。。
30 楼 ld57601870 2010-11-08  
(1)对于arr1
就相当于Object o = arr1.get(0);
(2)对于arr2
就相当于String obj2 = arr2.get(0);(报错)
29 楼 悲剧了 2010-11-08  
jwnest 写道
最后一句改成
System.out.println((Object)arr2.get(1));
就不会报错,正常打印出来33了。

所以说,编译器其实是对你最后一句print语句进行了优化而已,你可以看看jdk源码里面println的实现。并没有所谓的println(Integer x)这种实现,所有对象类型的都是调用println(Object x),除了String类型有自己的实现println(String x)。




谢谢分析!
总算弄明白了,原来是这么个原因.
看来可以结贴了.
28 楼 jwnest 2010-11-08  
最后一句改成
System.out.println((Object)arr2.get(1));
就不会报错,正常打印出来33了。

所以说,编译器其实是对你最后一句print语句进行了优化而已,你可以看看jdk源码里面println的实现。并没有所谓的println(Integer x)这种实现,所有对象类型的都是调用println(Object x),除了String类型有自己的实现println(String x)。



27 楼 chen672671 2010-11-08  
qianhd 写道
你们的瞎猜真没创意

改成 ArrayList arr2=new ArrayList<String>();  

就不会有异常
至于为什么 回去慢慢想

这根本就不是泛型了:
ArrayList arr2=new ArrayList<String>();  
arr2.add(123);
不会出错的!
26 楼 forestking 2010-11-08  
悲剧了 写道
forestking 写道
arr2.add("why");
arr2.getClass().getMethod("add", Object.class).invoke(arr2, 33);
Object obj2 = arr2.get(1);
// Class clazz2 = obj2.getClass();
System.out.println(obj2); //#1
System.out.println(arr2.get(1));//#2
#1 是可以的, 但是#2是不能运行的。
按F3进入#1 println(),调用的是println(Object), 而#2调用的是println(String), 那这个问题就解决了,事实上正如yunzhiyifeng所说的,这个是println()的重载造成的。


谢谢分析,想问下:
从哪得知arr2.get(1)是String?
如果分析正确,那arr1.get(1)应该是print(Integer),作为String类型调用它怎么打印不报异常呢?而且arr1.get(1)还打印出来了?

System.out.println() 是没有Integer作为参数的重载的,
System.out.println(new Integer(2)) 事实上也是调用的println(Object).
25 楼 crud0906 2010-11-08  
按住ctrl键,点击第一个System.out.println(arr1.get(1));发现进入的是
public void println(Object x) {
synchronized (this) {
    print(x);
    newLine();
}
    }
点击第二个System.out.println(arr2.get(1));发现进入的是
public void println(String x) {
synchronized (this) {
    print(x);
    newLine();
}
    }
因为定义ArrayList的时候已经知道arr.get()的返回值类型,所以还是楼上所说的重载问题造成的
24 楼 悲剧了 2010-11-08  
forestking 写道
arr2.add("why");
arr2.getClass().getMethod("add", Object.class).invoke(arr2, 33);
Object obj2 = arr2.get(1);
// Class clazz2 = obj2.getClass();
System.out.println(obj2); //#1
System.out.println(arr2.get(1));//#2
#1 是可以的, 但是#2是不能运行的。
按F3进入#1 println(),调用的是println(Object), 而#2调用的是println(String), 那这个问题就解决了,事实上正如yunzhiyifeng所说的,这个是println()的重载造成的。


谢谢分析,想问下:
从哪得知arr2.get(1)是String?
如果分析正确,那arr1.get(1)应该是print(Integer),作为String类型调用它怎么打印不报异常呢?而且arr1.get(1)还打印出来了?
23 楼 forestking 2010-11-08  
arr2.add("why");
arr2.getClass().getMethod("add", Object.class).invoke(arr2, 33);
Object obj2 = arr2.get(1);
// Class clazz2 = obj2.getClass();
System.out.println(obj2); //#1
System.out.println(arr2.get(1));//#2
#1 是可以的, 但是#2是不能运行的。
按F3进入#1 println(),调用的是println(Object), 而#2调用的是println(String), 那这个问题就解决了,事实上正如yunzhiyifeng所说的,这个是println()的重载造成的。

22 楼 lonelythinker 2010-11-08  
先看看啊

21 楼 yunzhiyifeng 2010-11-08  
这应该是一个println重载的问题,并不是反射引起的classcastexception,
System.out.println(arr1.get(1));   //此时调用的是println(Object x)的方法,看代码:
public void println(Object x) {
        String s = String.valueOf(x);
        synchronized (this) {
            print(s);
            newLine();
},这里会显示的调用valueOf()方法,所以在print的时候,都会调用该对象自身的toString,不会出异常。而System.out.println(arr2.get(1));此时arr2.get(1)的编译类型是String,所以调用的是println(String x),此时就不会做toString的操作,直接作为了String对象,所以编译器会产生checkcast语句,看arr2.get(1)能否转换为String,而此时arr2.get(1)是Integer,做类型转换的时候便会报错。
20 楼 liwen19840617 2010-11-08  
System.out.println("arr2中第0个元素是:"+arr2.get(0));
19 楼 forestking 2010-11-08  
forestking 写道
francis.xjl 写道
下面是我的猜想,仅供参考
再看了一下,我觉得应该是编译器进行了优化,跳过了一些步骤。
我们来读ArrayList的源码,它的add跟get方法是这样的:
public boolean add(E e) {
	ensureCapacity(size + 1);  // Increments modCount!!
	elementData[size++] = e;
	return true;
    }

public E get(int index) {
	RangeCheck(index);

	return (E) elementData[index];
    }

因此,你如果在ArrayList<String>运行时插入一个Integer类型的数据,由于范型只存在编译期的缘故,而且add方法没有对类型进行检查,因此,可以成功。但是,当你想取出这个对象时,就有一个问题:get方法中有一个强制转换,按照道理是会抛出异常的,如果在你原来的程序中加入这么一句试一下就知道了:
// add by francis.xjl
        System.out.println(arr1.get(1).getClass()); 

因此,其实抛出异常是正常的。

那为什么直接输出arr1.get(1)就可以呢?我估计是这样的:我们知道打印一个对象其实就是调用这个对象的toString()方法,而toString()方法是Object类型中的,因此,可能编译器觉得这里的强制转换没有必要,因此给省略了这个步骤。

因此,我觉得String类型的ArrayList会抛出异常可能是编译器考虑到其它的一些原因而没有进行优化。

当然,这仅仅是我的猜想,仅供参考


这个应该是正解。。。。


测试了一下,如果把代码改成下面的样子,是可以通过的:
                ArrayList<Integer> arr1 = new ArrayList<Integer>();
ArrayList<String> arr2 = new ArrayList<String>();
System.out.println(arr1.getClass() == arr2.getClass());
arr1.add(55);
arr1.getClass().getMethod("add", Object.class).invoke(arr1, "dodo");
Object obj = arr1.get(1);
Class clazz = obj.getClass();
System.out.println(clazz);
arr2.add("why");
arr2.getClass().getMethod("add", Object.class).invoke(arr2, 33);
Object obj2 = arr2.get(1);
Class clazz2 = obj2.getClass();
System.out.println(clazz2);

输出结果是:
true
class java.lang.String
class java.lang.Integer

所以上面的分析似乎没有解决这个问题。

相关推荐

    C#泛型类、泛型方法、泛型接口、泛型委托的实例

    泛型类是具有一个或多个类型参数的类。类型参数是在定义类时使用的占位符,实际的类型在创建类的实例时指定。例如,我们可以创建一个名为`GenericContainer&lt;T&gt;`的泛型类,其中`T`就是类型参数。这个类可以存储任何...

    C#泛型学习和理解代码示例

    C#泛型是.NET框架中的一个强大特性,它允许我们创建可重用的类型,这些类型可以在多种数据类型上工作,而无需重复编写相同代码。泛型的主要目标是提高代码的类型安全性和性能,同时减少类型转换的需要。在本文中,...

    java 带两个类型参数的泛型

    当我们谈论“java带两个类型参数的泛型”时,这意味着我们正在处理一个泛型类或泛型方法,它们接受不止一个类型作为参数。这样的设计可以让我们在不同数据类型之间建立更复杂的关联,同时保持类型安全。 首先,让...

    JVM如何理解Java泛型类.doc

    为了理解JVM如何处理泛型,我们需要了解一个重要的概念:“类型擦除”(Type Erasure)。类型擦除是指编译器在编译过程中去除所有泛型信息的过程。这一过程使得JVM不必直接支持泛型,因为所有泛型信息在编译后都被...

    java 一个关于泛型的简单例子

    此外,文件名`Java.jpg`可能表示的是一个与这个泛型例子相关的图片,可能是一个截图或者示意图,用于帮助理解泛型的概念或者`MyFirstGeneric`类的结构和用法。在实际学习过程中,视觉辅助工具往往能够帮助我们更好地...

    C# 泛型集合 C#初学者的一个适用案例

    综上所述,理解和熟练运用C#中的泛型集合对于任何C#初学者来说都是至关重要的。通过掌握List、Dictionary, TValue&gt;等泛型集合的使用,以及类型约束和泛型接口的概念,开发者能够编写出更安全、高效且易于维护的代码...

    C#泛型C#泛型C#泛型

    这个声明定义了一个泛型类 List,其中 T 是一个类型参数。这个泛型类可以实例化为不同的类型,例如 List、List&lt;string&gt; 等。 2. 类型参数 类型参数是一个简单的标识符,它指示了用来创建一个构造类型的类型参数的...

    重新理解Java泛型

    Java泛型是Java编程语言中的一个重要特性,它允许在类、接口和方法中使用类型参数,从而提高了代码的类型安全性和可复用性。本文旨在深入解析Java泛型的各个方面,帮助开发者形成全面、清晰的理解。 1. **泛型基础*...

    C# 泛型深入理解介绍

    泛型是C#编程语言中的一个重要特性,自C# 2.0版本开始引入,它极大地提升了代码的重用性和效率。本专题将深入探讨泛型的原理和优势,以及如何利用它们来优化代码。 首先,泛型允许我们在定义类、接口、委托和结构时...

    泛型java的泛型知识,非常有用

    例如,可以定义一个泛型方法,使其能接受不同类型的参数并返回不同类型的值。 9. **类型推断** - 自JDK 7起,Java引入了类型推断,允许在某些情况下省略类型参数,编译器会根据上下文自动推断类型。 理解并熟练...

    java泛型指南 经典

    泛型类允许我们在定义类时指定一个或多个类型参数。例如,我们可以定义一个简单的泛型类 `Box`: ```java public class Box&lt;T&gt; { private T item; public void setItem(T item) { this.item = item; } public...

    C#泛型,非泛型实现枚举

    例如,`List&lt;T&gt;`就是一个泛型类,其中`T`代表一个未指定的类型,实际使用时可以是整型、字符串或其他自定义类型。 当我们想要在泛型中实现枚举功能时,可能的场景是在一个集合中存储一组特定类型的枚举值。例如,...

    3个泛型项目源码.rar

    这个"3个泛型项目源码.rar"压缩包包含的三个项目,无疑为学习和理解泛型在实际项目中的应用提供了宝贵的资源。 泛型是C# 2.0引入的新特性,它允许开发者创建可以操作多种数据类型的类、接口和方法。这样做的好处...

    泛型用在类和方法上的写法

    泛型是Java编程语言中的一个重要特性,它引入了类型安全的概念,使得在编译时就能检查类型错误,而不是等到运行时。在类和方法上使用泛型,可以增强代码的可读性和复用性,避免类型转换的繁琐,并且提高了程序的效率...

    Jdk15泛型的实现

    例如,如果一个泛型类声明了类型参数`T extends Comparable&lt;T&gt;`,那么`T`必须是一个实现了`Comparable`接口的类型。这确保了使用该泛型类时,其内部操作能够正确地进行比较和排序。 自定义泛型类或算法时,开发者...

    VC++ 2005:泛型编程

    泛型编程的核心思想是参数化类型,即将数据类型作为一个参数传递给代码,使得代码能够适应不同类型的输入。 C++/CLI支持两种泛型机制:编译时泛型(基于ISO-C++模板)和运行时泛型(基于CLI泛型)。编译时泛型类似...

    泛型封装.rar

    在C#编程中,泛型是一种强大的特性,它允许我们创建可重用的类型安全的代码,能够处理多种数据类型。"泛型封装.rar"这个...通过深入研究这个例子,开发者可以更好地理解和掌握C#中的泛型技术,提升代码质量和效率。

Global site tag (gtag.js) - Google Analytics