Java.lang.ClassException: нельзя вставить в B

Я реализовал этот код:

class A {
    //some code
}
class B extends A {
    // some code
}

class C {
    public static void main(String []args)
    {
        B b1 = (B) new A();
        A a1 = (B) new A();
    }
}

Обе эти строки при компиляции отдельно компилируются в порядке, но дают ошибку времени выполнения с java.lang.ClassException: A cannot be cast into B.

Почему они хорошо компилируются, но дают ошибку времени выполнения?

Ответ 1

Переменные типа A могут хранить ссылки на объекты типа A или его подтипы, как в вашем классе дел B.

Так что возможно иметь такой код:

A a = new B();

Переменная a имеет тип A, поэтому она имеет доступ только к API этого класса, она не может получить доступ к методам, добавленным в класс B, к какому объекту она относится. Но иногда мы хотим иметь доступ к этим методам, чтобы можно было каким-то образом хранить ссылку из a в некоторой переменной более точного типа (здесь B), через которую мы могли бы получить доступ к этим дополнительным методам из класса. B.
Но как мы можем это сделать?

Попробуем добиться этого следующим образом:

B b = a;//WRONG!!! "Type mismatch" error

Такой код дает ошибку времени компиляции Type mismatch. Это спасает нас от такой ситуации:

  • class B1 extends A
  • class B2 extends A

    и у нас есть A a = new B1();.

    Теперь давайте попробуем назначить B1 b = a;. Помните, что компилятор не знает, что на самом деле хранится в переменной a, поэтому ему нужно генерировать код, который будет безопасен для всех возможных значений. Если компилятор не будет жаловаться на B1 b = a;, он также должен позволить скомпилировать B2 b = a;. Так что просто чтобы быть в безопасности, это не позволяет нам делать это.

    Итак, что мы должны сделать, чтобы назначить ссылку из a в B1? Нам нужно явно сообщить компилятору, что мы знаем о потенциальной проблеме несоответствия типов здесь, но мы уверены, что ссылка, хранящаяся в a, может быть безопасно назначена в переменной типа B. Мы делаем это путем приведения значения из a к типу B через (B)a.

    B b = (B)a;
    

Но вернемся к примеру из вашего вопроса

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

Оператор new возвращает ссылку того же типа, что и созданный объект, поэтому new A() возвращает ссылку типа A, поэтому

B b1 = (B) new A();

можно увидеть как

A tmp = new A();
B b1 = (B) tmp;

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

class A {
    // some code
}

class B extends A {
    private int i;
    public void setI(int i){
        this.i=i;
    }
}

Если это будет разрешено

B b = (B)new A();

позже вы могли бы вызвать b.setI(42);. Но будет ли это правильно? Нет, потому что экземпляр класса A не имеет ни метода setI, ни поля i, которое этот метод использует.

Поэтому для предотвращения такой ситуации (B)new A(); во время выполнения выдает java.lang.ClassCastException.

Ответ 2

Причина, по которой он не выполняется во время выполнения, заключается в том, что объект не является B. Это A. Так что в то время как some Как можно отличить B, ваш не может.

Компилятор просто не может проанализировать все, что произошло с вашим объектом A. Например.

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

B b1 = (B) a1;    // Ok
B b2 = (B) a2;    // Fails

Итак, компилятор не уверен, действительно ли ваш объект A наследуется на B. Таким образом, в приведенном выше примере он думал бы, что последние две строки были в порядке. Но когда вы на самом деле запускаете программу, она понимает, что a2 не является B, это только A.

Ответ 3

A a1 = (B) new A();

Потому что A НЕ B.

Время компиляции работает, потому что вы выполняете кастинг и явно гарантируете, что компилятор уверен, что во время выполнения A будет B.

Ответ 4

Имя it self подразумевает, что compiler будет просто смотреть на тип времени компиляции expression.

Он не делает допущений для типа времени выполнения expression.

http://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html#jls-5.5.1

Приступая к реальной проблеме

Вы не можете отличить A от B.You может отличать B от A. Когда у вас есть манго, у вас есть фрукты. Но когда у вас есть фрукты, это не означает, что у вас есть манго.

Ответ 5

Когда B расширяет A, это означает, что все методы и свойства A также присутствуют в B.

