关于Java:有没有办法确保实现接口的类实现静态方法?

Is there a way to make sure classes implementing an Interface implement static methods?

首先,我阅读了埃里克森的有用回答:"为什么我不能在Java接口中定义静态方法呢?".这个问题不是关于"为什么",而是关于"那怎么办?".编辑:我的原始示例是不正确的,但我将把它留在下面。

虽然我现在确信,在大多数情况下,我想做的是过度杀戮,但有一种情况可能需要这样做:

我再以ParametricFunction为例。现在让我们来看一个复杂的函数,比如贝塞尔函数,其中查找表是合适的。这必须初始化,因此这两个选项将参数直接传递给构造函数或提供init(double[] parameters)。后者的缺点是,getValue(double x)必须检查初始化每个调用(或ArrayIndexOutOfBoundsException必须被视为初始化检查),因此对于时间关键的应用程序,我更喜欢使用构造函数方法:

1
2
3
4
5
6
7
8
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方法。例如

1
2
3
4
5
6
7
8
9
10
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);
}

然后

1
2
3
4
5
6
7
8
9
10
11
public class Parabola implements ParametricFunction {
  /** @return f(x) = parameters[0] * x2 + 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放在一个包中,并使用反射列出所有这些内容,允许用户选择(例如)要绘制的内容。显然,可以提供一个包含可用ParametricFunction的数组的加载程序类,但每次实现一个新的类时,也必须记住将其添加到那里。

编辑:一个称之为

1
2
3
4
5
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});


不能要求类通过接口实现特定的静态方法。它在Java术语中毫无意义。接口强制实现接口的类中存在特定的非静态方法;这就是它们所做的。

最简单的方法肯定是使用某种工厂类来生成其他类的实例。是的,这意味着您在添加新实例时必须记住保持该工厂是最新的,但因为在进行新实现时,首先要做的是测试它(确实要测试它,是吗?)你会很快发现那个问题的!


为什么不尝试Java 5枚举?IE:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public enum ParametricFunctions implements ParametricFunction {
    Parabola() {
        /** @return f(x) = parameters[0] * x2 + 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
}

通过这个,您可以轻松地查找静态函数类型,并且具有编译时安全性,但是仍然允许在其他地方引用接口类型。还可以在枚举类型上放置方法,以允许按名称查找函数。

使用枚举方式编辑文件大小。

在这种情况下,您可以将每个函数定义为它自己的类,即:

1
2
3
4
5
6
7
8
9
10
11
12
public class Parabola implements ParametricFunction {

    /** @return f(x) = parameters[0] * x2 + 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);
    }

}

然后,您可以拥有许多独立的实现文件,并将它们组合成一个更小的、类似枚举的类,通过该类可以静态地访问函数。Ie:

1
2
3
4
5
public class ParametricFunctions {  
    public static final ParametricFunction parabola = new Parabola(),
                                           bessel = new BesselFunction(),
                                           // etc
}

这允许一个地方查找函数,实现保持独立。还可以将它们添加到静态集合中进行名称查找。然后您可以在函数中保持可读性,如另一条注释中所述:

1
2
3
4
5
6
import static ...ParametricFunctions.parabola;
// etc

public void someMethodCallingFit() {
    fit(parabola, xValues, yValues);
}


你对自己问题的回答可以进一步简化。保持ParametricFunction接口不变,将Parabola改为实现ParametricFunction的单件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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实例的目的是简化应用程序。

根据以下问题编辑:

不能使用标准Java构造强制类实现具有给定签名的静态方法。Java中没有抽象的静态方法。

您可以通过编写作为构建一部分运行的单独工具并检查源代码或编译代码来检查静态方法是否实现。但在我看来,这不值得这么做。如果您编译调用它的代码,或者在运行时试图反射地使用它,那么任何缺少的getInstance()都会出现。在我看来,这应该足够好了。

此外,我想不出一个令人信服的理由来解释为什么你需要类是单例的;也就是说,为什么getInstance方法是必要的。


The idea behind this is putting
several ParametricFunction's in a
package and use Reflection to list
them all, allowing the user to pick
e.g. which one to plot.

这将失败,原因更为基本:反射无法列出包中的所有类(因为由于类加载器机制的灵活性,"包中的所有类"不是一个定义良好的集合)。

这种情况的现代解决方案是通过依赖注入框架使其成为应用程序配置的一部分。

Obviously one could provide a loader
class containing an array of the
available ParametricFunction's, but
every time a new one is implemented
one has to remember adding it there,
too.

好吧,对于您的概念,每次实现一个新的概念时,都会强制将其放入同一个包中。通过将其放入配置文件或加载程序类(实际上是同一件事),您可以消除这种限制。


The reason is readability:
fit("Parabola", xValues, fValues) vs.
fit(Parabola.getInstance(), xValues,
fValues) vs. fit(new Parabola(),
xValues, fValues). Why would I want to
have an Instance of function defined
entirely by it's arguments with no
internal data?

实际上,您缺少了一些面向对象编程基础知识…

如果定义一个对象抛物线,这个对象应该表示一个抛物线,而不是一个检查参数的工具箱,等等…

抛物线项应该包含参数(x,y…)并且可以用构造函数传递它们…

1
2
3
4
5
6
double x;
double [] parameters;
public Parabola(double x, double[] parameters) {
  this.x = x;
  this.parameters = parameters;
}

因此,您不应该在函数上使用参数,因为参数现在声明为类成员属性…

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

然后打电话

1
parabolaInstance.getValue();


接口中的构造函数?休斯敦大学?是否可以调用接口i=new接口(double[]参数)?而计算机会在另一时间选择自己的实现吗?这和接口中的静态一样奇怪:d

如你所说,施工前应检查参数…但这并不意味着如果参数不正常,就不能在构造上引发异常。这只是您可以添加的一种安全性,它将确保构建的对象是一致的。但是这样的代码不允许您绕过以前的验证:在构造上引发异常将告诉您"嘿,您有一个bug!"当不验证参数时,只会告诉您"hohoo someone using gui tryed to set a bad value,we'll send him an error message…"

实际上,由于您需要验证这些值,而且对象甚至没有被构造,为什么您绝对要在模型对象上添加这个验证?无论是在验证Java类中的任何地方,都可以执行表单/ GUI/任何验证。只需在另一个名为ParameterFunctionValidationHelper的类中设置一个静态(或非静态)方法,在该类中添加方法和验证实现。

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

不管函数类型是如何表示的(我选择字符串是因为我想您可以从用户界面、Web或GUI获得它…)。它可能是枚举…

您甚至可以在构造对象之后对其进行验证:

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

您甚至可以将静态验证方法放入函数类中,然后您将得到:公共静态布尔验证参数函数(参数函数pamfunc){if(pamfunc instanceof besselfunction)返回besselfunction.validatebessel(pamfunc.getParameters);if(pamfunc instanceof抛物线函数)返回抛物线函数validateParabola(pamfunc.getParameters);}

是的,您将无法在接口中设置静态方法,但无论如何,您将如何调用这种方法?

用代码一样

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

????这毫无意义,因为JVM根本无法知道要使用静态方法的哪个实现,因为您不是从实例调用静态方法,而是从类调用静态方法!如果直接在ParametericFunction类中实现validate方法,这是唯一有意义的,但是无论如何,如果您执行这样的操作,您将必须执行与我以前用InstanceOf显示的完全相同的操作,因为只有PamFunc实例是您必须选择要使用哪种验证的唯一项…

这就是为什么你最好使用一个非静态的方法,把它放在接口中,比如:

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

实际上,你应该做的是:-检索参数(字符串?)从GUI/Web界面/任何-以良好格式分析字符串参数(字符串到int…)-使用验证类验证这些参数(是否为静态方法)-如果没有验证->打印消息给用户-Else构造对象-使用对象

我不认为在接口中有任何需要静态方法的地方…


@Sebastien: Why is there no interest
for both classes to share the exact
same static method name? Using
reflection this might be the only way
to make sure the method exists. I
would like getDescription() to return
the description of the class. Why
should it change on different
instances? That's why I'd like this
method to be static and yet enforce in
an Interface-like way that it is
implemented. – Tobias Kienzler 3

正如我已经说过的,声明方法static意味着您可以直接从类调用它,而不需要类实例。由于调用i.staticMethod()没有意义(如已解释的那样),您只需调用a.staticMethod1()和b.staticMethod2(),它们的名称并不重要,因为您从编译时已知的A或B类调用它们!

如果您希望getDescription返回相同的描述,不管参数函数的实例如何,只需将参数函数设置为抽象类,并直接在此类中实现静态方法。然后您就可以调用a、i或b.getdescription();(甚至a、i或b…)。但它仍然和在A和B中实现它并称之为抛出A或B一样。

从实例调用静态方法不是一个好的实践,也没有任何兴趣,因此应该调用a.meth()或b.meth(),而不是a.meth()或b.meth()。

Because I wanted A and B to implement
that staticMethod for sure and make
sure someone else using the Interface
for a new class will do so, too. –
Tobias Kienzler 5 hours ago

实际上,"其他人"通常不会调用a.meth()或b.meth(),因此,如果他创建了一个类c并希望调用c.meth(),他将永远无法这样做,因为c.meth()未实现或不是静态的…所以他会这样做,或者永远不会调用c.meth(),然后强制开发人员实现永远不会使用的静态函数也是没有意义的…

我不知道我能补充什么…


你想做的不好…

您希望在接口I中定义静态方法,并拥有该接口的一些实现A和B,以及它们自己在接口I中声明的这些静态方法的实现。

想象一下,如果调用i.staticMethod(),计算机将如何知道要做什么????它将使用A或B的实现吗?!!!

在接口中声明一个方法的兴趣是使用多态性,并且能够为不同的对象实现调用这个方法…但是对于静态方法,由于您不从实例调用该方法(实际上您可以但不真正需要……),但是使用classname.xxxmethod,它绝对没有兴趣……

因此,您不必在接口中放置这些静态方法…只需将它们放在这两个实现中,并使用a.staticMethod()和b.staticMethod()调用它们(它们甚至不需要共享相同的方法名!)

我想知道您要如何调用静态方法,您要示例代码来显示吗?


一个解决方案是使所有方法都非静态,并要求类必须具有默认的构造函数。然后您可以轻松地实例化它并调用所需的方法。