Интерфейс преобразования Java в класс

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?

18
задан duffymo 27 August 2010 в 09:26
поделиться

6 ответов

Хотя я не знаю правильного ответа, обычно не рекомендуется приводить интерфейс к классу по нескольким причинам.

a) Интерфейс определяет контракт, он гарантирует поведение. Класс может определять больше, чем этот контракт, использование других методов может иметь неожиданные побочные эффекты и прерывать работу API. Например.когда методу передается список, и вы обнаруживаете, что переданный объект на самом деле является LinkedList, и вы приводите его и используете методы на основе очереди, которые он также определяет, вы нарушаете API.

b) также, объект с интерфейсом может быть не "реальным" объектом во время выполнения, а, возможно, служебным прокси, созданным вокруг исходного объекта библиотекой, такой как Spring или EJB. В этих случаях ваш бросок потерпит неудачу.

Если вам абсолютно необходимо разыгрывать, никогда не делайте этого без экземпляра проверки:

if(myServiceObject instanceof MyServiceObjectImpl){
    MyServiceObjectImpl impl = (MyServiceObjectImpl) myServiceObject;
}
0
ответ дан 30 November 2019 в 06:59
поделиться

Спецификация языка 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 возможно во время выполнения, и это показывает, что в таких случаях решение лучше оставить на время выполнения если мы можем бросить или нет.

9
ответ дан 30 November 2019 в 06:59
поделиться

Он не может этого знать, поскольку тип времени компиляции 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.

0
ответ дан 30 November 2019 в 06:59
поделиться

Serializable НЕ является A, поэтому выдает ClassCastException.

-1
ответ дан 30 November 2019 в 06:59
поделиться

Подробные правила законности времени компиляции преобразования приведения значения ссылочного типа S времени компиляции в ссылочный тип времени компиляции T следующие:
[...]
Если S — тип интерфейса:
- Если T является типом массива, [...].
- Если тип T не является окончательным (§8.1.1), то, если существует супертип X типа T и супертип Y типа S, такие, что и X, и Y являются доказуемо различными параметризованными типами, и что стирания X и Y совпадают, возникает ошибка времени компиляции. В противном случае приведение всегда допустимо во время компиляции (поскольку даже если T не реализует S, подкласс T может это сделать) .

Источник:
JLS: преобразования и продвижения

0
ответ дан 30 November 2019 в 06:59
поделиться

Как вы заметили, это будет компилироваться:

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;
    }
}
29
ответ дан 30 November 2019 в 06:59
поделиться
Другие вопросы по тегам:

Похожие вопросы: