Нет ClassCastException при неправильном обобщенном приведении [Java] - PullRequest
0 голосов
/ 21 февраля 2019

Взгляните на метод main следующего класса:

public class Outer {
    static class A<T> {
        public A<T> a;
        public T t;

        public A() { a = null; }
        public A(T t) {
            a = (A<T>) new B();
            a.t = t;
        }
    }

    static class B extends A<Integer> {}

    public static void main(String[] args) {
        // A<String> c = new B();              // CREATION 1: would not work
        // A<String> b = (A<String>) new B();  // CREATION 2: would not work
        A<String> a1 = new A<>("str");         // CREATION 3: works (???)
        A<String> a2 = new A(0);               // CREATION 4: works (???)

        System.out.println(a1.a);    // CALL 1 -> Poly$B@...
        System.out.println(a1.t);    // CALL 2 -> null
        System.out.println(a1.a.a);  // CALL 3 -> null
        System.out.println(a1.a.t);  // CALL 4 -> str (???)
        System.out.println(a2.a.t);  // CALL 5 -> ClassCastException (???)
    }
}

Очевидно, создание 1 и 2 завершится неудачно во время компиляции, поскольку приведение невозможно.Создание 3-го объекта могло быть возможным, поэтому я не ожидал, что он потерпит неудачу во время компиляции, но: я ожидал, что (A<T>) new B(); потерпит неудачу во время выполнения, так как B расширяет A<Integer> и не должно быть возможности привести A<Integer> to A<String> (так же, как создание 2) ... Но здесь нет ClassCastException!?

К сожалению, создание 3 работает.a1.a.t (который является t членом B объекта) теперь будет хранить строку "str" ​​!?Таким образом, объект B (то есть t имеет тип Integer) может хранить String s в Integer t !!?

Еще одна вещь: посмотрите на a2.Попытка доступа к a2.a.t приведет к исключению ClassCastException.Это удивляет меня, так как во время доступа не выполняется даже приведение актеров - по крайней мере, я так думал ...

Итак, мои вопросы вкратце:

  1. Почему возможно создание 3 (/ 4)?
  2. Почему объект B способен хранить строку в своей общей целочисленной переменной t?
  3. Почему вызов 5 не выполняется во время выполнения?

Спасибо!:)

1 Ответ

0 голосов
/ 21 февраля 2019

Почему возможно создание 3 (/ 4)?

Создание 3 Является ли «алмазный» синтаксис для выводящего типа.Таким образом, new A<> - это то же самое, что и запись new A<String>.

. Таким образом, мы можем заменить это длинным синтаксисом до Java 1.7.

A<String> a1 = new A<String>("str");

Это будет проходить через следующий конструкторгде T было java.lang.String и t является java.lang.String.Обратите внимание, что во время выполнения существует только одна копия этого кода, с необработанным типом T, равным java.lang.Object.

    public A(T t) {
        a = (A<T>) new B();
        //  ^^^^^^ NB: This will at least give a rawtype warning
        //             as it causes heap pollution.
        a.t = t;
    } 

Тип a равен A<T> во время компиляции и java.lang.Object стерто.Типы a.t и t - T во время компиляции и java.lang.Object стерты.Таким образом, он компилируется без предупреждения, и нет приведения для проверки во время выполнения.Назначение выполнено успешно.

Создание 4 использует необработанные типы.Ваш компилятор должен выдать предупреждение (используйте -Xlint).Игнорирование этих предупреждений может привести к ClassCastException позже.Обобщения являются «фикцией» компилятора над реальной виртуальной машиной, которая, по существу, следует правилам языка Java 1.0.

Почему объект B способен хранить строку в своей общей целочисленной переменной t?

Стертый тип t в A (и, следовательно, B) равен java.lang.Object и поэтому может хранить любой ссылочный тип, если проверка отсутствует или неверна.

Почему вызов 5 не выполняется во время выполнения?

Объект a2.a.t является java.lang.Integer.a2.a имеет статический тип A<java.lang.String>, поэтому статический тип a2.a.t равен java.lang.String.javac вставит приведение, чтобы проверить, что возвращаемый необработанный тип соответствует статическому типу.В этом конкретном случае IIRC, более старые версии javac неправильно опускают приведение и используют перегрузку println(java.lang.Object) вместо println(java.lang.String).

. Вы можете использовать javap -c -private, чтобы точно определить, где checkcastинструкции помещены.

Заключение

Убедитесь, что у вас включены предупреждения.Все, что не имеет их по умолчанию, несколько подозрительно.Не игнорируйте и не подавляйте предупреждения, особенно о необработанных типах.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...