Итак, вы можете бросить B до A,

но вы НЕ МОЖЕТ отбрасывать A в B.

Вам действительно нужно заботиться о кастинге в своем приложении.

Ответ 6

когда вы говорите, что B продолжит A, A становится отцом B теперь технически B имеет все charecteristics A плюс свои собственные в то время как A имеет только charecteristics только

если вы скажете, что конвертируете A в B и назначаете B, это нормально но если вы произнесете A в B и назначите A, это невозможно, так как A здесь не знает дополнительных charecteristics, присутствующих в B.

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

Ответ 7

Я не уверен в части компиляции, но могу объяснить ошибку времени выполнения.

B расширяет A, что означает, что каждый объект класса B также является объектом типа A. Другой путь не соответствует действительности.

Сравните A с "Млекопитающим" и B с "Корова". Корова всегда млекопитающая, но не каждое Млекопитающее - корова.

Ответ 8

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

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

Ответ 9

Ниже приводится компиляция -

A a = new B();

Такие статические отливки неявно выполняются компилятором, потому что компилятор знает о том, что B - это A.

Следующее не компилируется -

B b = new A();

Нет компиляции, потому что компилятор знает, что A не B.

После компиляции -

B b = (B) new A();

Это динамическое кастинг. С помощью (B) вы сообщаете компилятору, что вы хотите, чтобы кастинг произошел во время выполнения. И вы получаете CCE во время выполнения, когда среда выполнения пытается выполнить бросок, но выясняет, что это невозможно сделать и выбрасывает CCE.

Когда вы выполняете (или должны делать) что-то вроде этого, ответственность лежит на вас (а не на компиляторе), чтобы убедиться, что CCE не встречается во время выполнения.

Ответ 10

Это просто. Подумайте, что при расширении вы должны использовать is a

B `is a` A
A `is not` B

Более реалистичный пример

class Animal{
}

class Dog extends Animal{
}

class Cat extends Animal{
}

A DOG is a Животное ЖИВОТНЫЕ IS NOT Необходима ДОБАВАЯ (Пример: кошка не собака, а кошка - животное)

Вы получаете runtime exception, во время выполнения понимаете, что это животное не собака, это вызов downcasting и небезопасно, что вы пытаетесь сделать.

Ответ 11

Тот факт, что B extends A означает, что B является A, то есть B подобен A, но, вероятно, добавляет некоторые другие вещи.

Противоположность неверна. A не B. Поэтому вы не можете использовать A для B.

Подумайте так. A - Animal. B - Bee. Является ли Bee животным? Да. Является ли какое-нибудь животное Пчелой? Это не так. Например, собака - это животное, но определенно не пчела.

Ответ 12

Поскольку A является родительским элементом B. B расширяет функциональность A, но сохраняет исходную функциональность A. Поэтому B можно фактически отличить до A, но не наоборот.

Что делать, если B добавлен новый метод, скажем newMethodInB(). Если вы попытаетесь вызвать этот метод с помощью переменной B в экземпляре A (представьте, что бросок работал), что бы вы ожидали? Ну, вы обязательно получите ошибку, потому что этот метод не существует в A.

Ответ 13

Потому что все, что видит компилятор, это то, что A передается в B. Так как некоторые A фактически могут быть B, это может работать для этих A. Записывая явное приведение, вы гарантируете, что данный конкретный A фактически является действительным B. Однако это не так.

A justA = new A();
A anAThatIsAlsoAValidB = new B(); // implicit cast to supertype

B b1 = (A) anAThatIsAlsoAValidB ; // Cast an A into a B. At runtime, this will work fine! Compiler allows casting A into B.
B b2 = (A) justA; // Cast an A into a B. At runtime, this won't work. Compiler has/uses no more info than above.

Вот почему компилятор действительно не знает о типе:

com.example.ThridPartyType obj = new com.example.ThridPartyType();
B b = (B) obj.getSomeA(); 
// getSomeA() returns A and that is all the compiler knows.
// Depeding on the implementation of "ThridPartyType::getSomeA()" the A returned may or may not actually also be a valid B. 
// Hence, if the cast works or not will only be known at runtime. If it doesn't, the Exception is thrown.