Возвращает тип метода для полного заполнения нескольких интерфейсов
Можно ли указать метод, который возвращает объект, реализующий два или несколько интерфейсов?
Скажем, у нас есть следующие интерфейсы:
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;
}