Возврат подкласса объектов с помощью дженериков

С абстрактным классом я хочу определить метод, который возвращает "this" для подклассов:

public abstract class Foo {
    ...
    public <T extends Foo> T eat(String eatCake) {
        ...
        return this;
    }
}  

public class CakeEater extends Foo {}

Я хочу иметь возможность делать такие вещи, как:

CakeEater phil = new CakeEater();
phil.eat("wacky cake").eat("chocolate cake").eat("banana bread");

Возможно, банановый хлеб вызовет IllegalArgumentException с сообщением "Не торт!"

Ответ 1

public abstract class Foo<T extends Foo<T>>  // see ColinD comment
{
    public T eat(String eatCake) 
    {
        return (T)this;
    }
}

public class CakeEater extends Foo<CakeEater> 
{
    public void f(){}
}

Изменить

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

Другое предлагаемое решение с ковариантным типом возврата должно сделать то же самое - попросить разработчиков подкласса на простом английском языке вернуть тип this. Это требование не может быть задано путем статического ввода.

Ответ 2

Со вкусом подход с точки зрения клиента (который обычно является тем, который вы хотите принять) заключается в использовании ковариантных типов возврата, которые были добавлены для поддержки дженериков, как указывает Майкл Баркер.

Немного менее со вкусом, но более со вкусом, чтобы бросить добавить метод getThis:

protected abstract T getThis();

public <T extends Foo> T eat(String eatCake) {
    ...
    return getThis();
}

Ответ 3

Я не думаю, что вам нужны общие шаблоны. Java 5 (и более поздние версии) имеет ковариантные типы возврата, например:

public abstract class Foo {
    ...
    public Foo eat(String eatCake) {
        ...
        return this;
    }
}  

public class CakeEater extends Foo {

    public CakeEater eat(String eatCake) {
        return this;
    }
}

Ответ 4

Подход, который я использовал ранее для достижения аналогичного поведения, заключается в том, чтобы подкласс передал свой тип в конструктор (генерализованного) родительского типа. В качестве отказа от ответственности я создавал подклассы "на лету", и наследование было чем-то вроде обмана, чтобы сохранить мое поколение кода простым, так как всегда мой первый инстинкт - попытаться полностью удалить отношения extends.