Почему этот класс компилируется, даже если он неправильно реализует свой интерфейс? - программирование

Почему этот класс компилируется, даже если он неправильно реализует свой интерфейс?

a List then Почему код (ниже) компилируется? Разумеется, MyClass2 должен вернуть List<Integer>?

public class Main {
  public static void main(final String[] args) {
    MyClass myClass = new MyClass();
    List list = myClass.getList();
    System.out.println(list);
    System.out.println(list.get(0).getClass());

    MyClass2 myClass2 = new MyClass2();
    List list2 = myClass2.getList();
    System.out.println(list2);
    System.out.println(list2.get(0).getClass());
  }

  public interface Int1 {
    public List getList();
  }

  public interface Int2 extends Int1 {
    @Override
    public List<Integer> getList();
  }

  public static class MyClass implements Int2 {
    @Override
    public List<Integer> getList() {
      return Arrays.asList(1, 2, 3);
    }
  }

  public static class MyClass2 implements Int2 {
    @Override
    public List getList() {
      return Arrays.asList("One", "Two", "Three");
    }
  }
}

Я заметил, что если вы попытаетесь сделать это List<String>, тогда вы получите сообщение об ошибке "java: Main.MyClass2 не является абстрактным и не переопределяет абстрактный метод getList() в Main.Int2". Я не совсем понимаю, почему вы не получили это в приведенном выше примере.

Примечание. Решение проблемы в моем проекте состоит в том, чтобы сделать сам интерфейс общим, т.е. Int1<X> (конечно, я использую более лучшие имена, чем это, его пример).

4b9b3361

Ответ 1

Ответ находится в JLS 7 5.5.1. Тип ссылочного типа:

Учитывая тип ссылки времени компиляции S (источник) и время компиляции ссылочный тип T (цель), преобразование каста существует от S до T, если ошибки компиляции не происходят из-за следующих правил.

Если S - тип класса:

If T is a class type, then either |S| <: |T|, or |T| <: |S|. Otherwise, a compile-time error occurs.

Furthermore, if there exists a supertype X of T, and a supertype Y of S, such that both X and Y are provably distinct parameterized types

(п. 4.5), и что стирания X и Y одинаковы, время компиляции возникает ошибка.

В вашем случае List<Integer> и List<String> являются явно различными параметризованными типами, и оба они имеют одинаковые стирания: List.

Ответ 2

Я подозреваю, что компилятор должен дать вам предупреждение для Unchecked conversion, вместо того, чтобы помечать его как ошибку компилятора. Позвольте понять, почему он вас предупреждает:

У вас есть два метода для реализации, чтобы выполнить контракт интерфейса, который вы реализуете:

public List getList();
public List<Integer> getList();

Теперь, если в вашем классе вы просто обеспечиваете реализацию первого метода, он может обрабатывать запрос для метода 2 nd. Вы можете вернуть List<Integer> с типом возврата List. Это потому, что List<Integer> - это не что иное, как List во время выполнения, из-за стирания типа.

Но если вы просто дадите реализацию метода 2 nd он не будет удовлетворять контракту первого метода. Первый метод говорит, что он может возвращать любой тип List, так как он использовал тип raw в возвращаемом типе. Таким образом, он может вернуть List<Integer>, List<Object>, List<String>, что угодно. Но метод 2 nd может возвращать только List<Integer>.

Компилятор будет проверять этот тип во время компиляции, и он даст вам предупреждение, что List требует неконтролируемого преобразования в List<Integer>, потому что преобразование в любом случае будет успешным во время выполнения из-за стирания типа.


Это аналогичный случай, как показано ниже:

List<String> listString = new ArrayList<String>();
List rawList = new ArrayList();

listString = rawList;  // Warning: Unchecked conversion
rawList = listString;

Рекомендуемое чтение:

Ответ 3

Подпись public List<Integer> getList() совпадает с сигнатурой public List getList() после стирания типа. Для переопределения методов вам необходим переопределяющий метод, который должен быть подвыражением переопределенного метода. Компилятор здесь решит, что метод подкласса переопределяет интерфейс, если он идентичен после стирания типа, и у нас никогда не будет конфликта.

Ответ 4

Это, буквально, не действует из-за стирания стилей:

public interface Int1 {
     public List getList();
}

public interface Int2 extends Int1 {
     @Override
     public List<Integer> getList();
}

Во время выполнения любой List<X> становится List. Поэтому вы здесь не @Override. Прототипом времени выполнения .getList() является List getList(). Тот факт, что вы параметризуете свой List в Int2, полностью игнорируется.

Компилятор предупреждает вас об этом:

java: Note: Main.java uses unchecked or unsafe operations.
java: Note: Recompile with -Xlint:unchecked for details.

Ответ 5

Int2 расширяет Int1, тогда List is ok,

List<String> 

не компилируется, потому что он не определен в Int1 или Int2.

List<Integer> 

в

Int2 

подлежит стиранию типа в соответствии со спецификацией Java http://docs.oracle.com/javase/tutorial/java/generics/erasure.html

Ответ 6

Таким образом, похоже, что это связано с стиранием типа и его видом, который генерирует предупреждение, а не ошибку компилятора. Я думаю, это похоже на другие непроверенные сценарии конверсии, которые я видел раньше. Спасибо за хорошо исследованные ответы людей.