泛型、数组和 ClassCastException

发布于 2024-07-10 14:50:14 字数 862 浏览 7 评论 0原文

我想这里一定发生了一些我不知道的微妙事情。 请考虑以下情况:

public class Foo<T> {
  private T[] a = (T[]) new Object[5];

  public Foo() {
    // Add some elements to a
  }

  public T[] getA() {
    return a;
  }
}

假设您的 main 方法包含以下内容:

Foo<Double> f = new Foo<Double>();
Double[] d = f.getA();

您将收到 CastClassException,其中包含消息 java.lang.Object 无法转换为 java.lang .双

谁能告诉我为什么? 我对 ClassCastException 的理解是,当您尝试将对象强制转换为无法强制转换的类型时,会抛出该异常。 也就是说,对于它不是实例的子类(引用文档)。 例如:

Object o = new Double(3.);
Double d = (Double) o; // Working cast
String s = (String) o; // ClassCastException

看来我能做到这一点。 如果 a 只是一个 T 而不是数组 T[],我们可以获取 a 并强制转换它,而无需一个问题。 为什么数组会破坏这个?

谢谢。

I think there must be something subtle going on here that I don't know about. Consider the following:

public class Foo<T> {
  private T[] a = (T[]) new Object[5];

  public Foo() {
    // Add some elements to a
  }

  public T[] getA() {
    return a;
  }
}

Suppose that your main method contains the following:

Foo<Double> f = new Foo<Double>();
Double[] d = f.getA();

You will get a CastClassException with the message java.lang.Object cannot be cast to java.lang.Double.

Can anyone tell me why? My understanding of ClassCastException is that it is thrown when you try to cast an object to a type that cannot be casted. That is, to a subclass of which it is not an instance (to quote the documentation). e.g.:

Object o = new Double(3.);
Double d = (Double) o; // Working cast
String s = (String) o; // ClassCastException

And it seems I can do this. If a was just a T instead of an array T[], we can get a and cast it without a problem. Why do arrays break this?

Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

温柔女人霸气范 2024-07-17 14:50:14
Foo<Double> f = new Foo<Double>();

当您使用此版本的泛型类 Foo 时,对于成员变量 a,编译器实际上会采用以下行:

private T[] a = (T[]) new Object[5];

并将 T 替换为 Double 得到这个:

private Double[] a = (Double[]) new Object[5];

你不能从 Object 转换为 Double,因此会出现 ClassCastException。

更新和说明:实际上,在运行一些测试代码后,ClassCastException 比这更微妙。 例如,这个 main 方法将正常工作,没有任何异常:

public static void main(String[] args) {
    Foo<Double> f = new Foo<Double>();
    System.out.println(f.getA());
}

就会出现问题:

public static void main(String[] args) {
    Foo<Double> f = new Foo<Double>();
    Double[] a2 = f.getA(); // throws ClassCastException
    System.out.println(a2);
}

当您尝试将 f.getA() 分配给 Double[] 类型的引用时, 是因为成员变量a的类型信息在运行时被删除。 泛型仅在编译时提供类型安全(我在最初的帖子中以某种方式忽略了这一点)。 所以问题并不是

private T[] a = (T[]) new Object[5];

因为在运行时这段代码实际上是

private Object[] a = new Object[5];

方法 getA() 的结果时出现的问题,该方法在运行时实际上返回一个 Object[],被分配给类型 Double[] 的引用 - 此语句会抛出 ClassCastException,因为 Object 无法转换为 Double。

更新 2:回答最后一个问题“为什么数组会破坏这个?” 答案是因为语言规范不支持通用数组创建。 请参阅此论坛帖子了解更多信息 - 为了向后兼容,什么都不知道关于运行时 T 的类型。

Foo<Double> f = new Foo<Double>();

When you use this version of the generic class Foo, then for the member variable a, the compiler is essentially taking this line:

private T[] a = (T[]) new Object[5];

and replacing T with Double to get this:

private Double[] a = (Double[]) new Object[5];

You cannot cast from Object to Double, hence the ClassCastException.

Update and Clarification: Actually, after running some test code, the ClassCastException is more subtle than this. For example, this main method will work fine without any exception:

public static void main(String[] args) {
    Foo<Double> f = new Foo<Double>();
    System.out.println(f.getA());
}

The problem occurs when you attempt to assign f.getA() to a reference of type Double[]:

public static void main(String[] args) {
    Foo<Double> f = new Foo<Double>();
    Double[] a2 = f.getA(); // throws ClassCastException
    System.out.println(a2);
}

This is because the type-information about the member variable a is erased at runtime. Generics only provide type-safety at compile-time (I was somehow ignoring this in my initial post). So the problem is not

private T[] a = (T[]) new Object[5];

because at run-time this code is really

private Object[] a = new Object[5];

The problem occurs when the result of method getA(), which at runtime actually returns an Object[], is assigned to a reference of type Double[] - this statement throws the ClassCastException because Object cannot be cast to Double.

Update 2: to answer your final question "why do arrays break this?" The answer is because the language specification does not support generic array creation. See this forum post for more - in order to be backwards compatible, nothing is known about the type of T at runtime.

病女 2024-07-17 14:50:14

@mattb 的解释可能存在一些小错误。

错误不是

java.lang.Object 无法转换为 java.lang.Double。

这是:

[Ljava.lang.Object; 无法转换为 [Ljava.lang.Double

[L 表示数组。 也就是说,错误在于对象的数组无法转换为 Double 数组。 这和下面的情况是一样的:

Object[] oarr = new Object[10];
Double[] darr = (Double[]) oarr;

这显然是不允许的。

对于创建类型安全数组的问题,另一种选择是在 init 中排除类对象并使用 Array.newInstance:

import java.lang.reflect.Array;

class Foo<T> {
  private T[] a ;

  public Foo(Class<T> tclass) {
    a = (T[]) Array.newInstance(tclass, 5);
  }

  public T[] getA() {
    return a;
  }

  public static <T> Foo<T> create(Class<T> tclass) {
    return new Foo<T>(tclass);
  }
}

class Array1
{
  public static final void main(final String[] args) {
    Foo<Double> f = Foo.create(Double.class);
    Double[] d = f.getA();
  }


}

There may be some small errors in @mattb's explanation.

The error is not

java.lang.Object cannot be cast to java.lang.Double.

It is:

[Ljava.lang.Object; cannot be cast to [Ljava.lang.Double

The [L means an array. That is, the error is that an array of Objects cannot be cast to an array of Double. This is the same case as following:

Object[] oarr = new Object[10];
Double[] darr = (Double[]) oarr;

This is obviously not allowed.

For your issue of creating typesafe arrays, another alternative is to except a class object in init and use Array.newInstance:

import java.lang.reflect.Array;

class Foo<T> {
  private T[] a ;

  public Foo(Class<T> tclass) {
    a = (T[]) Array.newInstance(tclass, 5);
  }

  public T[] getA() {
    return a;
  }

  public static <T> Foo<T> create(Class<T> tclass) {
    return new Foo<T>(tclass);
  }
}

class Array1
{
  public static final void main(final String[] args) {
    Foo<Double> f = Foo.create(Double.class);
    Double[] d = f.getA();
  }


}
失退 2024-07-17 14:50:14

@matt b:谢谢你的回答! 很有帮助。

我为感兴趣的人找到了一种解决方法:为 getA 方法提供一个要填充的初始化数组。 这样类型信息就可用了。

public class Foo<T> {
  private T[] a = (T[]) new Object[5];

  public Foo() {
    // Add some elements to a
  }

  public T[] getA(T[] holdA) {
    // Check whether holdA is null and handle it...then:
    holdA = (T[]) Array.newInstance(holdA.getClass().getComponentType(), a.length);
    System.arraycopy(a, 0, holdA, 0, a.length);
    return holdA;
  }
}

然后对于你的主要方法:

public static void main(String[] args) {
  Foo<Double> f = new Foo<Double>();
  Double[] a2 = new Double[1];
  a2 = f.getA(a2);
}

@matt b: Thanks for the answer! Very helpful.

I have found a workaround for those interested: give the getA method an initialized array to populate. That way the type info is available.

public class Foo<T> {
  private T[] a = (T[]) new Object[5];

  public Foo() {
    // Add some elements to a
  }

  public T[] getA(T[] holdA) {
    // Check whether holdA is null and handle it...then:
    holdA = (T[]) Array.newInstance(holdA.getClass().getComponentType(), a.length);
    System.arraycopy(a, 0, holdA, 0, a.length);
    return holdA;
  }
}

Then for your main method:

public static void main(String[] args) {
  Foo<Double> f = new Foo<Double>();
  Double[] a2 = new Double[1];
  a2 = f.getA(a2);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文