Есть ли способ убедиться, что классы, реализующие интерфейс, реализуют статические методы? - PullRequest
21 голосов
/ 22 апреля 2010

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


Изменить: мой оригинальный пример был некорректен, но я оставлю его ниже.

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

Я снова возьму пример ParametricFunction. Теперь давайте возьмем сложную функцию, такую ​​как функции Бесселя , для которой подходит таблица соответствия. Это должно быть инициализировано, поэтому две опции передают параметры непосредственно в конструктор или предоставляют init(double[] parameters). Последний имеет тот недостаток, что getValue(double x) должен проверять инициализацию при каждом вызове (или ArrayIndexOutOfBoundsException должен рассматриваться как проверка инициализации), поэтому для критичных ко времени приложений я бы предпочел метод-конструктор:

interface ParametricFunction {
  public double getValue(double x);
}

class BesselFunction implements ParametricFunction {
  public BesselFunction(double[] parameters) { ... }
  public double getValue(double x) { ... }
}

Что касается другой проблемы - невозможности использования конструкторов в интерфейсах. Что бы там было хорошим решением? Конечно, я мог бы использовать подход init(double[] parameters), но я упомянул причину, почему нет.
(Правка: ОК, здесь подойдет абстрактный класс, реализующий интерфейс)

Теперь давайте предположим, что ParametricFunction допускает только определенные параметры, например, целые положительные числа. Как проверить правильность параметров, переданных конструктору? Бросать исключение IllegalArgument было бы возможно, но checkParametersValidity(double[] parameters) кажется намного более удобным. Но проверку параметров необходимо выполнить до начала строительства, поэтому это должен быть статический метод. И именно здесь мне бы очень хотелось узнать, как каждый класс, реализующий интерфейс ParametricFunction, действительно определяет этот статический метод.

Я знаю, что этот пример довольно искусственный, и причина не просто использования метода init через интерфейс спорна, я все же хотел бы знать ответ. Считайте это академическим вопросом, если вам это не нравится.

(оригинальный пример)

Так что в основном я хочу, чтобы один интерфейс предоставлял как обычные методы, так и, например, getSimilarObject метод. Для (вымышленного) примера

public interface ParametricFunction {
  /** @return f(x) using the parameters */
  static abstract public double getValue(double x, double[] parameters);

  /** @return The function's name */
  static abstract public String getName();

  /** @return Whether the parameters are valid  [added on edit] */
  static abstract public boolean checkParameters(double[] parameters);
}

, а затем

public class Parabola implements ParametricFunction {
  /** @return f(x) = parameters[0] * x² + parameters[1] * x + parameters[2] */
  static public double getValue(double x, double[] parameters) {
    return ( parameters[2] + x*(parameters[1] + x*parameters[0]));
  }
  static public String getName() { return "Parabola"; }
  // edit:
  static public boolean checkParameters(double[] parameters) {
    return (parameters.length==3);
  }
}

Так как это не разрешено в текущем стандарте Java, что ближе к этому?

Идея, лежащая в основе этого, состоит в том, чтобы поместить несколько ParametricFunction в пакет и использовать Reflection, чтобы перечислить их все, что позволяет пользователю выбирать, например, какой сюжет. Очевидно, что можно предоставить класс загрузчика, содержащий массив доступных ParametricFunction s, но каждый раз, когда внедряется новый, нужно также не забывать добавлять его туда.

edit: пример для вызова это

public double evaluate(String fnName, double x, double parameters) throws (a lot) {
  Class<ParametricFunction> c = (Class<ParametricFunction>) ClassLoader.getSystemClassLoader().loadClass(fnName);
  Method m = c.getMethod("getValue", x, parameters);
  return ((double) m.invoke(null));
}

и звонят evaluate("Parabola", 1, new double[]{1,2,0});.

Ответы [ 9 ]

16 голосов
/ 22 апреля 2010

Вы не можете требовать, чтобы классы реализовывали определенные статические методы через интерфейс. Это просто не имеет смысла в терминах Java. Интерфейсы вызывают наличие определенных нестатических методов в классах, которые реализуют интерфейс; это то, что они делают.

