Java Наследование пониженное ClassCastException

#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 в a B , что недопустимо, поэтому вы получаете исключение во время выполнения.

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 может быть описана только следующим образом:

  1. Класс B — это класс A (каждый объект типа B может быть законно приведен к классу A).
  2. Класс 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 является дочерним классом.