С какой целью это может понадобиться? Например, если хочется использовать "текучий интерфейс". Правда, если часть методов разместить в родительском классе, то для них не получится сделать полноценный «текучий интерфейс» — эти методы требуется приводить (кастовать) к дочернему.
Казалось бы, печаль да и только, но если очень хочется, то ведь можется. Поэтому, давайте слегка нарушим правило и «известим» родительский класс о его наследниках.
Итак, имеется исходное состояние (коммит):
1) интерфейс
2) родительский класс
1) класс-наследник
А так же, напишем небольшой тестовый класс, в котором опишем три возможных варианта вызова методов
И вот здесь-то и кроется загвозка в коде — вместо того, чтобы вызвать метод
Что же, давайте исправлять это «недоразумение».
Для начала, изменим объявление интерфейса — вынесем Generic-тип с уровня методов на уровень интерфейса:
Далее внесём изменения в объявление класса
И заключительный шаг изменений в основном коде — изменим объявление в дочернем классе:
Теперь ничто не мешает нам переписать код в тесте с полноценным использованием «текучего интерфейса»:
было:
Коммит с результатом
PS: Конечно же мы ничего не нарушили и родительский класс как ничего не знал, так и остался в неведении о своих наследниках, но… Но зато он теперь может вернуть из метода тот же тип, что у его наследника, что, собственно, и требовалось.
UPD.0: Добавил пример для комментария
UPD.1: Добавил пример для комментария
Казалось бы, печаль да и только, но если очень хочется, то ведь можется. Поэтому, давайте слегка нарушим правило и «известим» родительский класс о его наследниках.
Итак, имеется исходное состояние (коммит):
1) интерфейс
IChildParam
, который знает о всех требуемых методах2) родительский класс
BaseClass
1) класс-наследник
ChildClass
public interface IChildParam {
int getBaseParam();
int getChildParam();
<T> T setBaseParam(int i);
<T> T setChildParam(int i);
}
public abstract class BaseClass implements IChildParam {
private int baseParam;
public int getBaseParam() {
return baseParam;
}
public BaseClass setBaseParam(int i) {
this.baseParam = i;
return this;
}
}
public class ChildClass extends BaseClass {
private int childParam;
public int getChildParam() {
return childParam;
}
public ChildClass setChildParam(int i) {
this.childParam = i;
return this;
}
}
А так же, напишем небольшой тестовый класс, в котором опишем три возможных варианта вызова методов
setBaseParam
и setChildParam
:public class ClassTest {
public static final int BASE_PARAM = 1;
public static final int CHILD_PARAM = 2;
@DataProvider(name = "data")
public static Object[][] data() {
final ChildClass item0 = (ChildClass) new ChildClass().setBaseParam(BASE_PARAM);
item0.setChildParam(CHILD_PARAM);
final ChildClass item1 = (ChildClass) new ChildClass().setChildParam(CHILD_PARAM).setBaseParam(BASE_PARAM);
final ChildClass item2 = ((ChildClass) new ChildClass().setBaseParam(BASE_PARAM)).setChildParam(CHILD_PARAM);
return new Object[][]{{item0}, {item1}, {item2}};
}
@Test(dataProvider = "data")
public void testChildClass(final IChildParam item) throws Exception {
Assert.assertEquals(item.getBaseParam(), BASE_PARAM);
Assert.assertEquals(item.getChildParam(), CHILD_PARAM);
}
}
И вот здесь-то и кроется загвозка в коде — вместо того, чтобы вызвать метод
setChildParam
сразу же после вызова метода setBaseParam
, приходится предварительно делать приведение типа и только потом вызывать setChildParam
. Да и в любом случае нам потребуется явное приведение типа к ChildClass
Что же, давайте исправлять это «недоразумение».
Для начала, изменим объявление интерфейса — вынесем Generic-тип с уровня методов на уровень интерфейса:
public interface IChildParam<T> {
int getBaseParam();
int getChildParam();
T setBaseParam(int i);
T setChildParam(int i);
}
Далее внесём изменения в объявление класса
BaseClass
, добавив ему Generic-тип «самого себя же»:public abstract class BaseClass<T extends BaseClass> implements IChildParam<T> {
private int baseParam;
public int getBaseParam() {
return baseParam;
}
public T setBaseParam(int i) {
this.baseParam = i;
return (T) this;
}
}
И заключительный шаг изменений в основном коде — изменим объявление в дочернем классе:
public class ChildClass extends BaseClass<ChildClass> {
private int childParam;
public int getChildParam() {
return childParam;
}
public ChildClass setChildParam(int i) {
this.childParam = i;
return this;
}
}
Теперь ничто не мешает нам переписать код в тесте с полноценным использованием «текучего интерфейса»:
было:
final ChildClass item0 = (ChildClass) new ChildClass().setBaseParam(BASE_PARAM);
item0.setChildParam(CHILD_PARAM);
final ChildClass item1 = (ChildClass) new ChildClass().setChildParam(CHILD_PARAM).setBaseParam(BASE_PARAM);
final ChildClass item2 = ((ChildClass) new ChildClass().setBaseParam(BASE_PARAM)).setChildParam(CHILD_PARAM);
стало:final ChildClass item0 = new ChildClass().setBaseParam(BASE_PARAM);
item0.setChildParam(CHILD_PARAM);
final ChildClass item1 = new ChildClass().setChildParam(CHILD_PARAM).setBaseParam(BASE_PARAM);
final ChildClass item2 = new ChildClass().setBaseParam(BASE_PARAM).setChildParam(CHILD_PARAM);
Коммит с результатом
PS: Конечно же мы ничего не нарушили и родительский класс как ничего не знал, так и остался в неведении о своих наследниках, но… Но зато он теперь может вернуть из метода тот же тип, что у его наследника, что, собственно, и требовалось.
UPD.0: Добавил пример для комментария
UPD.1: Добавил пример для комментария