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

Различные типы возвращаемых значений при реализации общих методов

Сегодня я наткнулся на некоторый рабочий код Java, который я даже не ожидал компиляции. Уменьшенный до минимума, он выглядит следующим образом:

import java.util.List;

interface A {
    <T> List<String> foo();
}

interface B {
    <T> List<Integer> foo();
}

class C implements A, B {
    @Override
    public List<?> foo()
    {
        return null;
    }
}

На первый взгляд параметр типа <T> методов foo в A и B выглядит ненужным, поскольку T больше не используется. Во всяком случае, я узнал, что это играет решающую роль в разрешении конфликтующих типов возвращаемых значений сосуществовать в одной и той же реализации: если один или оба из <T> не учитываются, код не компилируется. Здесь нерабочая версия:

import java.util.List;

interface A {
    List<String> foo();
}

interface B {
    List<Integer> foo();
}

class C implements A, B {
    @Override
    public List<?> foo()
    {
        return null;
    }
}

Мне не нужно исправлять фрагменты кода выше, поскольку это просто примеры, которые я изложил, чтобы объяснить мою мысль. Мне любопытно понять, почему компилятор ведет себя по-другому с ними. Может кто-нибудь объяснить, какие именно правила здесь делают?

4b9b3361

Ответ 1

Пока первый пример компилируется, он выдаст предупреждение о непроверенной конверсии:

// Type safety: The return type List<?> for foo() from the type C needs
// unchecked conversion to conform to List<String>
public List<?> foo()
{
    return null;
}

Здесь происходит то, что объявляя параметры типа, A.foo() и B.foo() являются общие методы. Тогда переопределение C.foo() опускает этот параметр типа. Это похоже на использование необработанного типа, по сути, "отказ" от проверки общего типа для этой сигнатуры метода. Это заставляет компилятор использовать унаследованные методы erasures: List<String> foo() и List<Integer> foo() оба становятся List foo(), что может поэтому быть реализовано C.foo().

Вы можете видеть, что, сохраняя параметр type в объявлении C.foo(), вместо этого будет ожидаемая ошибка компилятора:

// The return type is incompatible with A.foo()
public <T> List<?> foo()
{
    return null;
}

Аналогично, если какой-либо из методов интерфейса не объявляет параметр типа, то исключение параметра типа из переопределения не позволяет "отказаться" от проверки общего типа для этого метода, а тип возврата List<?> остается несовместимым.

Это поведение описано в JLS §8.4.2:

Понятие поднаборки предназначено для выражения взаимосвязи между двумя методами, чьи подписи не идентичны, но в которых можно переопределить другую. В частности, он позволяет методу, подпись которого не использует общие типы, чтобы переопределить любую генерализованную версию этого метода. Это важно, так что разработчики библиотек могут свободно генерировать методы независимо от клиентов, которые определяют подклассы или подинтерфейсы библиотеки.

Часто задаваемые вопросы о генералитетах Angelika Langer расширяются по этому поведению в ее разделе Может ли универсальный метод переопределить общий?:

Теперь рассмотрим пример, в котором не общие методы подтипа переопределить общие методы супертипа. Не общие методы подтипа считаются переопределяющими версиями общих методов супертипа, если стирания подписей идентичны.

Пример (из не общих методов подтипа, переопределяющих общий супертип методы):

class Super { 
  public <T> void set( T arg) { ... } 
  public <T> T get() { ... } 
} 
class Sub extends Super { 
  public void set( Object arg) { ... } // overrides 
  public Object get() { ... }    // overrides with unchecked warning 
} 

warning: get() in Sub overrides <T>get() in Super;  
return type requires unchecked conversion 
found   : Object 
required: T 
        public Object get() { 

Здесь методы подтипа имеют подписи, а именно set(Object) и get(), которые идентичны стиранию методов супертипа. Эти титрованные подписи считаются эквивалентными.

В случае метода get существует один недостаток: мы получаем unchecked warning, потому что типы возврата на самом деле не совместимы. Тип возвращаемого метода подтипа get равен Object, тип возврата метода супертипа get - параметр неограниченного типа. тип возврата метода подтипа не идентичен супертипу тип возвращаемого метода и не является его подтипом; в обеих ситуациях компилятор с радостью примет типы возврата как совместимые. Вместо этого возвращаемый тип метода подтипа Object можно преобразовать в тип возвращаемого типа супертипа посредством непроверенного преобразования. Непроверенное предупреждение указывает на необходимость проверки типа, которая ни компилятор, ни виртуальная машина не могут выполнять. В других слов, неконтролируемая операция не безопасна для типов. В случае конвертируемых типов возврата, кто-то должен был убедиться, что Возвращаемое значение метода подтипа является совместимым с типом супертипа метод возвращаемого типа, но никто, кроме программиста, не может обеспечить это.