Java. Переопределение возвращаемого типа расширенного интерфейса, когда тип возвращаемого типа использует обобщения для собственных типов параметров метода

Я наткнулся на любопытство в наследовании java, и я хотел, чтобы вы попросили о нем лучшие идеи:

Предположим, что два интерфейса A и A1

Интерфейс A1 расширяет A

Интерфейс A имеет метод, который возвращает общий тип.

Общий тип будет выглядеть как GenericType<T>.

Основная идея - теперь изменить этот общий тип возврата из GenericType<Object> в интерфейсе A в GenericType<String> в интерфейсе A1

Вначале кажется легким (плохие вещи придут позже)

Мы объявляем интерфейс A как

public interface InterfaceA {
  public GenericType<? extends Object> getAGenericType();  
}

и интерфейс A1, например

public interface InterfaceA1 extends InterfaceA
{
  @Override
  public GenericType<String> getAGenericType();
}

Как вы видите, мы вынуждены писать GenericType<? extends Object> в самом интерфейсе A, чтобы позволить переопределять его с помощью подклассов общего типа. (Фактически, общий параметр универсального типа подклассифицирован не сам общий тип)

Теперь предположим, что у GenericType есть свой метод, похожий на:

public interface GenericType<D>
{
  public void doSomethingWith( D something );
}

Теперь попытка создания экземпляра A1 отлично работает.

Скорее попытка создать экземпляр A будет сосать. Чтобы узнать, почему вы посмотрите на этот класс "use the interface":

public class LookAtTheInstance
{
  @SuppressWarnings("null")
  public static void method()
  {
    InterfaceA a = null;
    InterfaceA1 a1 = null;

    GenericType<String> aGenericType = a1.getAGenericType();

    GenericType<? extends Object> aGenericType2 = a.getAGenericType();
    Object something = null;
    aGenericType2.doSomethingWith( something );
  }
}

Вы спрашиваете: "А теперь?"

Он не работает в последних строках. На самом деле параметр "что-то" даже не от типа "Объект", это от типа "? Extends Object". Таким образом, вы не можете передать объявленный тип "Объект". Вы ничего не можете передать.

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

Есть ли у вас идеи о моделировании такого прецедента, когда подклассы должны будут переопределять тип возвращаемого значения, в то время как тип возвращаемого типа является обобщением?

Или как бы вы обошли такой пример модели?

Или я просто пропустил простую точку в общем объявлении, и мой пример возможен таким образом?

----------- (1) редактировать из-за ответов -----------

Очень хорошая базовая идея - сделать интерфейс более абстрактным! Сначала у меня была такая же идея, но... (это должно произойти)

Предположим, что:

Введем новый интерфейс AGeneric

public interface InterfaceAGeneric<T>{
  public GenericType<T> getAGenericType();
}

Теперь нам нужно будет расширить A и A1 из этого нового интерфейса:

public interface InterfaceA extends InterfaceAGeneric<Object>{}
public interface InterfaceA1 extends InterfaceAGeneric<String>{}

Это работает отлично, хотя он нарушает путь исходного наследования.

Если мы хотим, чтобы A1 все еще расширялся от A, мы должны изменить A1 на

public interface InterfaceA1 extends InterfaceA, InterfaceAGeneric<String>{}

и проблема снова. Это не работает, поскольку мы косвенно распространяем один и тот же интерфейс с разными типами. К сожалению, это не разрешено.

Вы видите проблему?

-

И указать на другое обстоятельство:

Если вы отбрасываете GenericType<? extends Object> в GenericType<Object>, это явно работает. Пример:

public class LookAtTheInstance
{
  public static void main( String[] args )
  {
    InterfaceA a = new InterfaceA()
    {
      @Override
      public GenericType<? extends Object> getAGenericType()
      {
        return new GenericType<Object>()
        {
          @Override
          public void doSomethingWith( Object something )
          {
            System.out.println( something );
          }
        };
      }
    };
    ;

    @SuppressWarnings("unchecked")
    GenericType<Object> aGenericType2 = (GenericType<Object>) a.getAGenericType();

    Object something = "test";
    aGenericType2.doSomethingWith( something );
  }  
}

Итак, мне кажется, что разрешение типа параметра метода

public interface GenericType<D extends Object>
{
  public void doSomethingWith( D something );
}

неверно.

Если D унифицирован с "? extends Object", почему тип параметра не является "Object"?

Разве это не станет более значимым?

Ответ 1

Основная идея - теперь изменить этот общий тип возврата из GenericType в интерфейсе A в GenericType в интерфейсе A1

Это невозможно, потому что Java Generics являются инвариантными. [1]

Как вы выяснили, у вас не может быть интерфейса, объявляющего метод, который возвращает GenericType<Object>, а в дополнительном интерфейсе переопределяет метод для возврата GenericType<String>: последний тип возврата не является подтипом первого. И по уважительной причине!

Вы пытались

косвенно распространяется на один и тот же интерфейс с разными типами. К сожалению, это не разрешено.

Невозможно, чтобы это могло работать: например. каков должен быть тип E в public E set(int index, E element) в классе, который реализовал как List<String>, так и List<Object>? Ваш подклассовый интерфейс должен был бы создать аналогичный гибрид: возвращаемое значение getAGenericType в под-интерфейсе должно было бы реализовать интерфейс GenericType<String> и GenericType<Object>. И, как мы видели, это невозможно.

Компилятор не знает, что вы собираетесь делать с параметром type в GenericType (хотя теоретически это можно было бы узнать, а это не так). Если у вас была переменная типа GenericType<String> и ей была назначена GenericType<Object>, вы вполне можете положить экземпляр Long, где ожидается String, и получить ClassCastException, где вы не будете ожидайте.

