Интерфейс Java Casting для класса
public class InterfaceCasting {
private static class A{}
public static void main(String[] args) {
A a = new A();
Serializable serializable = new Serializable(){};
a = (A)serializable;
}
}
Успешная компиляция, но исключение Runtime
Exception in thread "main" java.lang.ClassCastException: InterfaceCasting$1 cannot be cast to InterfaceCasting$A
ПОЧЕМУ ПОДКЛЮЧАЕТСЯ? Компилятор должен знать, что serialiazable не является A?
Ответы
Ответ 1
Как вы заметили, это скомпилирует:
interface MyInterface {}
class A {}
public class InterfaceCasting {
public static void main(String[] args) {
MyInterface myObject = new MyInterface() {};
A a = (A) myObject;
}
}
Однако этот не будет компилироваться:
interface MyInterface {}
class A {}
public class InterfaceCasting {
public static void main(String[] args) {
A a = (A) new MyInterface() {}; // javac says: "inconvertible types!"
}
}
Итак, что здесь происходит? Какая разница?
Ну, так как MyInterface
- это просто интерфейс, он вполне может быть реализован классом, который расширяет A, и в этом случае листинг от MyInterface
до A
будет легальным.
Этот код, например, будет преуспеть в 50% всех исполнений, и иллюстрирует, что компилятор должен будет решить, возможно, неразрешимые проблемы, чтобы всегда "обнаруживать" незаконные приведения во время компиляции.
interface MyInterface {}
class A {}
class B extends A implements MyInterface {}
public class InterfaceCasting {
public static void main(String[] args) {
MyInterface myObject = new MyInterface() {};
if (java.lang.Math.random() > 0.5)
myObject = new B();
A a = (A) myObject;
}
}
Ответ 2
Спецификация языка Java утверждает, что:
Некоторые приведения могут быть неверными во время компиляции; такие приведения приводят к ошибке времени компиляции.
И позже в шоу Подробные правила законности компиляции при кастинговом преобразовании значения ссылочного типа времени компиляции S в тип задания времени компиляции T - будьте осторожны, они очень сложны и трудно понять.
Интересное правило:
- Если S является интерфейсом:
- Если T - это тип не окончательный (§8.1.1), то, если существует супертип X из T и супертип Y из S, так что и X, и Y являются явно различными параметризованными типами, и что стирания X и Y одинаковы, возникает ошибка времени компиляции. В противном случае листинг всегда является законным во время компиляции (потому что даже если T не реализует S, может быть подкласс T).
В вашем примере совершенно ясно, что приведение является незаконным. Но рассмотрите это небольшое изменение:
public class InterfaceCasting {
private static class A{}
private static class B extends A implements Serializable{}
public static void main(String[] args) {
A a = new A();
Serializable serializable = new B(){};
a = (A)serializable;
}
}
Теперь во время выполнения возможно отбрасывание от Serializable
до A
, и это показывает, что в этих случаях ему лучше оставить среду выполнения, чтобы решить, можем ли мы использовать или нет.
Ответ 3
Serializable serializable;
a = (A)serializable;
Что касается компилятора, переменная serializable может содержать любой объект, реализующий Serializable
, который включает подклассы A
. Поэтому предполагается, что вы знаете, что переменные действительно содержат объект A
и позволяют эту строку.
Ответ 4
Компилятор недостаточно умен, чтобы проследить происхождение serializable
и понять, что он никогда не может быть типа A
. Он действительно оценивает только строку:
a = (A)serializable;
и видит, что serializable
ссылка типа serializable
, но может ссылаться на класс, который также имеет тип A
. Фактический класс, который serializable
ссылки неизвестен до времени выполнения.
В этом тривиальном случае мы знаем, что этот прилив никогда не преуспеет, но в целом это остается проблемой во время выполнения, поскольку различные пути кода, которые могут привести к кастингу, (теоретически) бесконечны.
Если вы хотите избежать этой проблемы во время выполнения, вы можете проверить ее.
if (serializable instanceof A) {
a = (A)serializable;
} else ....
Ответ 5
Он не может знать, потому что тип времени компиляции serializable
равен serializable
.
Чтобы проиллюстрировать это, рассмотрите следующее:
private static class A{}
private static class B implements Serializable {}
Serializable serializable = new B();
A a = (A)serializable;
это точно так же, как ваш вопрос, он компилируется.
private static class A{}
private static class B implements Serializable {}
B b = new B();
A a = (A)b;
это не скомпилируется, потому что b
не является A
.
Ответ 6
Хотя я не знаю правильного ответа, обычно не рекомендуется вводить интерфейс в класс по нескольким причинам.
a) Интерфейс определяет контракт, он гарантирует поведение. Класс может определять больше, чем этот контракт, использование других методов может иметь неожиданные побочные эффекты и нарушать API. Например. когда метод передается список, и вы узнаете, что переданный объект на самом деле является LinkedList, и вы его применяете и используете методы на основе очереди, которые он также определяет, вы нарушаете API.
b), объект с интерфейсом может не быть "реальным" объектом во время выполнения, но, возможно, прокси-сервером службы, созданным вокруг исходного объекта библиотекой, такой как Spring или EJB. Ваш бросок не будет работать в этих случаях.
Если вы абсолютно должны бросить, никогда не делайте этого без проверки экземпляра:
if(myServiceObject instanceof MyServiceObjectImpl){
MyServiceObjectImpl impl = (MyServiceObjectImpl) myServiceObject;
}
Ответ 7
Подробные правила законности компиляции времени преобразования кастования значения ссылочного типа S периода компиляции для ссылочного типа времени компиляции T следующие:
[...]
Если S - тип интерфейса:
- Если T - тип массива, [...].
- Если T является типом, который не является окончательным (§8.1.1), то, если существует супертип X из T и супертип Y из S, такой, что оба X и Y являются, по-видимому, различными параметризованными типами, и что стирания X и Y одинаковы, возникает ошибка времени компиляции. В противном случае листинг всегда является законным во время компиляции (потому что даже если T не реализует S, подкласс T может).
Источник:
JLS: конверсии и рекламные акции
Ответ 8
Serializable
НЕ является A
, поэтому он бросает ClassCastException
.