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

Posted

技术标签:

【中文标题】有没有办法确保实现接口的类实现静态方法?【英文标题】:Is there a way to make sure classes implementing an Interface implement static methods? 【发布时间】:2011-02-10 23:18:19 【问题描述】:

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


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

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

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

这背后的想法是将几个ParametricFunctions 放在一个包中并使用反射将它们全部列出,允许用户选择例如绘制哪一个。显然,可以提供一个包含可用ParametricFunctions 数组的加载器类,但是每次实现一个新的时,都必须记住在其中添加它。

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

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

【问题讨论】:

为什么 getValue 必须是静态的?如果 getValue 不是静态的,您将能够完全按照您的意愿行事。 然后我必须创建一个类的实例。如果我错了,请纠正我,但关于它的目的似乎在这里没有用。 你的设计有问题。这不是OO。 parameters[] 应该是 Parabola 类的实例字段,在构造函数中设置和检查,并在 getValue() 类中使用。 我真的没有看到实例化这些类的问题,这是最简单的解决方案。如果您希望能够将新的 ParametricFunction 实现动态部署到您的应用程序,您可以在已部署 jar 的清单文件中指明兼容的类。您当然需要一些代码来启用此机制。另一方面,我看不出静态接口方法在这里有什么帮助。 @Michael:但现在假设我想将数据点拟合到用户选择的函数,现在我所要做的就是调用fit(fnName, double[] x, double y[]),Fitter 会找到参数。我知道这不是 OO,但这里有什么优势?好吧,现在问题来了,为什么要把参数放在提供静态拟合功能的Fitter中……好吧,你说服了我,我得重新考虑设计。 【参考方案1】:

这背后的想法是 几个 ParametricFunction 在一个 打包并使用反射列出 它们全部,允许用户选择 例如绘制哪一个。

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

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

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

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

【讨论】:

好吧,我可以做坏事并使用文件系统列出包的目录......对我来说,将所有ParametricFunctions 放入像my.math.function.parametric 这样的包中是非常有意义的。但是配置文件还需要类加载器并提供扩展的东西,例如(我将立即编辑问题)static boolean checkParameters(...) 仍然需要我要求的机制,不是吗? @Tobias:配置文件将有一个特定的名称并列出要加载的类的特定名称 - 没问题。至于那个 checkParameter() 事情 - 那是错误的。您从根本上滥用了 OO 概念。类意味着有多个具有不同状态的实例,而不是静态方法的集合。 如果不是类,我可以将什么用作静态方法的集合?我自己的回答至少能让事情变得更好吗? @MichaelBorgwardt “类意味着具有不同状态的多个实例”。这似乎是一个过于笼统的概括。这不考虑单例模式。【参考方案2】:

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

【讨论】:

@Ha:你的意思是像我自己发布的答案一样?我重新考虑了一下,但对我来说似乎没有必要实例化一个类,因为多个实例的可能性并没有提供任何有用的东西。 是的,你丢失了 16 个字节来存储实例,但速度很快,因为只有构造函数是通过反射调用的。 我在 eclipse 中看到过这种模式。 Mylyn 连接器插件提供了扩展一些抽象类的类,并且只有连接器类的单个实例通过反射在 mylyn 内部的某处创建。你的情况也是如此 - 只有一个反射感知单例类会创建所有必要的实例并将它们放在地图中。 如何在编译时确保一个类有一个默认的、零参数的构造函数?我非常感兴趣! 我不知道如何在编译时检查此类内容。【参考方案3】:

你想做的不行……

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

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

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

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

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

【讨论】:

我永远不想调用 I.staticMethod(),这也是我将其标记为抽象的原因。现在编辑的问题中有一个示例。 所以你会调用 A.staticMethod 或 B.staticMethod... 这样你在编译时就知道你在哪个类上调用了该方法,那么为什么你需要与静态方法有一个通用接口?如果您要在对象实例上调用静态方法(i.staticMethod(无论如何不可能)、a.staticMethod 或 b.staticMethod),那么您最好考虑使用非静态方法... 因为我希望 A 和 B 实现该静态方法肯定,并确保使用该接口的其他人也会这样做。【参考方案4】:

您对自己问题的回答可以进一步简化。保持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 方法。

【讨论】:

+1 绝对没有我的方法复杂,谢谢。但是仍然无法确保一个新类,比如Gaussian,也将实现getInstance() 方法。【参考方案5】:

原因是可读性: 拟合(“抛物线”,xValues,fValues)与。 适合(抛物线.getInstance(),xValues, fValues)与拟合(新抛物线(), x 值,f 值)。我为什么要 定义了一个函数实例 完全由它的论点而没有 内部数据?

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

如果你定义一个对象抛物线,这个对象应该代表一个抛物线,而不是一个检查参数是否正常等的工具箱......

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

【讨论】:

我知道,但这需要一个 fit 函数来每次调用 setParameters(parameters)getValue(x) 而不是简单的 getValue(x, parameters)。另一方面, setParameters 只会为新的拟合而调用,并且为每次评估传递参数是一种浪费......我明白了。 +1 其实为什么不做这样的事情:抛物线抛物线=新抛物线(x;y;z)拟合(抛物线)与公共无效(或你想要的)拟合(参数函数pamFunc)// yourimpl +1 确实如此。但是我在回复 Donal Fellows 的回答时所说的问题呢? ***.com/questions/2689312/…【参考方案6】:

为什么不试试 Java 5 枚举?即:

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

【讨论】:

+1 支持这个想法。但不幸的是,如果我开始包含 BesselFunctions 等,这会导致一个巨大的文件,这在单独的类中可能真的更好...... 您可以创建多个枚举。它们仅用于确保实现是单例的。这种方法的一个自然发展是在 UI 中以某种方式将函数分组到同一个枚举中。 @Ha:我打算建议使用多个枚举,但我认为如果没有它们在同一个文件中,你就不能嵌套或分组它们。虽然我不确定是否有必要在编译时对它们进行分组,但在这种情况下如果你能做到ParametricFunctions.quadratic.ParabolaParametricFunctions.logarithmic.SomeLogFunction 会很酷。不知道我是否对虚构的例子读得太多了;-) @TobiasKienzler(迟到的回复,对于其他人)您还可以“隐藏”其他文件中的类中的处理并保持枚举文件简单。我在图像处理中使用相同的方法,其中我的图像处理器位于枚举文件中,但代码位于其他类中,由枚举的方法调用。【参考方案7】:

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

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

【讨论】:

为什么没有意义?接口要求类不能提供static public String getDescription() 吗?我不会将此与 javadoc 混淆,我的意思是例如一个 GUI,用户选择 ParametricFunction 以适应数据,并且如果单独的名称不那么直观,可能想知道每个函数的作用。然后考虑 ParametricFunction 是更复杂的东西,因此实例化会消耗大量时间,特别是在提供一个包含描述的列表时,比如 100 个 ParametericFunction s,最终只会选择一个. 这毫无意义,因为您在类而不是实例上调用静态方法。两个类没有兴趣共享完全相同的静态方法名称,因为您不从接口调用此方法...您希望 getDescription() 返回什么?类的描述?还是当前实例类的描述? 一些对象系统通过使静态方法成为类对象上的方法来解决这个问题。 Java 没有这样做,决定在这方面更接近 C++。 @Sebastien:为什么两个类没有兴趣共享完全相同的静态方法名称?使用反射这可能是确保该方法存在的唯一方法。我希望getDescription() 返回类的描述。为什么要在不同的实例上改变?这就是为什么我希望这个方法是静态的,并以类似于接口的方式执行。 我最近遇到了一个场景,我必须定义IFileSystemUtil 接口,该接口将实现不同的文件系统特定类。例如,如果我有 XyzFileSystemUtil 实现 IFileSystemUtil,我会希望那些 util 类实现某些功能,因为它们是 util 类,所以它们需要是 static。我相信应该可以通过接口强制执行此要求。【参考方案8】:

@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 肯定和 make 确定其他人使用该界面 因为新班级也会这样做。 – Tobias Kienzler 5 小时前

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

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

【讨论】:

【参考方案9】:

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

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

实际上,既然你需要验证值,而对象甚至没有被构造,为什么你绝对要在模型对象上添加这个验证呢?表单/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);
  ......

您甚至可以将静态验证方法放在函数类中,然后您将拥有: 公共静态布尔 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...) - 使用验证类验证这些参数(静态方法与否) - 如果没有验证 -> 向用户打印消息 - 否则构造对象 - 使用对象

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

【讨论】:

以上是关于有没有办法确保实现接口的类实现静态方法?的主要内容,如果未能解决你的问题,请参考以下文章

PHP 抽象类,接口,抽象方法,静态方法

为啥 C# 不允许静态方法实现接口?

Java 8-接口的默认方法和静态方法

解决办法三层交换机通过静态路由和接口互联,实现不同VLAN间通信的两种方法

静态代理与动态代理

Java中的接口