Как узнать, разрешено ли понижающее приведение в Java?

Предположим, у меня есть три класса A, B и C.

C расширяет B, а B расширяет A. A реализует интерфейс InterfaceA B реализует интерфейсы InterfaceB1 и InterfaceB2.

Как лучше всего найти все возможные комбинации понижения?

Я имею в виду?

Предположим, у нас есть: Бб = новый Б(); Интерфейс A i = (Интерфейс A)(A)(B) b.

Как мы можем легко узнать, компилируется ли это и вызовет ли это classcastException без IDE?

Я знаю, как работают объекты и ссылки, и неплохо разбираюсь в полиморфизме в Java.

Я начал рисовать набросок структуры класса и интерфейса.

Обновлено: я знаю, что мой пример неверен, но я всегда борюсь, когда интерфейсы присоединяются к истории.

Как мне подойти к этой проблеме, не компилируя ее?

Ayoub Rossi 20.01.2019 13:49

«Как мы можем легко узнать, скомпилируется ли это», попробуйте скомпилировать его.

Andy Turner 20.01.2019 13:50

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

Ayoub Rossi 20.01.2019 13:51

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

Joe C 20.01.2019 14:13

Предполагая, что классы имеют осмысленные и логичные имена, это не должно быть так сложно.

Bubletan 20.01.2019 14:15
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
5
251
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Давайте сначала разберемся с тем, компилируется он или нет.

Ключом к определению этого является просмотр типа времени компиляции, то есть объявленного типа переменных.

В целом,

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

Чтобы определить, успешно ли выполнено приведение во время выполнения, необходимо посмотреть на тип времени выполнения объекта, хранящегося в переменной. Например:

A a = new B();

Тип времени компиляции aA, а тип времени выполнения — B.

Как правило, приведение типа класса среды выполнения T к типу класса или интерфейса U выполняется успешно только в том случае, если T наследуется от U или реализует U.


Для будущих читателей,

Если вместо этого вы хотите узнать, как проверить успешность приведения используя код, вам следует обратиться к этому вопрос.

ваше объяснение уже довольно приятно. однако для полноты, я думаю, вы можете добавить практическую сторону вещей, т.е. "foo.getClass().isAssignableFrom(Bar.class)" и "foo instanceof Bar"

rmalchow 20.01.2019 14:13

@rmalchow Это уже покрыто другим вопросом. ОП здесь только хочет знать, как это сделать с ручкой и бумагой.

Sweeper 20.01.2019 14:17

ты редактировал это? да, я думаю, ссылка на другой ответ в порядке.

rmalchow 20.01.2019 14:19

Спасибо за ваше объяснение. Если я правильно понял, InterfaceB2 b2 = (InterfaceB2) a;, это то же самое, что InterfaceB2 b2 = (InterfaceB2) (B) (A) a;?

Ayoub Rossi 20.01.2019 15:13

@AyoubRossi Если под a вы имеете в виду a в A a = new B();, то да, эти два оператора делают одно и то же, преобразуя A в InterfaceB2. Другими словами, промежуточные приведения к B и A избыточны.

Sweeper 20.01.2019 15:15

Спасибо, последний вопрос. Предположим, у нас есть класс B, реализующий интерфейс Ib, и B b = new B(); Ib interface = b не будет работать. Должно быть Ib interface = (Ib) b. В чем причина этого?

Ayoub Rossi 20.01.2019 15:23
Ib interface = b; не будет работать нет, потому что вам нужно приведение, а потому что interface является зарезервированным словом и поэтому не может быть именем переменной. Ib ib = b; должен компилироваться. @АюбРосси
Sweeper 20.01.2019 15:25

Существуют ли случаи, когда обязательно писать два приведения или требуется только первое, а остальные избыточны?

Ayoub Rossi 20.01.2019 15:31

@AyoubRossi Почти всегда вам нужен только один актерский состав. Я сказал «почти», потому что могут быть действительно очень неясные случаи, о которых я не знаю. Я лично не сталкивался с такими случаями. Также обратите внимание, что иногда добавление большего количества приведений вызовет исключение. Предположим, что b имеет тип среды выполнения B, (InterfaceB1)b завершится успешно, но (InterfaceB1)(C)b завершится с ошибкой с ClassCastException, потому что он сначала попытается преобразовать b в C.

Sweeper 20.01.2019 15:36

Прежде всего, в вашем сценарии следующее вовсе не является понижением, потому что в описанной иерархии B - это B, B - это A, а B - это InterfaceA. Таким образом, любое из этих приведений может быть опущено.

InterfaceA i = (InterfaceA)(A)(B) b;

Затем, отвечая на ваш вопрос. Понижающее приведение будет компилироваться тогда и только тогда, когда возможно добиться успеха во время выполнения. Введем дополнительный класс D, расширяющий класс A и реализующий InterfaceB1.

A a = new A();
InterfaceB1 b1 = (InterfaceB1) a; // compilable as A reference can contain object of class B that implements InterfaceB1
InterfaceB2 b2 = (InterfaceB2) a; // compilable as A reference can contain object of class B that implements InterfaceB2
b1 = (InterfaceB1) b2; // compilable as InterfaceB2 reference can contain object of class B that implements both InterfaceB1, InterfaceB2
B b = (B) a; // compilable as A reference can contain object of class B
C c = (C) a; // compilable as A reference can contain object of class C
D d = (D) a; // compilable as A reference can contain object of class D
d = (D) b1; // compilable as InterfaceB1 reference can contain object of class D in runtime

b = (B) d; // not compilable since D reference can NEVER contain object of class B in runtime
c = (C) d; // not compilable since D reference can NEVER contain object of class D in runtime

Что касается возникновения ClassCastException, это всегда связано с тем, что на самом деле содержится в вашей ссылке на объект.

A a1 = new A();
A a2 = new B();

InterfaceB1 b1 = (InterfaceB1) a1; // compiles but causes ClassCastException as A cannot be cast to InterfaceB1
InterfaceB1 b2 = (InterfaceB1) a2; // compiles and runs just normally as B can be cast to InterfaceB1

Другие вопросы по теме