Когда я запускаю исходный код ниже, ошибка выполнения, которую я вижу в окне оболочки, выглядит следующим образом:
Exception in thread "main" java.lang.ClassCastException: pkgs.main.A cannot be c
ast to pkgs.test.B
at pkgs.main.Main.main(Main.java:9)
Должна ли эта ошибка быть захвачена компилятором во время компиляции, а не во время выполнения? Вот мои три отдельных файла исходного кода, которые коллективно компилируют ОК, без ошибок или предупреждений:
// ------------------------
// class Main, package main
// ------------------------
package pkgs.main;
import pkgs.test.B;
class Main {
static public void main(String args[]) {
((B)new A()).m(); // causes runtime ClassCastException to be raised.
}
}
// ---------------------
// class A, package main
// ---------------------
package pkgs.main;
public class A {
public int m() { return 1; }
}
// ---------------------
// class B, package test <--- note the different package to classes A and Main
// ---------------------
package pkgs.test;
import pkgs.main.A;
public class B extends A {
public int m() { return 100; }
}
Вот содержимое моих командных файлов компиляции и запуска на основе оболочки:
REM batch file for compilation
javac -Xlint -sourcepath ..\src -d ..\cls ..\src\pkgs\main\Main.java
REM batch file for execution
java -cp ..\cls pkgs.main.Main
Этот очень простой тестовый проект доступен для быстрой загрузки и тестирования на следующей ссылке DropBox: https://www.dropbox.com/s/60qi0vbadc5m7es/t16_mk2.7z
Правило о том, возникает ли ошибка компиляции, приведено в 5.5.1:
Учитывая ссылочный тип времени компиляции S (источник) и тип задания времени компиляции T (target), преобразование каста существует от S до T, если ошибки времени компиляции не происходят из-за следующих правил.
Если S - тип класса:
- Если T - тип класса, то либо | S | <: | T |, или | T | <: | S |. В противном случае возникает ошибка времени компиляции.
Это означает, что это допустимый листинг, если какой-либо тип является подклассом другого. Он работает таким образом, чтобы мы могли делать такие вещи следующим образом:
A actuallyB = new B(); // note implicit widening but could be (A)new B()
B theB = (B)actuallyB;
Человеку легко понять, что выражение (B)new A()
будет бросать во время выполнения; однако для компилятора не требуется выполнение этого распознавания.
B
является подклассом A
Таким образом, экземпляр A
нельзя отнести к B
Возможно обратное.
B b = new B();
A a = (A)b; //valid
A a = new A();
B b = (B)a; //invalid
Возможно, вы захотите прочитать отношения IS A.
A
, она все равно может иметь тип B
, поэтому приведение A
к B
все же имеет смысл.
Должна ли эта ошибка быть захвачена компилятором во время компиляции, а не во время выполнения?
Его следует поймать во время выполнения.
Зачем?
В классе Hirearchy class B extends A
, рассмотрим следующий код:
void perforAction(A obj) {
obj.someMethodDeclaredInA();
//more method calls from A
if (obj instanceof B) {
B casted = (B) obj;
casted.someMethodDeclaredInB();
}
}
Идея состоит в том, что первая часть этого метода обрабатывает объекты A и B одинаково, а вторая часть выполняет какое-то дополнительное действие для объекта из B. Howerver, вам нужно отбрасывать A
до B
чтобы достичь этого, поэтому это не может быть компилировать ошибку, потому что тогда этот тип кода будет скомпилирован.
Вы пытаетесь передать фактический объект A (который является родителем) в ссылку на объект B (которая является дочерней). Поэтому он дает ошибку.
Если бы это был фактический объект B; но ссылки как объект A; тогда литье типа было бы успешным. например
B obj_of_B = new B();
A obj_of_A = obj_of_B;
B other_obj_of_B = (B)obj_of_A;
Это будет работать правильно, как если бы ссылка на объект с именем "obj_of_A" имеет тип A, фактический объект имеет тип B.
Так что понимайте разницу между "типом ссылки на объект" и "типом фактического объекта". Только во время выполнения вы будете знать "тип фактического объекта". Таким образом, эта ошибка будет запущена во время выполнения.