Как мне написать функцию Java, которая возвращает типизированный экземпляр this и работает при расширении?

Это немного ленивый вопрос, но вы получите репутацию так :-)

У меня есть класс Java, который возвращает экземпляры самого себя, чтобы разрешить цепочку (например, ClassObject.doStuff (). doStuff ())

Например:

public class Chainer
{
    public Chainer doStuff()
    {
       /* Do stuff ... */
       return this;
    }
}

Я хотел бы расширить этот класс. Есть ли способ, возможно, используя дженерики, расширить этот класс без необходимости перезаписывать каждую сигнатуру метода?

Например. нет:

public class ChainerExtender extends Chainer
{
    public ChainerExtender doStuff()
    {
       super.doStuff();
       return this;
    }
}

Я пытался:

public class Chainer
{
    public <A extends Chainer> A doStuff()
    {
       /* Do stuff ... */
       return (A)this;
    }
}

public class ChainerExtender extends Chainer
{
    public <A extends Chainer> A doStuff()
    {
       /* Do stuff ... */
       return super.doStuff();
    }
}

Но это не сработало, давая ошибку:

type parameters of <A>A cannot be determined; 
no unique maximal instance exists for type variable A with upper bounds A,Chainer

Я вынужден иметь такие объявления классов, как:

public class Chainer<T extends Chainer<T>> {}
public class ChainerExtender extends Chainer<ChainerExtender>

Согласно этот вопрос?

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
0
4 071
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

Не могли бы вы просто опубликовать полный пример, приводящий к отображаемому вами сообщению об ошибке?

Я просто скомпилировал и без проблем выполнил следующее:

public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        ChainerExtender c = new ChainerExtender();
        c.doStuff().doStuff();
    }

    public static class Chainer
    {
        public <A extends Chainer> A doStuff()
        {
           /* Do stuff ... */
            System.out.println("Chainer");
           return (A)this;
        }
    }

    public static  class ChainerExtender extends Chainer
    {
        /** 
         * @see test.Test.Chainer#doStuff()
         */
        @Override
        public <A extends Chainer> A doStuff()
        {
            System.out.println("ChainerExtender");
           return super.doStuff();
        }
    }

}

Дает:

ChainerExtender
Chainer
ChainerExtender
Chainer

Я неправильно понял эту проблему, пожалуйста, проголосуйте против и прокомментируйте.

Спасибо, хотя я думаю, мне следует использовать super. <ChainerExtender> doStuff ()?

Matt Mitchell 23.10.2008 09:36

Я проверю это, когда у меня будет возможность - спасибо :-) И через некоторое время выйдет голос за: P

Matt Mitchell 23.10.2008 10:16

Почему бы им всем не вернуть интерфейс? Вам нужно будет определить все возможные связанные методы в интерфейсе и предоставить «нулевые» реализации в базовом классе, но вы сможете привязать их к своему сердцу.

public interface IChainer
{
  IChainer doStuff();
  IChainer doSomethingElse();
}

public class Chainer implements IChainer
{
  public IChainer doStuff()
  {
     // really do something
     return this;
  }

  public IChainer doSomethingElse()
  {
      return this; // do nothing
  }
}

public class ChainerExtender extends Chainer
{
   // simply inherits implementation for doStuff()

   public override IChainer doSomethingElse()
   {
       // really do something
       return this;
   }
}

Примечание: у меня могут быть некоторые проблемы с синтаксисом выше. Последние несколько лет я программировал в основном на C#. Надеюсь, вы поняли идею.

Спасибо. Я играл с интерфейсом по другим причинам, но вы правы, это решит проблему, и нет проблем. Я тоже большую часть времени использую C# :-).

Matt Mitchell 23.10.2008 10:14

Мне это не нравится. Предположим, у вас есть некий общедоступный интерфейс и несколько конкретных реализаций, которые вы хотите настроить с помощью свободного программирования. Добавление методов конфигурации, зависящих от реализации, к общему интерфейсу, быстро приведет к ужасному беспорядку.

erickson 10.11.2008 23:41

Я думаю, что простой ответ - «нет, не можешь». Я понимаю, чем это может быть полезно, но я чувствую, что такое использование наследования кричит мне о «неожиданных последствиях». Это только вопрос времени, когда кто-то решит добавить немного дополнительных функций в подкласс, в отличие от простого выполнения super.doStuff(), и следить за потоком управления будет чрезвычайно сложно.

Одно из возможных решений состоит в том, чтобы ваши цепочки (если они используют шаблон строитель) возвращали новые экземпляры разных классов построителей вместо this. Посмотрите, что Стивен Колеборн сделал с построителями даты в JSR 310, что довольно умно с точки зрения API, хотя и неудобно для написания.

Прошло некоторое время с тех пор, как я сделал какие-либо дженерики Java.

Что о :


<T>
public class  Chainer 
{
    public T doStuf()
   {
   }

}

public class ChainerDerived : extends Chainer<ChainerDerived>
{
   public ChainerDerived doStuf()
   {
   }
}

В последнее время я в основном занимаюсь C++, и такие вещи - обычное дело. Поддерживают ли дженерики Java этот тип структуры?

Надеюсь, я понял ваш вопрос.

Я заставил это работать таким образом. Он использует литерал класса, чтобы указать, к какому типу привести во время выполнения (см. Раздел 8 Учебное пособие Гилада Брахи по дженерикам).

public class Chainer {
  public <T extends Chainer> T doStuff (Class<T> foo) {
    /* do stuff */
    return foo.cast (this);
  }
}

public class ChainerExtender extends Chainer {
  public <T extends ChainerExtender> doOtherStuff (Class<T> foo) {
    /* do other stuff */
    return foo.cast (this);
  }
}

Итак, вызовы выглядят так:

Chainer c1 = new Chainer();
c1 = c1.doStuff (c1.getClass());
// ...
ChainerExtender ce1 = new ChainerExtender();
ce1 = ce1.doStuff (ce1.getClass()).doOtherStuff (ce1.getClass());

Одним из недостатков этого метода является то, что, если у вас есть ссылка на Chainer, вы должны явно проверить, действительно ли это объект ChainerExtender, прежде чем вызывать для него doOtherStuff. Однако это имеет смысл, поскольку попытка вызвать метод объекта Chainer во время выполнения приведет к ошибке.

Ответ принят как подходящий

Вы пробовали прямолинейный

public class Chainer
{
    public Chainer doStuff()
    {
       /* Do stuff ... */
       return this;
    }
}

public class ChainerExtender extends Chainer
{
    @Override
    public ChainerExtender doStuff()
    {
       /* Do stuff ... */
       super.doStuff();
       return this;
    }
}

В Java 5 вы можете объявить переопределяющие методы с ко-вариантами возвращаемых типов, что означает именно то, что здесь сказано: подкласс может иметь переопределяющую сигнатуру метода с более конкретным типом возвращаемого значения.

О, интересно - мне придется это проверить, когда у меня будет время просмотреть проект, к которому это относится!

Matt Mitchell 03.11.2008 01:31

Обратите внимание, что "super. <A> doStuff ()" скомпилирует работу и. Он намекает на общие сведения о типе, которого вы ожидаете.

Другие вопросы по теме