В методе doSomethingWith вашей переменной GenericType<? extends Object> aGenericType2 вы можете передать одну вещь: null. null - единственная ссылка на объект, которая имеет подтип ? extends Object. Тип нижней границы ? extends Object - это нулевой тип, который не может быть выражен в Java и только неявно существует как тип ссылки null.

[1] http://en.wikipedia.org/wiki/Covariance_and_contravariance_%28computer_science%29#Java

Ответ 2

@Переопределить аннотацию:

При переопределении метода вы можете хотите использовать аннотацию @Override который инструктирует компилятор, что вы намереваются переопределить метод в суперкласс. Если по какой-то причине компилятор обнаруживает, что метод не существует в одном из суперклассов, он будет генерировать ошибку.

С помощью этой аннотации вы не можете изменить тип возвращаемой функции.

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

public interface InterfaceA<T> {
  public GenericType<T> getAGenericType();  
}

Пример об переопределении общего метода в родовом классе.

Ответ 3

Я не знаю, это то, чего вы ожидаете, но вы можете объявить свой интерфейс что-то вроде этого

public interface Interface <K extends Object> { ... }    

Пока ваш класс может выглядеть как   public class InterfaceImpl extends Interface<String> { ... }

Ответ 4

Проблема в том, что InterfaceA не знает, какой тип он удерживает. Если вы получите InterfaceA, чтобы принять общий аргумент, вы можете сделать это:

public interface InterfaceA<T>
{
  public GenericType<T> getAGenericType();  
}

public interface InterfaceA1 extends InterfaceA<String>
{
  @Override
  public GenericType<String> getAGenericType();
}

public class LookAtTheInstance
{
  @SuppressWarnings("null")
  public static void method()
  {
    InterfaceA<String> a = null;
    InterfaceA1 a1 = null;

    GenericType<String> aGenericType = a1.getAGenericType();

    GenericType<String> aGenericType2 = a.getAGenericType();
    String something = null;
    aGenericType2.doSomethingWith( something );
  }
}

Ответ 5

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

interface GenericType<D> {
    D getAValue();
    void doSomethingWith(D value);
}

class StringType implements GenericType<String> {
    @Override
    public String getAValue() {
        return "Hello World";
    }

    @Override
    public void doSomethingWith(final String value) {
        System.out.println(value.length());
    }
}


interface InterfaceA {
    GenericType<? extends Object> getAGenericType();
}

interface InterfaceA1 extends InterfaceA {
    @Override
    GenericType<String> getAGenericType();
}

class AnActualA1 implements InterfaceA1 {
    @Override
    public GenericType<String> getAGenericType() {
        return new StringType();
    }
}


class LookAtTheInstance {
    public static void method() {
        InterfaceA1 a1 = new AnActualA1();

        // 'g1' is a StringType, which implements GenericType<String>; yay!
        GenericType<String> g1 = a1.getAGenericType();

        // Everything here is fine.
        String value = g1.getAValue();
        g1.doSomethingWith("Hello World");


        // But if we upcast to InterfaceA???
        InterfaceA a = (InterfaceA) a1;

        // Note: a.getAGenericType() still returns a new StringType instance,
        // which is-a GenericType<? extends Object>.
        GenricType<? extends Object> g = a.getAGenericType();

        // StringType.getAValue() returns a String, which is-an Object; yay!
        Object object = g.getAValue();

        // StringType.doSomethingWith() method requires a String as the parameter,
        // so it is ILLEGAL for us to pass it anything that cannot be cast to a
        // String. Java (correctly) prevents you from doing so.

        g.doSomethingWith(new Object()); // Compiler error!
    }
}

Концептуально, GenericType не является GenericType, поскольку GenericType может выполнять только строкиSomethingWith(), в то время как GenericType должен иметь возможность делатьSomethingWith() любой объект. GenericType - это компромисс, который компилятор позволяет вам использовать как "базовый класс" для любого GenericType, где D - это объект, но только позволяет использовать ссылку этого типа для вызова методов, безопасных для любой возможной среды выполнения значение '?' (например, getAValue(), возвращаемое значение которого всегда может быть безопасно перенесено на объект, поскольку D - это объект, независимо от типа исполнения).

Трудно сказать, что (если что-либо) исходный плакат действительно пытался смоделировать с помощью этого кода, и в частности, насколько большая часть родового GenericType действительно нужна, но, возможно, наследование должно было пойти другим путем вокруг?

/**
 * I can do something with instances of one particular type and one particular
 * type only.
 */
interface GenericType<D> {
    void doSomethingWith(D value);
}

/**
 * I can do something with instances of any type: I am-a GenericType<String>
 * because I can totally do something with a String (or any other kind of
 * Object).
 */
interface NonGenericType extends GenericType<Object>, GenericType<String> {
    @Override
    void doSomethingWith(Object value);
}


interface StringHandlerFactory { // nee InterfaceA1
    GenericType<String> getAGenericType();
}

/**
 * I extend StringHandlerFactory by returning a NonGenericType (which is-a
 * GenericType<String>, satisfying the interface contract, but also so much
 * more).
 */
interface ObjectHandlerFactory extends StringHandlerFactory { // nee InterfaceA
    @Override
    NonGenericType getAGenericType();
}

Недостатком является то, что нет хорошего способа выразить java-компилятору, что NonGenericType расширяет GenericType, хотя концептуально это могло в этом случае, поскольку GenericType никогда не использует D в качестве возвращаемого значения. Вам нужно вручную указать каждый GenericType, который вы хотите продлить.: (

Ответ 6

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

Я думаю, что цель InterfaceA не должна создаваться вообще, потому что один из ее надежных классов является общим. Это то, что вы хотели сказать:

public GenericType<? extends Object> getAGenericType()