【问题标题】:Weird runtime error (ClassCastException while declaring)奇怪的运行时错误(声明时出现 ClassCastException)
【发布时间】:2012-01-09 19:13:40
【问题描述】:

我正在制作的程序中有以下代码:

01  public class Clazz<T>
02  {
03    T[] t;
04    
05    public Clazz<T> methodA(int... ints)
06    {
07      Clazz<Integer> ints2 = new Clazz<>();
08      int remInd[] = new int[t.length - ints2.t.length];
09      return this;
10    }
11  }

但是当我运行方法 methodA 时,我得到了这个错误:

Exception in thread "AWT-EventQueue-0" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
    at Clazz.methodA(Clazz.java:8)

为什么会出现此错误?当然,与所讨论的巨大类相比,我展示的代码是不完整的(例如,数组t 在检查其长度时不会为空),但我相信我已经展示了所有重要的东西。为什么不运行?

注意:我是用 JDK 1.7 做这个的,所以这就是第 7 行编译和工作的原因

工作解决方案


无论出于何种原因,我决定实施以下解决方案,并且成功了:

01  public class Clazz<T>
02  {
03    T[] t;
04    
05    public Clazz<T> methodA(int... ints)
06    {
07      Clazz<Integer> ints2 = new Clazz<>();
08      int remInd[] = new int[t.length - ints2.length()];
09      return this;
10    }
11    
12    public int length()
13    {
14      return t.length;
15    }
16  }

虽然这是一个解决方案,但我仍然想知道它为什么有效。

【问题讨论】:

  • 您缺少部分代码,即初始化 t 的位置。这很重要。
  • 第 7 行真的可以编译吗?
  • 就像我说的,这是 841 行类的 11 行版本。如果我向您展示发生的一切,这个示例将比需要的更大。只要确保一切都编译,并且 t 总是被初始化(我有几个构造函数,所有这些都确保了这一点)。另请参阅最新编辑
  • @Supuhstar:我说具体的事情很重要,因为具体的事情确实很重要。我没有要求其他 829 行,只是那一个特定的行。您如何初始化t 决定了您是否会收到该错误。在这种情况下,我不得不假设您使用 Object[] 对其进行初始化。
  • @Supuhstar:在尝试访问阵列时,您仍然可能在其他点遇到麻烦。您可能会考虑在我的回答中采纳建议,而根本不声明通用数组。他们从来都不是一个好主意。如果您有一个由数组支持的泛型类,那么该数组应该 100% 封装,在这种情况下,没有理由不使用 Object[]。关于为什么您的“解决方案”有效,我在回复@DHall 对我的回答的评论时解释了这一点。

标签: java classcastexception


【解决方案1】:

您缺少初始化T 的代码,但我假设它看起来像这样。我添加了几行不会更改任何功能但有助于演示错误的行:

public class Clazz<T> {
    T[] t = (T[]) new Object[5];

    public Clazz<T> methodA(int... ints) {
        Clazz<Integer> ints2 = new Clazz<Integer>();
        int l1 = t.length;
        int l2 = ints2.t.length;
        int remInd[] = new int[l1 - l2];
        return this;
    }

    public static void main(String...args) {
        Clazz<String> clazz = new Clazz<String>();
        clazz.methodA(54, 7);
    }
}

使用此代码,我可以重现该错误。这里的问题出在这段代码中:

int l2 = ints2.t.length

由于编译器知道ints2ints2.t 的类型参数,因此可以认为这大致相当于:

Integer[] temp = ints2.t;
int l2 = temp.length;

这是在隐式转换为Integer[](其类简单名称为[Ljava.lang.Integer)时失败的,因为tObject[] 而不是Integer[],并且不能转换为其他。

使用泛型数组

使用在其他地方记录的泛型类型声明的数组有许多复杂性。简而言之,我会说,如果您需要一个“通用数组”而不是考虑以各种方式声明并使用它作为 Object[]除了,当您与类,您要么接受要么只返回 T 而不是 Object (用于返回,通过未经检查的演员表)。例如,

Object[] t = new Object[5];

public T getSomethingFromArray() {
    return (T)t[2];
}

public void setSomethingInArray(T something) {
    t[2] = something;
}

顺便说一句,这就是ArrayList 的工作方式。看看its code on DocJar

编辑

除了通用数组,我认为您不理解隐式转换的想法。下面是更短的代码,但失败并出现基本相同的错误:

public class Clazz<T> {
    T t = (T) new Object();

    public static void main(String...args) {
        Clazz<String> clazz = new Clazz<String>();
        clazz.t.toString();
    }
}

Exception in thread "main" java.lang.ClassCastException: java.lang.Object cannot be cast to java.lang.String
    at Clazz.main(Clazz.java:6)
    ...

即使不需要将clazz.t 转换为字符串,它也隐式地通过引用 clazz.t 来实现。这是该编译类的javap -c 输出:

Compiled from "Clazz.java"
public class Clazz extends java.lang.Object{
java.lang.Object t;

public Clazz();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   aload_0
   5:   new #2; //class java/lang/Object
   8:   dup
   9:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   12:  putfield    #3; //Field t:Ljava/lang/Object;
   15:  return

public static void main(java.lang.String[]);
  Code:
   0:   new #4; //class Clazz
   3:   dup
   4:   invokespecial   #5; //Method "<init>":()V
   7:   astore_1
   8:   aload_1
   9:   getfield    #3; //Field t:Ljava/lang/Object;
//BELOW is the line that will fail
   12:  checkcast   #6; //class java/lang/String
   15:  invokevirtual   #7; //Method java/lang/String.toString:()Ljava/lang/String;
   18:  pop
   19:  return

}

对于您的原始代码,这里是javap -cmethodA() 输出:

public Clazz methodA(int[]);
  Code:
   0:   new #5; //class Clazz
   3:   dup
   4:   invokespecial   #6; //Method "<init>":()V
   7:   astore_2
   8:   aload_0
   9:   getfield    #4; //Field t:[Ljava/lang/Object;
   12:  arraylength
   13:  aload_2
   14:  getfield    #4; //Field t:[Ljava/lang/Object;
//BELOW is the line that will fail
   17:  checkcast   #7; //class "[Ljava/lang/Integer;"
   20:  arraylength
   21:  isub
   22:  newarray int
   24:  astore_3
   25:  aload_0
   26:  areturn

【讨论】:

  • 这对每个数组的 .length 对象有何影响?不是一直都是int吗? ints2.t.length 怎么了?
  • @Supuhstar:结果是一个 int,但在编译器之间必须使用包含隐式转换的引用 ints2.t。我相信无论你在获得参考之后做了什么,它都会失败。你可以做ints2.t.toString(),同样会失败。
  • @DHall:在这种情况下,隐式转换不存在,因为您在 in 通用类实例中,所有类型信息都被擦除(T 被擦除为 @ 987654351@)。因此它只会尝试隐式转换为Object[]
  • 至于你建议t 成为Object[],恐怕不行,因为我做了很多clazzObj.t instanceof Clazz2
  • @Supuhstar:我不确定你认为问题出在哪里...t instanceof Clazz2 当然总是会失败,因为t 是一个数组,但不管你声明什么 一个变量(在本例中为t),因为它永远不会影响其运行时类型,这将通过instanceof 进行检查。
猜你喜欢
  • 2014-09-08
  • 2011-02-07
  • 2017-02-14
  • 1970-01-01
  • 2014-12-20
  • 2017-01-03
  • 2016-07-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多