Java generi c метод стирания и наследования - PullRequest
1 голос
/ 02 февраля 2020

Я столкнулся с проблемой с обобщениями javas и переопределяющими методами. Представьте, что у меня глубокая древовидная иерархия классов. Класс верхнего уровня определяет метод foo, который принимает 1 аргумент типа Strategy. Стратегия имеет типовой параметр c.

Каждый класс в моей иерархии классов должен переопределить foo, чтобы ограничить тип стратегии, которую он может передавать, чтобы параметр типа generi c стратегии соответствовал объявленному классу. Ниже приведен пример:

abstract static class TopLevelClass {

    static final Strategy<TopLevelClass> s1 = tlc -> System.out.println(tlc.getTopLevelAtt());

    String getTopLevelAtt() {
        return "TopLevelAtt";
    }

    void foo(Strategy<TopLevelClass> s) {s.bar(this);}
}
static class MidLevelClass extends TopLevelClass {

    static final Strategy<MidLevelClass> s2 = mlc -> System.out.println(mlc.getMidLevelAtt());

    String getMidLevelAtt() {
        return "MidLevelAtt";
    }

    void foo(Strategy<MidLevelClass> s) {s.bar(this);}
}
static class LowLevelClass extends MidLevelClass  {

    static final Strategy<LowLevelClass> s3 = llc -> System.out.println(llc.getTopLevelAtt());

    String getLowLevelAtt() {
        return "LowLevelAtt";
    }

    void foo(Strategy<LowLevelClass> s) {s.bar(this);}
}
static interface Strategy<X> {
    void bar(X x);
}

В этом примере я хочу иметь возможность вызывать foo для экземпляров класса LowLevelClass с любыми из stati c ссылок s1, s2 и s3, определенных в TopLevelClass, MidLevelClass и LowLevelClass соответственно. В идеале мне не нужно вызывать разные методы foo1, foo2 или foo3 в зависимости от аргумента.

Приведенный выше код NOT компилируется в java. Ошибка времени компиляции:

Имя cla sh: Метод foo (Strategy) типа MidLevelClass имеет такое же стирание, что и foo (Strategy) типа TopLevelClass, но не переопределяет его

Я сомневаюсь, что это можно легко решить. Я мог бы просто использовать raw-типы и полагаться на проверки типов во время выполнения, но я бы предпочел сохранить безопасность типов. Что я могу сделать, чтобы достичь этого, не жертвуя иерархией типов или безопасностью типов? Обратите внимание, что передача стратегии в конструкторе НЕ вариант для меня! Должна быть возможность вызывать foo несколько раз в течение срока службы объекта.

Редактировать:

Я понимаю, что этой проблеме, возможно, трудно следовать, не зная обстоятельств, окружающих ее. Я открыл более подробный вопрос, объясняющий причину моей проблемы здесь: Как сделать этот тип шаблона Стратегия-Объект безопасным

Ответы [ 2 ]

3 голосов
/ 02 февраля 2020

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

abstract class TopLevelClass {
    void fooTop(Strategy<TopLevelClass> s) {/*...*/}
}
class MidLevelClass extends TopLevelClass {
    void fooMid(Strategy<MidLevelClass> s) {/*...*/}
}
class LowLevelClass extends MidLevelClass  {
    void fooLow(Strategy<LowLevelClass> s) {/*...*/}
}

Однако, я подозреваю, удаление - не ваша проблема . Вероятно, вы хотите переопределить тот же метод.

Экземпляр Strategy<LowLevelClass> не может быть Strategy<MidLevelClass>, который не может быть стратегией;

Учитывая

Strategy<LowLevelClass> l;
Strategy<MidLevelClass> m;

Тогда вы не можете назначить одно другому.

l = m; // Compile-time fail.
m = l; // Compile-time fail.

И поэтому не имеет смысла делать то же самое с помощью переопределения метода. (Также всегда было верно, что bar(TopLevelClass) не может переопределить bar(MidLevelClass), хотя начиная с 1.5 существуют ковариантные возвращаемые типы.)

