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

Можно ли указать метод, который возвращает объект, реализующий два или несколько интерфейсов?

Скажем, у нас есть следующие интерфейсы:

interface FooBar {
    [Foo] & [Bar] getFooBar();
}

interface Foo {
    void doFoo();
}

inteface Bar {
    void doBar();
}

Разработчики FooBar должны предоставить метод getFooBar(), который возвращает экземпляр типа, который заполняет Foo, а также Bar.

До сих пор я пытался сделать это с помощью дженериков:

interface FooBar {
    <T extends Foo & Bar> T getFooBar()
}

class SomeImplementor implements FooBar {
    private FooAndBarImpl fSomeField;

    public <T extends Foo & Bar> T getFooBar() {
        return fSomeField;
    }

}

Учитывая, что FooAndBarImpl является некоторым типом, предоставляемым фреймворком или библиотекой, и реализует Foo и Bar, я думаю, что это должно работать. Однако это не так, потому что «FooAndBarImpl не может быть преобразован в T». Это почему? Контракт, подразумеваемый getFooBar(), на мой взгляд, не нарушен.

Другим решением было бы определить новый интерфейс, расширяющий Foo и Bar, и использовать его в качестве возвращаемого типа. Я просто не вижу особого смысла возвращать пустую обертку для fSomeField в реализации getFooBar().

ИЗМЕНИТЬ:

Был бы признателен, если бы кто-нибудь мог объяснить, почему подход дженериков не работает. Я просто не вижу этого.


person z00bs    schedule 20.01.2011    source источник
comment
обновил мой ответ после того, как вы обновили, чтобы включить (что я считаю) довольно хорошее объяснение проблемы с дженериками   -  person davin    schedule 20.01.2011


Ответы (3)


Вы можете сделать T параметром класса:

class SomeImplementor<T extends Foo & Bar> implements FooBar {
    private T fSomeField;

    public T getFooBar() {
        return fSomeField;
    }

}

Что касается того, почему ваш подход к дженерикам не сработал. Давайте создадим следующие два класса, которые реализуют Foo и Bar:

class A implements Bar, Foo{
   private int a;
   ...
}
class B implements Bar, Foo{
   private String b;
   ...
}
class SomeImplementor implements FooBar {
   private A someField;
   public <T extends Foo & Bar> T getFooBar() {
      return someField;
   }
}

Итак, теперь мы должны быть в состоянии выполнить следующее:

SomeImplementor s = new SomeImplementor();
A a = s.getFooBar();
B b = s.getFooBar();

Хотя getFooBar() возвращает объект типа A, который не имеет допустимого приведения к типу B (откуда возьмется член String?), даже несмотря на то, что B выполняет требование <T extends Foo & Bar>, т. е. является допустимым T.

Короче говоря, компилятор (помните, что дженерики — это механизм времени компиляции) не может гарантировать, что каждый T типа <T extends Foo & Bar> может иметь присвоение ему типа A. Это именно та ошибка, которую вы видите: компилятор не может преобразовать данный A в каждый допустимый T.

person davin    schedule 20.01.2011

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

Я бы сказал, остановитесь на этом варианте.

person Brian    schedule 20.01.2011
comment
И создать обертку, так как тип определяется фреймворком? - person z00bs; 20.01.2011
comment
Не знаете, что вы имеете в виду под оберткой? если fSomeField является экземпляром чего-то, что реализует ваш новый комбинированный интерфейс FooBar, зачем использовать оболочку? - person Brian; 20.01.2011
comment
Его тип предоставляется библиотекой, т.е. я не могу изменить код. - person z00bs; 20.01.2011
comment
Проблема заключается в том, что в ситуации с ОП уже есть классы/интерфейсы, которые являются подклассами Foo и Bar, поэтому, если ОП создала интерфейс, расширяющий Foo и Bar, уже существующие интерфейсы (которые не расширяют интерфейс ОП) не может быть возвращен из метода OP. - person Kröw; 24.06.2018

Вы можете вернуть контейнер, чтобы предоставить Foo и Bar.

public class Container{
   private FooBarBam foobar;
   public Bar asBar(){
      return foobar;
   }
   public Foo asFoo(){
      return foobar;
   }
}

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

Что касается того, почему общий подход не работает: нет способа предоставить тип T, и компилятор не может просто угадать его тип, поэтому разрешение T невозможно.

Ответ Давина выглядит хорошо, но также требует общедоступного класса/интерфейса, который реализует Foo и Bar для работы.

Обновление:

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

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

//Does not compile expecting > after Foo
List<? extends Foo & Bar> getFooBar(){
    final List<FooAndBarImpl> l = new ArrayList<FooAndBarImpl>();
    l.add(new FooAndBarImpl());
    return l;
} 
person josefx    schedule 20.01.2011
comment
Вот чего я не понимаю: компилятор знает, что тип fSomeField реализует Foo и Bar, и поэтому должен сделать вывод, что это подтип Foo и Bar, не так ли? - person z00bs; 20.01.2011