Как реализовать абстрактные статические методы в Java?

существует множество вопросов о невозможности включения статических абстрактных методов Java. Существует также довольно много обходных путей для этого (недостаток дизайна/прочность дизайна). Но я не могу найти ничего для конкретной проблемы, которую я собираюсь изложить в ближайшее время.

мне кажется, что люди, которые сделали Java, и довольно много людей, которые его используют, не думают о статических методах так, как я, и многие другие, делают-как функции класса или методы, принадлежащие классу и ни к какому объекту. Итак, есть ли другой способ реализации функции класса?

вот мой пример: в математике,группа - это набор объектов, которые могут быть составлены друг с другом с помощью некоторой операции * каким-то разумным образом - например, положительные вещественные числа образуют группу при нормальном умножении (x * y = x × y), а множество целых чисел образуют группу, в которой операция "умножение" - это сложение (m * n = m + n).

естественным способом моделирования этого в Java является определение интерфейса (или абстрактного класса) для групп:

public interface GroupElement
{
  /**
  /* Composes with a new group element.
  /* @param elementToComposeWith - the new group element to compose with.
  /* @return The composition of the two elements.
   */
  public GroupElement compose(GroupElement elementToComposeWith)
}

мы можем реализовать этот интерфейс для двух примеров, которые я привел выше:

public class PosReal implements GroupElement
{
  private double value;

  // getter and setter for this field

  public PosReal(double value)
  {
    setValue(value);
  }

  @Override
  public PosReal compose(PosReal multiplier)
  {
    return new PosReal(value * multiplier.getValue());
  }
}

и

public class GInteger implements GroupElement
{
  private int value;

  // getter and setter for this field

  public GInteger(double value)
  {
    setValue(value);
  }

  @Override
  public GInteger compose(GInteger addend)
  {
    return new GInteger(value + addend.getValue());
  }
}

однако есть еще одно важное свойство, которое имеет группа: каждая группа имеет элемент - элемент e такое, что x * e = x для всех x в группе. Например, элемент identity для положительных значений при умножении равен 1, а элемент identity для целых чисел при добавлении -0. В этом случае имеет смысл иметь метод для каждого реализующего класса следующим образом:

public PosReal getIdentity()
{
  return new PosReal(1);
}

public GInteger getIdentity()
{
  return new GInteger(0);
}

но здесь мы нарваться на проблемы-метод getIdentity не зависит от какого-либо экземпляра объекта и поэтому должен быть объявлен static (действительно, мы можем обратиться к нему из статического контекста). Но если мы положим getIdentity метод в интерфейс, то мы не можем объявить его static в интерфейсе, поэтому это не может быть static в любом классе реализации.

есть ли способ реализовать это getIdentity метод:

  1. согласованность сил над всеми реализации GroupElement, так что каждая реализация GroupElement вынужден включить

7 ответов


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

вы действительно не можете сделать это на Java, но вопрос: вам действительно нужно?

Итак, предположим, вы берете свой текущий вариант реализации статического getIdentity() в каждом из подклассов. Считайте, что вам на самом деле не понадобится этот метод, пока вы использовать и, конечно, если вы попытаетесь использовать его, но он не определен, вы будет получить ошибку компилятора, напоминающую вам определить его.

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

поскольку вы не можете вызывать подклассы статических методов через базовый тип, вы всегда придется позвонить их явно, например GInteger.getIdentity(). И так как компилятор уже будет жаловаться, если вы попытаетесь вызвать GInteger.getIdentity(), когда getIdentity() не определено, или если вы используете его неправильно, вы по существу получаете проверку времени компиляции. Единственное, что вам не хватает, - это возможность обеспечить определение статического метода, даже если вы никогда не используете его в своем коде.

Итак, то, что у вас уже есть, довольно близко.

ваш пример является хорошим примером, который объясняет что вы хочу, но я бы предложил вам придумать пример, когда наличие предупреждения во время компиляции о отсутствующей статической функции является необходимостью; единственное, что я могу придумать, это если вы создаете библиотеку для использования другими, и вы хотите убедиться, что вы не забыли реализовать определенную статическую функцию, но правильное модульное тестирование всех ваших подклассов может поймать это во время компиляции (вы не могли протестировать getIdentity() Если бы это было не так настоящий.)

Примечание: глядя на ваш новый вопрос комментарий: Если вы просите о возможности вызов статический метод дал Class<?>, вы не можете, per se (без отражения) -- но вы все еще можете получить функциональность, которую хотите, как описано в ответ Джованни Ботты; вы пожертвуете проверками времени компиляции для проверок времени выполнения, но получите возможность писать общие алгоритмы с использованием идентификатора. Таким образом, это действительно зависит от вашей конечной цели.


математическая группа имеет только одну характеристическую операцию, однако класс Java может иметь любое количество операций. Поэтому эти два понятия не совпадают.

Я могу представить себе что-то вроде Java class Group состоящий из Set элементов и конкретной операции, которая будет интерфейсом сама по себе. Что-то вроде

public interface Operation<E> {
   public E apply(E left, E right);
}

С этим, вы можете создать свою группу:

public abstract class Group<E, O extends Operation<E>> {
    public abstract E getIdentityElement();
}

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


там могут быть некоторые misunderstaning в ваших рассуждениях. Вы видите, что математическая "группа" такова, как вы ее определяете (если я хорошо помню); но ее элементы не характеризуются тем, что они принадлежат к этой группе. Я имею в виду, что целое число (или реальное) является автономной сущностью, которая также принадлежит группе XXX (среди других ее свойств).

