Подтвердить что ты не робот

Интерфейс 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?

4b9b3361

Ответ 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.