Добавьте параметр типа в класс для использования в качестве аргумента типа в методе.

abstract class TopLevelClass<T extends TopLevelClass<T>> {
    void foo(Strategy<T> s) {/*...*/}
}
class MidLevelClass<T extends MidLevelClass<T>> extends TopLevelClass<T> {
    void foo(Strategy<T> s) {/*...*/}
}
class LowLevelClass<T extends LowLevelClass<T>> extends MidLevelClass<T>  {
    void foo(Strategy<T> s) {/*...*/}
}

В обновленном вопросе добавлено использование this в качестве аргумента для вызова Strategy.foo. Это означает, что MidLevelClass должно быть abstract - это не может гарантировать, что foo переопределено. Тип this теперь должен соответствовать параметру типа. Для этого добавьте метод abstract "getThis" (бетон в конкретных подклассах).

    protected abstract X getThis();
...
    @Override protected X getThis() { return this; }

Тип полей static требует подстановочных знаков:

static final Strategy<? extends TopLevelClass<?>> s1 =
    tlc -> System.out.println(tlc.getTopLevelAtt());

(лучшие дизайны предпочитают композицию наследованию.)

1 голос
/ 02 февраля 2020

Я чувствую, что с этим дизайном есть пара трудностей:

  1. Поскольку каждый ваш класс реализует свои собственные Strategy, используя только методы в одном классе, нет необходимости в Strategy.bar() взять экземпляр класса. Эта передача параметра является одной из причин, препятствующих реализации этого аккуратно.
  2. Поскольку все реализации foo() делают абсолютно одно и то же, вам не нужно несколько реализаций.

Вот код, который имеет частичное решение. Частично, потому что в хорошем решении вы должны быть в состоянии передать ссылку TopLevelClass в измененном методе foo(). Если вы можете найти способ для этого, я думаю, это будет просто замечательно. С этим решением иерархия классов не является фактором, так как мы используем определенные c ссылочные типы.

Я прокомментировал измененные части, начиная с "CHANGE".

public class Erasure1{

    public static void main( String[] args ){
        LowLevelClass slc = new LowLevelClass(); //'slc' must be exactly the same type as the instance. This is a limitation with this solution. Ideal thing would have been that this reference be of type TopLevelClass.
        slc.foo( LowLevelClass.s3, slc );
    }

    abstract static class TopLevelClass{

        static final Strategy<TopLevelClass> s1 = tlc -> System.out.println( tlc.getTopLevelAtt() );

        String getTopLevelAtt(){ return "TopLevelAtt"; }

        /* CHANGE 1: It is necessary that the instance of TopLevelClass subtype be passed since 
         * Strategy.bar() doesn't accept wildcards. Changing it to accept a 'T' to pass to bar().
         * Also, since, this is now taking 'T' as a parameter, this method could very well be a 
         * static method in a utility class. */
        <T> void foo( Strategy<T> s, T tlc ){
            s.bar( tlc );
        }
    }

    static class MidLevelClass extends TopLevelClass{
        static final Strategy<MidLevelClass> s2 = mlc -> System.out.println(mlc.getMidLevelAtt());;

        String getMidLevelAtt(){ return "MidLevelAtt"; }

        /* CHANGE 2: Since this method is not doing anything different from its parent, this is not required. */
        //void foo(Strategy<MidLevelClass> s) {s.bar(this);}
    }

    static class LowLevelClass extends MidLevelClass{

        static final Strategy<LowLevelClass> s3 = llc -> System.out.println( llc.getLowLevelAtt() );

        String getLowLevelAtt(){ return "LowLevelAtt"; }

        /* CHANGE 2: Since this method is not doing anything different from its parent, this is not required. */
        //void foo(Strategy<LowLevelClass> s) {s.bar(this);}
    }

    static interface Strategy<X> {
        void bar( X x );
    }
}
...