Самым простым способом, безусловно, является наличие какого-то фабричного класса, который создает экземпляры других. Да, это означает, что вы должны помнить, чтобы поддерживать эту фабрику в актуальном состоянии при добавлении новых экземпляров, но поскольку первое, что вы делаете, когда создаете новую реализацию, это проверяете ее (вы проверяете ее, да?), Вы ' Я очень быстро пойму эту проблему!

4 голосов
/ 22 апреля 2010

Почему бы не попробовать Java 5 enum? то есть:

public enum ParametricFunctions implements ParametricFunction {
    Parabola() {
        /** @return f(x) = parameters[0] * x² + parameters[1] * x + parameters[2] */
        public double getValue(double x, double[] parameters) {
            return ( parameters[2] + x*(parameters[1] + x*parameters[0]));
        }

        public String getName() { return "Parabola"; }

        public boolean checkParameters(double[] parameters) {
            return (parameters.length==3);
        }
    },

    // other functions as enum members
}

С этим вы можете легко просматривать статические типы функций и с безопасностью во время компиляции, но все же разрешать ссылаться на тип интерфейса в другом месте. Вы также можете поместить метод в тип enum, чтобы разрешить поиск функции по имени.


ПРАВКА для вопросов о размере файла с путем enum.

В этом случае вы можете определить каждую функцию как ее собственный класс, то есть:

public class Parabola implements ParametricFunction {

    /** @return f(x) = parameters[0] * x² + parameters[1] * x + parameters[2] */
    public double getValue(double x, double[] parameters) {
        return ( parameters[2] + x*(parameters[1] + x*parameters[0]));
    }

    public String getName() { return "Parabola"; }

    public boolean checkParameters(double[] parameters) {
        return (parameters.length==3);
    }

}

Тогда вы можете иметь много отдельных файлов реализации и объединить их в один, меньший, подобный перечислимому классу, через который функции могут быть доступны статически. То есть:

public class ParametricFunctions {  
    public static final ParametricFunction parabola = new Parabola(),
                                           bessel = new BesselFunction(),
                                           // etc
}

Это позволяет в одном месте искать функции, а реализация остается отдельной. Вы также можете добавить их в статическую коллекцию для поиска по имени. Затем вы можете сохранить читабельность в своих функциях, как указано в другом комментарии:

import static ...ParametricFunctions.parabola;
// etc

public void someMethodCallingFit() {
    fit(parabola, xValues, yValues);
}
3 голосов
/ 22 апреля 2010

Ваш ответ на ваш собственный вопрос может быть упрощен в дальнейшем. Сохраните интерфейс ParametricFunction как есть и замените Parabola на единый объект, реализующий ParametricFunction:

public class Parabola implements ParametricFunction {
  private static Parabola instance = new Parabola();

  private Parabola() {}

  static public ParametricFunction getInstance() {
    return instance;
  }

  public double getValue(double x, double[] parameters) {
    return ( parameters[2] + x*(parameters[1] + x*parameters[0]));
  }
  public String getName() { return "Parabola"; }
  public boolean checkParameters(double[] parameters) {
    return (parameters.length==3);
  }
}

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

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

РЕДАКТИРОВАТЬ в ответ на ваш вопрос ниже:

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

Вы можете проверить, что статический метод реализован, написав отдельный инструмент, который запускается как часть вашей сборки и проверяет либо исходный код, либо скомпилированный код. Но ИМО, это не стоит усилий. Любой пропущенный getInstance() будет отображаться, если вы скомпилируете код, который его вызывает, или во время выполнения, если вы попытаетесь использовать его рефлексивно. На мой взгляд, этого должно быть достаточно.

Кроме того, я не могу придумать убедительную причину, по которой вам нужен класс, чтобы быть одиночкой; то есть, почему необходим getInstance метод.

3 голосов
/ 22 апреля 2010

