Подтвердить что ты не робот

Литье "с" интерфейсом

interface I{}
class A implements I{}
class B{}

Во-первых:

I[] arr = new A[10];
arr[0] = (I) new B(); // will produce ClassCastException at runtime

Во-вторых: где, если я использую конкретный класс

I[] arr = new A[10];
arr[0] = (A) new B(); // will produce compile-time error

Какая разница, если в моем первом примере, (I) new B(), компилятор java также должен произвести ошибку компиляции?

Разве это не значит, что компилятор java может отличить, что он также является "неконвертируемым типом"? Особенно, когда новый оператор приходит немедленно?

Есть ли какой-либо случай/шанс, что возможно, что создание этого нового экземпляра B может создать конвертируемый тип типа I?

В какой-то момент я знаю, что компилятор Java не должен сразу говорить, что это ошибка компилятора, например, когда вы это делаете:

I i = (I) getContent(); // wherein getContent() will return a type of Object

Edit:

Позвольте мне пояснить этот вопрос, почему это не является возможным дублированием этого: Передавать ссылку известного типа на интерфейс вне иерархии типов

Цель этого вопроса заключается не в том, что я не знаю, что будет результатом или что-то не так с чем-то и т.д.

Я просто хочу узнать "более подробное объяснение технически" о том, почему JVM ведет себя так или почему Java придумал такое решение о том, чтобы не сделать такой сценарий ошибкой времени компиляции.

Как мы все знаем, всегда лучше находить "проблемный код" во время компиляции, а не во время выполнения.

Другое дело, ответ, который я ищу, был найден здесь, в этой теме, не на тех "дубликатах?".

4b9b3361

Ответ 1

Правила для того, что касты являются законными компиляции, учитывают только статические типы.

Когда компилятор Java анализирует выражение (I) new B(), он видит, что статический тип выражения new B() равен B. Мы можем сказать, что new B() не может быть экземпляром I, но правила анализа времени компиляции не могут сказать, что объект фактически не является экземпляром подкласса B, который реализует I.

Таким образом, компилятор должен пропустить это. В зависимости от того, насколько сложным является компилятор, он может обнаружить странность и выдать какое-то предупреждение, но таким же образом 1/0 не является ошибкой времени компиляции, это не может быть ошибкой времени компиляции.

Ответ 2

Разница в этой ситуации, очевидно, заключается в том, что I является интерфейсом, который может быть реализован. Это означает, что даже если B не имеет ничего общего с I, может существовать подкласс B, который реализует интерфейс I. Проиллюстрируем это на примере:

interface I{}
class A implements I{}
class B{}
class C extends B implements I{}

I[] arr = new A[10]; // valid, cause A implements I
B b = new C(); // Valid because C is a subclass of B
arr[0] = (I) b; // This won't produce ClassCastException at runtime, because b
                // contains an object at runtime, which implements I
arr[0] = (I) new B(); // This will compile but will result in a ClassCastException
                      // at runtime, cause B does not implement I

Важно различать разницу между типами static и dynamic. В этом случае статическим типом переменной B является B, но он имеет динамический тип (тип выполнения) C, где new B() также имеет статический тип B, но динамический тип также B. А так как в некоторых случаях отбрасывание от B до I не приведет к исключениям (как в этом сценарии), компилятор допускает такие приведения, но только для типа интерфейса.

Теперь рассмотрим следующий сценарий:

I[] arr = new A[10];
B b = new C(); // Valid because C is a subclass of B
A a1 = (A) b; // compile time error
A a2 = (A) new B(); // compile time error

Возможно ли, что подкласс B будет когда-либо расширяться A и B? Ответ НЕТ, потому что вы ограничены только одним суперклассом для расширения на Java (где на некоторых других языках OO это не так), поэтому компилятор запрещает это, потому что нет возможности сценарии, где это будет работать.

Ответ 3

Вы можете наложить любой объект на нужный var, только если вы примените его к типу var:

interface I{}
class A implements I{}
class B{}

I var = (I) object; // This is always possible in compile-time, no matter the object type, because object is casted to the var type, I
I var = (A) object; // Not possible in compile-time because of the difference of types

Исключение времени выполнения возникает, когда объект не может быть запущен, но вы не можете знать его до времени выполнения.

A object = new A();
I var = (I) object;
B anotherObject = new B();
var = (I) anotherObject;

Оба из вышеперечисленного будут работать во время компиляции, но только первый из них будет выполнять его во время выполнения из-за реализации интерфейса I.

Ответ 4

Наследование классов - это одиночное наследование, ни один будущий потомок не может ввести новый базовый класс, например. в вашем примере ни один из потомков B не может быть добавлен к A. Но он может ввести новый интерфейс, т.е. Потомок B может поддерживать I.

Это неудача компилятора в том, что он не может решить ваш простой случай, но это не тот случай, который вы когда-либо видели бы в дикой природе. Создайте и создайте в одной строке.

Пример того, почему компилятор не может обнаружить это в более сложном случае, используя ваши классы

void method(B b){
  I i = (I) b;
}

class C extends B implements I{} // a descendent of B that introduces support for I

method(new A()); //still compile time error
method(new B()); //runtime exception
method(new C()); //works

Ответ 5

Я пробовал четыре случая:

  • Листинг класса для другого класса.
  • Передача класса в интерфейс.
  • Внедрение интерфейса в класс.
  • Передача интерфейса другому интерфейсу.

Ошибка компиляции в первом случае.

static interface I {}
static interface J {}

static class A {}
static class B {}

Object o = (B) new A();       // compile-time error
Object o = (I) new A();       // runtime error
Object o = (B) ((I) new A()); // runtime error
Object o = (J) ((I) new A()); // runtime error

Моя догадка заключается в том, что это происходит из-за того, что определение того, будет ли подача успешной или нет относительно простой в первом случае, по сравнению с другими тремя случаями. Основная причина заключается в том, что класс может расширять только один класс, что позволяет компилятору рассуждать о том, будет ли прилив успешным или нет. См. Следующие примеры:

Предположим, что помимо вышеперечисленных классов и интерфейсов я добавляю новый класс:

static class C extends B implements I, J {}

Пример кода, который отличает класс к интерфейсу (последняя строка):

C c = new C();
B b = (B) c;
I i = (I) b; // This is ok.

Пример кода, который передает интерфейс классу (последняя строка):

C c = new C();
I i = (I) c;
B b = (B) i; // This is ok.

Пример кода, который передает интерфейс другому интерфейсу (последняя строка):

C c = new C();
I i = (I) c;
J j = (J) i; // This is ok.