#java #casting
#java #наследование #classcastexception #пониженный
Вопрос:
учитывая следующий код, у меня есть вопрос:
class A{}
class B extends A {}
class C extends B{}
public class Test {
public static void main(String[] args) {
A a = new A();
A a1=new A();
B b = new B();
// a=b;// ok
// b=(B)a;// ClassCastException
// a=(A)a1; // ok
// a=a1; // ok
a=(B)a1; // compiles ok, ClassCastException
}
}
Мой вопрос касается строки, выделенной жирным шрифтом. Я понимаю, что для компиляции кода просто необходимо убедиться, что классы находятся в одной иерархии, и в результате это может сработать (вверх по дереву неявное приведение, вниз по дереву требуется явное приведение).
Всякий раз, когда я сталкивался с ClassCastException, это потому, что ссылка указывала на объект вверх по дереву, например, ссылка типа B, указывающая на объект типа A.
Рассматриваемая строка, по-видимому, является ссылкой типа A, указывающей на объект типа A. Очевидно, что приведение к (B) является причиной ClassCastException. Может кто-нибудь объяснить, пожалуйста, что он делает для этого?
Примечание: если a1 указывал на объект типа B, то он работает (только что протестировал его). Таким образом, downcast является законным в отношении компилятора, и его можно выполнить без исключения, если ссылка указывает на объект правильного типа.
Приведение ссылки a1 к ссылке B и присвоение ее a, похоже, что ссылка A a больше не ожидает ссылки на объект типа A, но B?
Спасибо, Шон.
PS Я знаю, что это немного необычно, подготовка к сертификации Java. Обычно мы понижаем до типа с левой стороны, например, b = (B) a; (и я понимаю, почему это дает ClassCastException).
Комментарии:
1. Неясно, в чем именно заключается ваш вопрос. Вы пытаетесь преобразовать an
A
в aB
, что недопустимо, поэтому вы получаете исключение во время выполнения.2. Если a1 указывал на объект типа B, то он работает (только что протестировал его). Таким образом, downcast является законным в отношении компилятора, и его можно выполнить без исключения, если ссылка указывает на объект правильного типа.
3. Я что-то пропустил в этой последней строке? Это похоже на C / C . Это не компилируется в Java (как следует из комментария).
4. Извините, ** были введены при вводе моего q? Пытался выделить строку. Удалил их сейчас…
Ответ №1:
Все B являются A, по наследству. Но не все A являются B. Этот конкретный экземпляр не является, следовательно, исключение во время выполнения.
Комментарии:
1. Приведение ссылки a1 к ссылке B и присвоение ее a, похоже, что ссылка A a больше не ожидает ссылки на объект типа A, но B. Звучит ли это разумно?
2. Объекты имеют тип. При приведении типа знание объекта о его собственном типе проверяется на соответствие типу, к которому вы его приводите (по сути, так же, как работает оператор instanceOf). Если это действительно экземпляр нужного типа, приведение завершается успешно, и вы получаете ссылку на объект соответствующего типа. Если это не экземпляр типа, к которому выполняется приведение, вы получаете исключение ClassCastException во время выполнения. Ваш a1 является экземпляром A, но не является экземпляром подкласса B, поэтому он не может быть приведен к B. Но поскольку это МОГЛО быть B, если для переменной было установлено что-то другое, это компилируется.
3. Большое спасибо за ваш ответ. Хорошо, поэтому я должен просто забыть (на данный момент) о ссылочном типе с левой стороны и сосредоточиться на правой стороне. Суть этого в следующем: может ли ссылка, указывающая на объект типа A, быть приведена к объекту типа B? Ans: Y во время компиляции, как это МОЖЕТ работать; N во время выполнения, поскольку ссылка B никогда не может указывать на объект A. Я был смущен тем фактом, что с левой стороны была ссылка! Но ClassCastException генерируется справа? Это точно?
4. Не совсем. Любая ссылка B является ссылкой A и всегда может быть приведена таким образом, поскольку B расширяет A. Но ТОЛЬКО НЕКОТОРЫЕ A являются B, поэтому приведение переменной типа A (вашей
a1
) к типу B может быть успешным, а может и нет, в зависимости от того, является ли этот конкретный A или не является B. Может быть проще, если вы переименуете их как Cat и PersianCat — все PersianCats — кошки, но только некоторые кошки являются PersianCats, и в данном конкретном случае это было не так.5. Предполагая соответствующую иерархию: Cat c1 = new PersianCat(); всегда работает, поскольку все PersianCats являются кошками. Теперь Cat c2 = (PersianCat)new Cat(); выдает исключение ClassCastException, потому что я пытаюсь создать ссылку PersianCat для ссылки на объект Cat. Это неверно, поскольку не все кошки являются PersianCats, т.Е. Ссылка PersianCat не может указывать вверх по иерархии.
Ответ №2:
Вы пытаетесь привести ссылочную переменную суперкласса к типу подкласса. Вы не можете этого сделать. Подумайте практично, объект суперкласса не может содержать независимые методы (кроме методов суперкласса) подкласса.
Во время выполнения вы можете вызвать метод в подклассе, который, безусловно, не входит в объект суперкласса.
class A{
public void foo(){}
}
class B extends A {
public void bar(){}
}
Теперь,
A a=new A();
B b=(B)a;
b.bar();
Когда вы вызываете подобным образом компилятор, будет проверять только bar()
, существовал ли метод в class B
. Вот и все. Ему все равно, что находится в «объекте», потому что он создается во время выполнения.
Но во время выполнения, как было сказано ранее bar()
, в объекте нет метода a
. b
это просто ссылка, которая указывает на объект a, но a
содержит только foo()
not bar()
Надеюсь, вы поняли. Спасибо.
Комментарии:
1. Спасибо — прекрасный, простой пример. Я попытался обобщить в ответе на @keshlam выше. Если у вас есть шанс, посмотрите. Еще раз спасибо.
2. Рад, что вы получили лучшее простое объяснение, чем мое 🙂
3. Используя ту же логику, если мы попробуем b.foo(); это должно сработать, потому что «b» — это просто ссылка, указывающая на объект «a», А «a» содержит foo()
Ответ №3:
Downcasts являются незаконными. A — это не B, и поэтому вы не можете превратить его в единицу. Вы можете преобразовать B в A, но не наоборот.
Ответ №4:
В вашем примере кода переменная a
является ссылкой на объект типа A. Класс B расширяет класс B, но взаимосвязь между классами A и B может быть описана только следующим образом:
- Класс B — это класс A (каждый объект типа B может быть законно приведен к классу A).
- Класс A не является классом B (вы никогда не сможете присвоить rvalue типа B ссылке типа A).
Это законно: a = (A)b;
потому что класс B — это класс A.
Один из способов думать об этом — класс B является надмножеством класса A.
Если A — это набор, содержащий (1, 2), а B — это набор, содержащий (1, 2, 3), то B является надмножеством A (в терминах Java: B может быть преобразован в A), но A не является надмножеством B (A не может быть преобразован вБ).
С другой точки зрения:
- Сократ был смертным.
- Все люди смертны.
Сократ (класс В) — это человек (класс А).
Это недопустимое второстепенное утверждение: все люди — Сократы.
Ответ №5:
Помните, что допустимые приведения определяются тестом «IS-A», почему вы можете скомпилировать свой код?, потому ClassCastException
что является непроверенным исключением, поскольку распространяется от RuntimeException
ClassCastException — ЯВЛЯЕТСЯ—> RuntimeException (еще один пример возможного юридического приведения).
Ответ №6:
Чтобы понизить класс в Java и избежать исключений во время выполнения, используйте следующий код.
if (animal instanceof Dog) {
Dog dogObject = (Dog) animal;
}
Animal
является родительским классом и Dog
является дочерним классом.