public class InterfaceCasting {
private static class A{}
public static void main(String[] args) {
A a = new A();
Serializable serializable = new Serializable(){};
a = (A)serializable;
}
}
Компиляция завершилась успешно, но исключение во время выполнения
Exception in thread "main" java.lang.ClassCastException: InterfaceCasting$1 cannot be cast to InterfaceCasting$A
ПОЧЕМУ КОМПИЛЯЦИЯ УДАЛЕНА? Компилятор должен знать, что serialiazable - это не A?
Хотя я не знаю правильного ответа, обычно не рекомендуется приводить интерфейс к классу по нескольким причинам.
a) Интерфейс определяет контракт, он гарантирует поведение. Класс может определять больше, чем этот контракт, использование других методов может иметь неожиданные побочные эффекты и прерывать работу API. Например.когда методу передается список, и вы обнаруживаете, что переданный объект на самом деле является LinkedList, и вы приводите его и используете методы на основе очереди, которые он также определяет, вы нарушаете API.
b) также, объект с интерфейсом может быть не "реальным" объектом во время выполнения, а, возможно, служебным прокси, созданным вокруг исходного объекта библиотекой, такой как Spring или EJB. В этих случаях ваш бросок потерпит неудачу.
Если вам абсолютно необходимо разыгрывать, никогда не делайте этого без экземпляра проверки:
if(myServiceObject instanceof MyServiceObjectImpl){
MyServiceObjectImpl impl = (MyServiceObjectImpl) myServiceObject;
}
Спецификация языка Java гласит, что:
Некоторые приведения могут быть признаны неверными во время компиляции; такие приведения приводят к ошибке времени компиляции.
И позже в шоу Подробные правила законности времени компиляции преобразования приведения значения ссылочного типа 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
возможно во время выполнения, и это показывает, что в таких случаях решение лучше оставить на время выполнения если мы можем бросить или нет.
Он не может этого знать, поскольку тип времени компиляции 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
.
Serializable
НЕ является A
, поэтому выдает ClassCastException
.
Подробные правила законности времени компиляции преобразования приведения значения ссылочного типа S времени компиляции в ссылочный тип времени компиляции T следующие:
[...]
Если S — тип интерфейса:
- Если T является типом массива, [...].
- Если тип T не является окончательным (§8.1.1), то, если существует супертип X типа T и супертип Y типа S, такие, что и X, и Y являются доказуемо различными параметризованными типами, и что стирания X и Y совпадают, возникает ошибка времени компиляции. В противном случае приведение всегда допустимо во время компиляции (поскольку даже если T не реализует S, подкласс T может это сделать) .
Источник:
JLS: преобразования и продвижения
Как вы заметили, это будет компилироваться:
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;
}
}