Обобщения, массивы и ClassCastException
-
21-08-2019 - |
Вопрос
Я думаю, здесь должно быть что-то неуловимое, о чем я не знаю.Рассмотрим следующее:
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
заключается в том, что он выдается, когда вы пытаетесь привести объект к типу, который не может быть приведен.То есть, к подклассу, экземпляром которого он не является (цитирую документацию).например ,:
Object o = new Double(3.);
Double d = (Double) o; // Working cast
String s = (String) o; // ClassCastException
И, кажется, я могу это сделать.Если a
был просто T
вместо массива T[]
, мы можем получить a
и разыграйте его без проблем.Почему массивы нарушают это?
Спасибо.
Решение
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, поскольку объект не может быть преобразован в Double.
Обновление 2:чтобы ответить на ваш последний вопрос "почему массивы нарушают это?" Ответ заключается в том, что языковая спецификация не поддерживает создание универсального массива. Смотрите это сообщение на форуме для получения дополнительной информации - для обеспечения обратной совместимости ничего не известно о типе T во время выполнения.
Другие советы
В объяснении @mattb могут быть некоторые небольшие ошибки.
Ошибка заключается в не
java.lang.Объект не может быть преобразован в java.lang.Double.
Это так:
[Лява.язык.Объект;не может быть приведен к [Ljava.lang.Двойной
[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();
}
}
@мэтт би:Спасибо за ответ!Очень полезно.
Я нашел обходной путь для тех, кто заинтересован:предоставьте методу 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);
}