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

时间:2010-04-22 08:40:07

标签: java interface static abstract

首先,我阅读了erickson对"Why can’t I define a static method in a Java interface?"的有用回复。这个问题不是关于“为什么”而是关于“当时如何?”。

<小时/> 编辑:我原来的例子不合适,但我会把它留在下面。

虽然我现在确信在大多数情况下我想做的事情是矫枉过正,但有一种情况可能需要它:

我将再次采用ParametricFunction示例。现在让我们采用一个复杂的函数,比如Bessel functions,其中查找表是合适的。必须初始化,因此两个选项将参数直接传递给构造函数或提供init(double[] parameters)。后者的缺点是getValue(double x)必须检查每次调用的初始化(或ArrayIndexOutOfBoundsException必须被视为初始化检查),因此对于时间要求严格的应用程序,我更喜欢构造函数方法:< / p>

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数组的加载器类,但每次实现新的时,都必须记住在那里添加它。

编辑:调用它的一个例子是

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 个答案:

答案 0 :(得分:16)

不能要求类通过接口实现特定的静态方法。在Java术语中它没有任何意义。接口强制在实现接口的类中存在特定的非静态方法;这就是他们的所作所为。

最简单的方法是使用某种工厂类来生成其他工厂的实例。是的,这确实意味着你必须记住在添加新实例时保持该工厂的最新状态,但是,当你做一个新的实现时,你要做的第一件事是测试它(你测试它,是吗?)你'我会很快发现这个问题!

答案 1 :(得分:4)

为什么不试试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
}

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


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

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

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);
}

答案 2 :(得分:3)

  

这背后的想法是放   几个ParametricFunction中的一个   包和使用反射列表   他们所有,允许用户选择   例如哪一个要策划。

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

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

  

显然,人们可以提供装载机   包含数组的类   可用的ParametricFunction,但是   每次实施新的   一个人必须记得在那里添加它,   太

嗯,根据您的概念,每次实施新的概念时,都会被迫将其放入同一个包中。通过将其放入配置文件或加载器类(实际上是同样的事情),您可以删除该限制。

答案 3 :(得分:3)

您对自己问题的回答可以进一步简化。保持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实例的目的简化您的应用

编辑以回答您的问题:

您不能使用标准Java构造来强制类实现具有给定签名的静态方法。在Java中,没有这样的东西是一个抽象的静态方法。

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

此外,我想不出一个令人信服的理由,为什么你需要这个班级成为一个单身人士;即为什么getInstance方法是必要的。

答案 4 :(得分:1)

  

原因是可读性:   适合(“抛物线”,xValues,fValues)vs.   fit(Parabola.getInstance(),xValues,   fValues)vs. fit(新抛物线(),   xValues,fValues)。我为什么要这样做   有一个功能实例定义   完全靠它的论点没有   内部数据?

实际上你缺少一些关于面向对象编程基础知识的东西......

如果你定义一个对象抛物线,这个对象应该代表一个抛物线,而不是一个检查参数的工具箱就可以了......

你的抛物线项应该包含参数(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();

答案 5 :(得分:0)

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

答案 6 :(得分:0)

你想做什么不行......

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

想象一下,如果你打电话给I.staticMethod(),电脑怎么会知道怎么做?它会使用A或B的实现吗?!!

在接口中声明方法的兴趣是使用多态并能够为不同的对象实现调用此方法...但对于静态方法,因为您不从实例调用该方法(实际上您可以但并不是真的需要...)但是对于ClassName.xxxMethod,它绝对没有兴趣......

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

我想知道你想如何调用静态方法,是否要示例代码显示?

答案 7 :(得分:0)

  

@Sebastien:为什么没有兴趣   两个类分享确切的   相同的静态方法名称?运用   反思这可能是唯一的方法   确保方法存在。一世   希望getDescription()返回   班级的描述。为什么   应该改变不同的   实例?这就是我喜欢这个的原因   方法是静态的,但在执行中   它是一种类似接口的方式   实现。 - Tobias Kienzler 3

正如我已经说过声明方法static意味着你可以直接从类中调用它而不需要类实例。因为没有理由调用I.staticMethod()(如已经解释过的),你可以调用A.staticMethod1()和B.staticMethod2(),因为你从A中调用它们,它们的名字根本不重要。 B类,在编译时已知!

如果您希望getDescription返回相同的描述,无论相关的ParametricFunction实例如何,只需将ParametricFunction设为抽象类并直接在此类中实现静态方法。然后你就可以调用A,I或B.getDescription(); (甚至是a,i或b ......)。但它仍然与在A和B中实现它并且称它抛出A或B ......

相同

从实例调用静态方法不是一个好习惯而且没有兴趣,所以你应该调用A.meth()或B.meth()而不是a.meth()或b.meth()< / p>

  

因为我想要A和B来实现   那个staticMethod肯定和make   确定其他人使用界面   对于新班级也会这样做。 -   Tobias Kienzler 5小时前

实际上“其他人”通常不会调用a.meth()或b.meth()因此如果他做了C级并想要调用C.meth(),他将永远无法做到这一点,因为C.meth()没有实现或者不是静态的...所以他会这样做,或者C.meth()永远不会被调用,然后迫使开发人员实现永远不会被使用的静态函数也是没有意义的...

我不知道我能添加什么...

答案 8 :(得分:0)

界面中的构造函数?呃?您是否希望能够调用Interface i = new Interface(double []参数)?而计算机又会选择自己实施吗?这和界面中的静态一样奇怪:D

正如你所说,检查参数应该在施工前完成...... 但这并不意味着如果参数不正确,你不能在构造时引发异常。它只是一个你可以添加的安全性,它将确保构造的对象是连贯的。但是这样的代码不允许你绕过先前的验证:在构造上引发异常将告诉你“嘿,你有一个bug!”虽然没有验证参数只会告诉你“有人使用GUI试图设置一个错误的值,我们会向他发送错误信息......”

实际上,由于您需要验证值,并且对象甚至不构造,为什么您绝对想在模型对象上添加此验证? Form / 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,因为我想你是从用户界面,网络或gui ...它可能是Enum ...

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

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

您甚至可以在函数类中放置静态验证方法,然后您将拥有:     public static boolean 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方法才有意义,但无论如何,如果你做了这样的事情,你必须做的事与我之前用instanceOf完全相同,因为pamFunc的实例是您必须选择的唯一项目,您必须使用哪种验证...

这就是为什么你最好使用非静态方法并将其放在界面中:

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

其实你应该做的是: - 从GUI / Web界面/任何内容中检索参数(String?) - 以良好格式解析String参数(String to int ...) - 使用验证类验证这些参数(静态方法与否) - 如果没有验证 - &gt;向用户打印消息 - 否则构造对象 - 使用对象

我在界面中看不到任何需要静态方法...