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

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

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

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

interface Foo {
    void doFoo();
}

inteface Bar {
    void doBar();
}

Реализации FooBar должны предоставить метод getFooBar(), который возвращает экземпляр типа fullfills 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().

Спасибо за любые другие советы и/или объяснения.

EDIT:

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

Ответы

Ответ 1

Вы можете сделать 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.

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

Ответ 2

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

Я бы сказал, для этого варианта.

Ответ 3

interface FooBar extends Foo, Bar {
    FooBar getFooBar();
}

Ответ 4

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

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

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

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

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

Update:

Проблема заключается в том, что компилятор не знает, что тип 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;
}