Идея, лежащая в основе этого, состоит в том, чтобы поместить несколько ParametricFunction в пакет и использовать Reflection, чтобы перечислить их все, позволяя пользователю выбрать, например, какой из них построить.

Это не удастся дляболее общая причина: отражение не предлагает способа перечислить все классы в пакете (поскольку «все классы в пакете» не являются четко определенным набором из-за гибкости механизма загрузчика классов).

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

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

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

1 голос
/ 22 апреля 2010

Причина читабельности: подходят ("Парабола", xValues, fValues) против fit (Parabola.getInstance (), xValues, fValues) против соответствия (новая парабола (), xValues, fValues). Почему я хочу иметь определенный экземпляр функции полностью его аргументы без внутренние данные?

На самом деле вы упускаете что-то из основ ориентированного объектного программирования ...

Если вы определяете объект Parabola, этот объект должен представлять Parabola, а не набор инструментов для проверки параметров в порядке и т. Д. *

Ваш элемент Parabola должен содержать параметры (x, y ...), и вы можете передать их с помощью конструктора ...

double x;
double [] parameters;
public Parabola(double x, double[] parameters) {
  this.x = x;
  this.parameters = parameters;
}

Таким образом, вы не должны использовать параметры в вашей функции, поскольку параметры теперь объявлены как атрибуты члена класса ...

public double getValue() {
  return ( this.parameters[2] + x*(this.parameters[1] + x*this.parameters[0]));
}

Тогда просто позвоните

parabolaInstance.getValue();
0 голосов
/ 26 апреля 2010

Конструктор в интерфейсе? э? Хотели бы вы иметь возможность вызывать интерфейс i = новый интерфейс (параметры double [])? А компьютер в другой раз сам выберет реализацию? Это так же странно, как статический интерфейс: D

Как вы сказали, проверка параметров должна быть сделана до начала строительства ... Но это не значит, что вы не можете вызвать исключение при построении, если параметры не в порядке. Вы можете добавить только безопасность, которая будет гарантировать, что построенный объект будет согласованным. Но такой код не позволяет вам обойти предыдущую проверку: повышение исключения при создании скажет вам «эй, у вас ошибка!» не проверяя параметры, вы просто скажете: «Кто-то, кто использовал графический интерфейс, попытался установить неверное значение, мы отправим ему сообщение об ошибке ...»

На самом деле, поскольку вам нужно проверить значения, а объект даже не сконструирован, почему вы абсолютно хотите добавить эту проверку на объект модели? Форма / Gui / любая проверка может быть выполнена где угодно, в классе проверки Java ... Просто установите статический (или нет) метод в другом классе с именем ParametricFunctionValidationHelper, где вы добавляете свой метод и реализацию проверки.

public static boolean validateParametricFunction(String functionType, double[] parameters) {
  if ( functionType.equals("bessel") ) return validateBessel(parameters);
  if ( functionType.equals("parabola") ) return validateParabola(parameters);
}

