Обобщения, массивы и исключение ClassCastException - PullRequest
15 голосов
/ 16 декабря 2008

Я думаю, что здесь должно быть что-то тонкое, о чем я не знаю. Учтите следующее:

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

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

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

Предположим, что ваш основной метод содержит следующее:

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

Вы получите CastClassException с сообщением java.lang.Object не может быть приведено к java.lang.Double.

Может кто-нибудь сказать мне, почему? Мое понимание ClassCastException заключается в том, что он выбрасывается, когда вы пытаетесь привести объект к типу, который не может быть приведен. То есть для подкласса, экземпляром которого он не является (цитируя документацию). e.g.:

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

И, похоже, я могу это сделать. Если a был просто T вместо массива T[], мы можем получить a и привести его без проблем. Почему массивы ломают это?

Спасибо.

Ответы [ 3 ]

20 голосов
/ 16 декабря 2008
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 является более тонким, чем этот. Например, этот основной метод будет работать без каких-либо исключений:

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

Проблема возникает при попытке присвоить f.getA() ссылку типа Double[]:

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

Это связано с тем, что информация о типе переменной-члена a стирается во время выполнения. Обобщения обеспечивают безопасность типов только во время компиляции (я как-то игнорировал это в своем первоначальном посте). Так что проблема не в

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

потому что во время выполнения этот код действительно

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

Проблема возникает, когда результат метода getA(), который во время выполнения фактически возвращает Object[], присваивается ссылке типа Double[] - этот оператор генерирует исключение ClassCastException, поскольку Object не может быть приведен к Double.

Обновление 2 : чтобы ответить на ваш последний вопрос "почему массивы ломают это?" Ответ в том, что спецификация языка не поддерживает создание универсального массива. См. Это сообщение на форуме для получения дополнительной информации - для обеспечения обратной совместимости ничего не известно о типе T во время выполнения.

5 голосов
/ 17 декабря 2008

В объяснении @ mattb могут быть небольшие ошибки.

Ошибка не

java.lang. Объект не может быть приведен к 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();
  }


}
2 голосов
/ 16 декабря 2008

@ Мэтт Б: Спасибо за ответ! Очень полезно.

Я нашел обходной путь для тех, кто заинтересован: дайте методу 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);
}
...