С какой целью это может понадобиться? Например, если хочется использовать "текучий интерфейс". Правда, если часть методов разместить в родительском классе, то для них не получится сделать полноценный «текучий интерфейс» — эти методы требуется приводить (кастовать) к дочернему.
Казалось бы, печаль да и только, но если очень хочется, то ведь можется. Поэтому, давайте слегка нарушим правило и «известим» родительский класс о его наследниках.
Итак, имеется исходное состояние (коммит):
1) интерфейс
2) родительский класс
1) класс-наследник
А так же, напишем небольшой тестовый класс, в котором опишем три возможных варианта вызова методов
И вот здесь-то и кроется загвозка в коде — вместо того, чтобы вызвать метод
Что же, давайте исправлять это «недоразумение».
Для начала, изменим объявление интерфейса — вынесем Generic-тип с уровня методов на уровень интерфейса:
Далее внесём изменения в объявление класса
И заключительный шаг изменений в основном коде — изменим объявление в дочернем классе:
Теперь ничто не мешает нам переписать код в тесте с полноценным использованием «текучего интерфейса»:
было:
Коммит с результатом
PS: Конечно же мы ничего не нарушили и родительский класс как ничего не знал, так и остался в неведении о своих наследниках, но… Но зато он теперь может вернуть из метода тот же тип, что у его наследника, что, собственно, и требовалось.
UPD.0: Добавил пример для комментария
UPD.1: Добавил пример для комментария
Казалось бы, печаль да и только, но если очень хочется, то ведь можется. Поэтому, давайте слегка нарушим правило и «известим» родительский класс о его наследниках.
Итак, имеется исходное состояние (коммит):
1) интерфейс
IChildParam, который знает о всех требуемых методах2) родительский класс
BaseClass1) класс-наследник
ChildClasspublic 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: Добавил пример для комментария