Неважно, как представлен ваш functionType (я выбираю String, потому что, я полагаю, вы получаете его из пользовательского интерфейса, Интернета или графического интерфейса ... это мог быть Enum ...

Вы даже можете проверить объект после его создания:

public static boolean validateParametricFunction(ParametricFunction pamFunc) {
  if ( pamFunc instanceOf BesselFunction ) return validateBessel(pamFunc.getParameters);
  ......
}

Вы даже можете поместить статические методы проверки в классы функций, и тогда у вас будет: public static логическое validateParametricFunction (ParametricFunction pamFunc) { if (pamFunc instanceOf BesselFunction) возвращает BesselFunction.validateBessel (pamFunc.getParameters); if (pamFunc instanceOf ParabolaFunction) возвращает ParabolaFunction.validateParabola (pamFunc.getParameters); }

Да, вы не сможете установить статический метод в интерфейсе, но как бы вы назвали такой метод?

С кодом типа

public static boolean validateParametricFunction(ParametricFunction pamFunc) {
  return ParametricFunction.validate(pamFunc);
}

??? В этом нет никакого смысла, потому что JVM вообще не сможет узнать, какую реализацию статического метода использовать, поскольку вы вызываете статический метод не из экземпляра, а из класса! Это имеет смысл только в том случае, если вы реализуете метод validate непосредственно в классе ParametricFunction, но в любом случае, если вы сделаете такую ​​вещь, вам придется делать то же самое, что я показал вам ранее с instanceOf, поскольку экземпляр pamFunc это единственный элемент, который вам нужно будет выбрать, какой тип проверки вы будете использовать ...

Вот почему вам лучше использовать нестатический метод и поместить его в интерфейс, например:

public static boolean validateParametricFunction(ParametricFunction pamFunc) {
  return pamFunc.validate();
}

На самом деле вы должны сделать следующее: - Получить параметры (String?) Из GUI / веб-интерфейса / что-нибудь - Разобрать параметры String в хорошем формате (String to int ...) - Проверьте эти параметры с помощью класса проверки (статический метод или нет) - Если нет проверки -> распечатать сообщение для пользователя - остальное построить объект - Используйте объект

Я нигде не вижу необходимости в статическом методе интерфейса ...

0 голосов
/ 22 апреля 2010

@ Себастьян: Почему нет интереса для обоих классов, чтобы поделиться точным то же имя статического метода? С помощью отражение это может быть единственным способом чтобы убедиться, что метод существует. я хотел бы вернуть getDescription () описание класса. Зачем должен ли он меняться на разные случаи? Вот почему я хотел бы это метод быть статичным и в то же время обеспечивать соблюдение Интерфейс, как это реализованы. - Тобиас Кинцлер 3

Как я уже сказал, объявление статического метода означает, что вы можете вызывать его напрямую из класса и не нуждаться в экземпляре класса. Поскольку нет смысла вызывать I.staticMethod () (как уже объяснено), вы можете просто вызывать A.staticMethod1 () и B.staticMethod2 (), их имя не имеет значения вообще, так как вы вызываете их из Класс B, известный во время компиляции!

Если вы хотите, чтобы getDescription возвращало одно и то же описание, независимо от того, какой экземпляр ParametricFunction задействован, просто сделайте ParametricFunction абстрактным классом и реализуйте статические методы непосредственно в этом классе. Тогда вы сможете вызывать A, I или B.getDescription (); (даже а, я или б ...). Но это то же самое, что реализовать его в A и B и назвать его бросившим A или B ...

Вызов статического метода из экземпляра не является хорошей практикой и не представляет интереса, поэтому вы должны вызывать A.meth () или B.meth (), а не a.meth () или b.meth ()

Потому что я хотел, чтобы А и Б реализовали это staticMethod наверняка и сделать уверен, что кто-то еще использует интерфейс для нового класса тоже так сделаю. - Тобиас Кинцлер 5 часов назад

На самом деле «кто-то другой» обычно не вызывает a.meth () или b.meth (), поэтому, если он создает класс C и хочет вызвать C.meth (), он никогда не сможет этого сделать, потому что C.meth () не реализован или не является статичным ... поэтому он будет делать это, иначе C.meth () никогда не будет вызываться, и тогда нет смысла заставлять разработчиков разрабатывать статические функции, которые никогда не будут использоваться ...

Я не знаю, что я могу добавить ...

0 голосов
/ 22 апреля 2010

То, что вы хотите сделать, не в порядке ...

Вы хотите определить статические методы в интерфейсе I, и у вас есть несколько реализаций A и B этого интерфейса с их собственной реализацией этих статических методов, объявленных в интерфейсе I.

Только представьте, как компьютер будет знать, что делать, если вы вызываете I.staticMethod () ??? будет ли он использовать реализацию A или B? !!

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

Таким образом, вам не нужно помещать эти статические методы в интерфейс ... просто поместите их в обе реализации и вызовите их с помощью A.staticMethod () и B.staticMethod () (и они даже не нужны использовать одно и то же имя метода!)

Интересно, как вы хотите вызывать статический метод, пример кода для показа?

0 голосов
/ 22 апреля 2010

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

...