Итак, в контексте программирования я бы разделил определение (class) формы группы, которая ее членов, вероятно, используя дженерики:

interface Group<T> {
    T getIdentity();
    T compose(T, T);
}

еще более аналитическое определение будет:

/** T1: left operand type, T2: right ..., R: result type */
interface BinaryOperator<T1, T2, R> {
    R operate(T1 a, T2 b);
}

/** BinaryOperator<T,T,T> is a function TxT -> T */
interface Group<T, BinaryOperator<T,T,T>> {
    void setOperator(BinaryOperator<T,T,T> op);
    BinaryOperator<T,T,T> getOperator();
    T getIdentity();
    T compose(T, T); // uses the operator
}

все это идея; я на самом деле не прикасался к математике в течение длительного времени, поэтому я могу быть дико неправ.

удачи!


нет способа java сделать это (вы можете сделать что-то подобное в Scala), и все обходные пути, которые вы найдете, основаны на некотором соглашении о кодировании.

типичный способ, которым это делается на Java, - это иметь свой интерфейс GroupElement объявите два статических метода, таких как это:

public static <T extends GroupElement> 
  T identity(Class<T> type){ /* implementation omitted */ }

static <T extends GroupElement> 
  void registerIdentity(Class<T> type, T identity){ /* implementation omitted */ }

вы можете легко реализовать эти методы с помощью класс для экземпляра map или домашние решением. Дело в том, что ты держишь статическая карта элементов идентичности, по одному на каждый GroupElement реализация.

и здесь возникает необходимость в Конвенции: каждый подкласс GroupElement придется статически объявить свой собственный элемент личности, например,

public class SomeGroupElement implements GroupElement{
  static{
    GroupElement.registerIdentity(SomeGroupElement.class, 
      /* create the identity here */);
  }
}

на identity метод вы можете бросить RuntimeException если личность никогда не была зарегистрирована. Это не даст вам статическую проверку, но, по крайней мере, проверку времени выполнения для вашего GroupElement классы.

альтернатива этому немного более многословна и требует, чтобы вы создали экземпляр вашего GroupElement классы только через фабрику, которая также позаботится о возвращении элемента идентификатора (и других подобных объектов / функций):

public interface GroupElementFactory<T extends GroupElement>{
  T instance();
  T identity();
}

это шаблон, обычно используемый в корпоративных приложениях, когда фабрика вводится через некоторые рамки инъекций зависимостей (Guice, Spring) в приложении, и это может быть слишком многословным, сложнее поддерживать и, возможно, перебор для вас.

редактировать: после читая некоторые другие ответы, я согласен, что вы должны моделировать на уровне группы, а не на уровне элемента группы, поскольку типы элементов могут быть разделены между различными группами. Тем не менее, приведенные выше ответы предоставляют общий шаблон для обеспечения описываемого вами поведения.

правка 2: под" соглашением о кодировании " выше я имею в виду статический метод getIdentity в каждом подклассе GroupElement, как говорили некоторые. Этот подход имеет обратную сторону, не позволяя generic алгоритмы, которые будут написаны против группы. Еще раз, лучшим решением для этого является тот, который упоминается в первом редактировании.


Если вам нужна возможность генерировать идентификатор, где класс не известен во время компиляции, первый вопрос: как вы знаете, во время выполнения, какой класс вы хотите? Если класс основан на каком-то другом объекте, то я думаю, что самый чистый способ-определить метод в суперклассе, который означает "получить идентификатор, класс которого такой же, как" какой-то другой объект.

public GroupElement getIdentitySameClass();

это должно быть переопределено в каждом подклассе. Переопределение, вероятно, не будет использовать объект; объект будет использоваться только для выбора правильного getIdentity называть полиморфно. Скорее всего, вам также понадобится static getIdentity в каждом классе (но я не знаю, как компилятор может заставить его писать), поэтому код в подклассе, вероятно, будет выглядеть как

public static GInteger getIdentity() { ... whatever }

@Override
public GInteger getIdentitySameClass() { return getIdentity(); }

С другой стороны, если класс вам необходимо из Class<T> объект, я думаю, вам нужно будет использовать отражение, начиная с getMethod. Или увидеть ответ Джованни, который, я думаю, лучше.


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

public interface Group<MyGroupElement extends GroupElement>{
    public MyGroupElement getIdentity()
}

мы реализуем группы как синглтоны таким образом, мы можем получить доступ getIdentity статически через instance.

public class GIntegerGroup implements Group<GInteger>{

    // singleton stuff
    public final static instance = new GIntgerGroup();
    private GIntgerGroup(){};

    public GInteger getIdentity(){
        return new GInteger(0);
    }
}

public class PosRealGroup implements Group<PosReal>{

    // singleton stuff
    public final static instance = new PosRealGroup();
    private PosRealGroup(){}        

    public PosReal getIdentity(){
        return new PosReal(1);
    }
}

если нам также нужно иметь возможность получить идентификатор из элемента группы, я бы обновил ваш GroupElement интерфейс:

public Group<GroupElement> getGroup();

и GInteger с:

public GIntegerGroup getGroup(){
    return GIntegerGroup.getInstance(); 
}

и PosReal с:

public PosRealGroup getGroup(){
    return PosRealGroup.getInstance(); 
}

" в getIdentity способ не dependonany экземпляр объекта, и, следовательно, должен быть объявлен static"

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

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