Java - переопределение возвращаемого типа расширенного интерфейса, когда возвращаемый тип использует обобщенные значения для собственных типов параметров метода - PullRequest
13 голосов
/ 20 февраля 2011

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

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

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

В интерфейсе A есть метод, который возвращает универсальный тип.

Универсальный тип будет выглядеть как GenericType<T>.

Основная идея состоит в том, чтобы изменить этот универсальный тип возврата сGenericType<Object> в интерфейсе A в GenericType<String> в интерфейсе A1

Поначалу кажется, что все просто (плохие вещи появятся позже)

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

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

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

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

Как вы видите, мы вынуждены написать GenericType<? extends Object> в самом интерфейсе А, чтобы разрешить переопределение его общими «подклассами».(На самом деле универсальный параметр универсального типа находится в подклассе, а не сам универсальный тип)

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

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

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

Скорее, попытка создать экземпляр A будет отстойной.Чтобы понять, зачем смотреть на этот класс «use 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 );
  }
}

Вы спрашиваете: «А сейчас?»

Он не работает на последних строках.На самом деле параметр «что-то» даже не из типа «Объект», а из Типа «расширяет Объект».Таким образом, вы не можете передать объявленный тип «Объект».Вы вообще ничего не можете передать.

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

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

Или как бы вы обходили такой типовой случай?

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

----------- (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 объединено с "«расширяет объект», почему тип параметра не обязательно должен быть «объектом»?

Разве это не создает больше смысла?

Ответы [ 6 ]

6 голосов
/ 20 февраля 2011

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

Это невозможно, потому что Обобщения Java инвариантны . [1]

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

Вы пытались

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

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

Компилятор не знает, что вы собираетесь делать с параметром типа в 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 голосов
/ 10 февраля 2015

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

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

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

2 голосов
/ 20 февраля 2011

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

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

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

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

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

Пример переопределения универсального метода в универсальном классе.

1 голос
/ 30 июля 2013

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

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 может выполнять только StringSomethingWith (), в то время как GenericType должен иметь возможность делать любой объект () любого объекта. 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, который вы хотите расширить. (

1 голос
/ 20 февраля 2011

Беда в том, что 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 );
  }
}
0 голосов
/ 20 февраля 2011

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

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

public GenericType<? extends Object> getAGenericType()
...