30

首先,我阅读了 Erickson 对“为什么我不能在 Java 接口中定义静态方法?”的有用回复。. 这个问题不是关于“为什么”,而是关于“那么如何?”。


编辑:我原来的例子是不合适的,但我会把它留在下面。

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

我再举个ParametricFunction例子。现在让我们看一个复杂的函数,比如Bessel 函数,其中查找表是合适的。这必须被初始化,所以这两个选项是将参数直接传递给构造函数或提供一个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-exception 是可能的,但 acheckParametersValidity(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 标准中不允许这样做,那么最接近这一点的是什么?

这背后的想法是将几个ParametricFunctions 放在一个包中并使用反射将它们全部列出,允许用户选择例如要绘制的一个。显然,我们可以提供一个包含可用 s 数组的加载器类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});

4

9 回答 9

22

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

最简单的方法肯定是拥有某种产生其他实例的工厂类。是的,这确实意味着您必须记住在添加新实例时使该工厂保持最新状态,但是由于您在创建新实现时要做的第一件事就是对其进行测试(您确实对其进行了测试,是吗?)很快就会解决这个问题!

于 2010-04-22T10:23:01.570 回答
4

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

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

}

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

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);
}
于 2010-04-22T10:15:18.050 回答
3

这背后的想法是将几个ParametricFunction 放在一个包中并使用Reflection 将它们全部列出,允许用户选择例如要绘制的一个。

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

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

显然,可以提供一个加载器类,其中包含一组可用的 ParametricFunction,但每次实现一个新的时,都必须记住在其中添加它。

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

于 2010-04-22T08:47:54.967 回答
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 需要成为单例类,您可以摆脱静态方法和属性,并将构造函数公开。

创建实例的目的Parabola为了简化您的应用程序

编辑以回应您的以下问题:

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

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

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

于 2010-04-22T09:47:33.890 回答
1

原因是可读性:fit("Parabola", xValues, fValues) vs. fit(Parabola.getInstance(), xValues, fValues) vs. fit(new Parabola(), 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();
于 2010-04-22T10:09:38.547 回答
0

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

于 2010-04-22T09:17:43.850 回答
0

你想做的不行……

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

试想一下,如果您调用 I.staticMethod() ,计算机将如何知道该怎么做???它会使用A还是B的实现?!

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

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

我想知道你想如何调用你的静态方法,你有示例代码来展示吗?

于 2010-04-22T09:28:43.357 回答
0

@Sebastien:为什么两个类没有兴趣共享完全相同的静态方法名称?使用反射这可能是确保该方法存在的唯一方法。我希望 getDescription() 返回类的描述。为什么要在不同的实例上改变?这就是为什么我希望这个方法是静态的,并且以类似于接口的方式执行它。——托拜厄斯·金茨勒 3

正如我已经说过的,将方法声明为静态意味着您可以直接从类中调用它,而不需要类实例。由于调用 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()

因为我希望 A 和 B 确定实现该 staticMethod 并确保其他人使用新类的接口也会这样做。– Tobias Kienzler 5 小时前

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

我不知道我可以添加什么...

于 2010-04-22T16:43:30.253 回答
0

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

正如您所说,应在构造之前检查参数......但这并不意味着如果参数不正确,您就不能在构造时引发异常。这只是您可以添加的一种安全措施,它将确保构造的对象是连贯的。但是这样的代码不允许你绕过之前的验证:在构造时引发异常会告诉你“嘿,你有一个错误!” 虽然不验证参数只会告诉你“hoho有人使用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 是因为我想您是从用户界面、Web 或 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 ) return BesselFunction.validateBessel(pamFunc.getParameters); if ( pamFunc instanceOf ParabolaFunction ) return ParabolaFunction.validateParabola(pamFunc.getParameters); }

是的,您将无法在界面中设置静态方法,但无论如何您将如何调用这样的方法?

像这样的代码

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

???这是没有意义的,因为 JVM 根本无法知道要使用哪个静态方法的实现,因为您不是从实例而是从类调用静态方法!仅当您直接在 ParametricFunction 类中实现 validate 方法时才有意义,但无论如何,如果您这样做,您将不得不做与我之前用 instanceOf 展示过的完全相同的事情,因为 pamFunc 的实例是您必须选择必须使用哪种验证的唯一项目...

这就是为什么您最好使用非静态方法并将其放在接口中,例如:

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

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

我在接口中看不到任何需要静态方法的地方......

于 2010-04-26T15:17:41.337 回答