如何在Java中实现抽象静态方法?

关于包含静态抽象Java方法的不可能性存在许多问题。 关于此的解决方法(设计缺陷/设计强度)也有很多。 但我找不到任何关于我即将陈述的具体问题。

在我看来,制作Java的人和很多使用它的人并没有像我和其他许多人那样思考静态方法 – 作为类函数或属于类的方法而不是任何对象。 那么还有其他一些实现类函数的方法吗?

这是我的例子:在数学中,一个是一组对象,可以使用某些操作*以某种合理的方式相互组合 – 例如,正实数在正常乘法下形成一组( x * y = x × y ),并且整数集形成一个组,其中’乘法’运算是加法( m * n = m + n )。

在Java中对此进行建模的一种自然方法是为组定义接口(或抽象类):

public interface GroupElement { /** /* Composes with a new group element. /* @param elementToComposeWith - the new group element to compose with. /* @return The composition of the two elements. */ public GroupElement compose(GroupElement elementToComposeWith) } 

我们可以为上面给出的两个例子实现这个接口:

 public class PosReal implements GroupElement { private double value; // getter and setter for this field public PosReal(double value) { setValue(value); } @Override public PosReal compose(PosReal multiplier) { return new PosReal(value * multiplier.getValue()); } } 

 public class GInteger implements GroupElement { private int value; // getter and setter for this field public GInteger(double value) { setValue(value); } @Override public GInteger compose(GInteger addend) { return new GInteger(value + addend.getValue()); } } 

但是,组还有另一个重要的属性:每个组都有一个标识元素 – 一个元素e ,使得组中所有x的 x * e = x 。 例如,乘法下正实数的标识元素是1 ,加法下整数的标识元素是0 。 在这种情况下,为每个实现类创建一个方法是有意义的,如下所示:

 public PosReal getIdentity() { return new PosReal(1); } public GInteger getIdentity() { return new GInteger(0); } 

但是在这里我们遇到了问题 – 方法getIdentity不依赖于对象的任何实例,因此应该声明为static (实际上,我们可能希望从静态上下文中引用它)。 但是如果我们将getIdentity方法放入接口中,那么我们就不能在接口中声明它是static的,所以它在任何实现类中都不能是static

有没有办法实现这个getIdentity方法:

  1. 强制在GroupElement所有实现上保持一致性,以便强制GroupElement每个实现都包含getIdentity函数。
  2. 静态地行事; 即,我们可以获取GroupElement的给定实现的identity元素,而无需实例化该实现的对象。

条件(1)本质上是说’是抽象’而条件(2)是’静态’,我知道staticabstract在Java中是不兼容的。 那么语言中是否有一些可用于执行此操作的相关概念?

基本上你要求的是能够在编译时强制一个类定义一个具有特定签名的给定静态方法。

你不能用Java真正做到这一点,但问题是:你真的需要吗?

因此,假设您采用当前在每个子类中实现静态getIdentity()选项。 考虑到在使用它之前实际上不需要这种方法,当然,如果您尝试使用它但未定义它,您收到编译器错误,提醒您定义它。

如果您定义它但签名不是“正确”,并且您尝试使用它而不是您定义它,那么您也将遇到编译器错误(关于使用无效参数调用它,或返回类型问题等)。 )。

由于您无法通过基类型调用子类静态方法,因此您始终必须显式调用它们,例如GInteger.getIdentity() 。 并且,如果您在未定义getIdentity() GInteger.getIdentity()时尝试调用GInteger.getIdentity() ,或者如果您错误地使用它,编译器就会抱怨,您实际上会获得编译时检查。 当然,您唯一缺少的是能够强制定义静态方法,即使您从未在代码中使用过静态方法。

所以你已经非常接近了。

你的例子是一个很好的例子,可以解释你想要什么 ,但我会挑战你想出一个例子,其中有一个关于缺少静态函数的编译时警告是必要的; 我唯一能想到的就是如果你正在创建一个供别人使用的库,你想确保你不要忘记实现一个特定的静态函数 – 但是对所有子类进行适当的unit testing也可以在编译期间捕获它getIdentity()如果不存在,则无法测试getIdentity() )。

注意:查看您的新问题评论:如果您要求能够在给定Class 调用静态方法,则您本身(没有reflection)也不能 – 但您仍然可以获得所需的function,如在Giovanni Botta的回答中描述; 您将牺牲运行时检查的编译时检查,但能够使用标识编写通用算法。 所以,这实际上取决于你的最终目标。

数学组只有一个特征操作,但Java类可以有任意数量的操作。 因此这两个概念不匹配。

我可以想象类似Java类的Group由一Set元素和一个特定的操作组成,它本身就是一个接口。 就像是

 public interface Operation { public E apply(E left, E right); } 

有了它,你可以建立你的团队:

 public abstract class Group> { public abstract E getIdentityElement(); } 

我知道这并不完全是你的想法,但正如我上面所说,一个数学小组是一个有点不同的概念而不是一个类。

你的推理可能会有一些误解。 你看到一个数学“组”就像你定义它(如果我能记得很清楚); 但它的要素并不是因为它们属于这一群体。 我的意思是整数(或实数)是一个独立的实体,也属于组XXX(在其他属性中)。

因此,在编程的上下文中,我将分离其成员的Groupforms的定义( class ),可能使用generics:

 interface Group { T getIdentity(); T compose(T, T); } 

更具分析性的定义是:

 /** T1: left operand type, T2: right ..., R: result type */ interface BinaryOperator { R operate(T1 a, T2 b); } /** BinaryOperator is a function TxT -> T */ interface Group> { void setOperator(BinaryOperator op); BinaryOperator getOperator(); T getIdentity(); T compose(T, T); // uses the operator } 

这一切都是一个想法; 我很长一段时间没有真正触及数学,所以我可能会非常错误。

玩的开心!

没有java方法可以执行此操作(您可以在Scala中执行类似的操作),并且您将找到的所有变通方法都基于某些编码约定。

在Java中完成此操作的典型方法是让您的接口GroupElement声明两个静态方法,例如:

 public static  T identity(Class type){ /* implementation omitted */ } static  void registerIdentity(Class type, T identity){ /* implementation omitted */ } 

您可以通过使用类到实例映射或选择的本地解决方案轻松实现这些方法。 关键是你要保留一个静态的身份元素映射,每个GroupElement实现一个。

这里需要一个约定: GroupElement每个子类都必须静态声明自己的标识元素,例如,

 public class SomeGroupElement implements GroupElement{ static{ GroupElement.registerIdentity(SomeGroupElement.class, /* create the identity here */); } } 

identity方法中,如果从未注册过身份,则可以抛出RuntimeException 。 这不会给你静态检查,但至少会为你的GroupElement类进行运行时检查。

对此的替代方法稍微冗长一些,并且要求您仅通过工厂实例化GroupElement类,这也将负责返回标识元素(以及其他类似的对象/函数):

 public interface GroupElementFactory{ T instance(); T identity(); } 

这是一种通常在企业应用程序中使用的模式,当工厂通过应用程序中的某个dependency injection框架(Guice,Spring)注入时,它可能过于冗长,难以维护并且可能对您来说过度杀伤。

编辑 :在阅读了其他一些答案之后,我同意您应该在组级别而不是组元素级别进行建模,因为元素类型可以在不同的组之间共享。 尽管如此,上述答案提供了一种强制执行您描述的行为的一般模式。

编辑2 :通过上面的“编码约定”,我的意思是在GroupElement每个子类中都有一个静态方法getIdentity ,如某些人所述。 这种方法的缺点是不允许针对该组编写通用算法。 再次,最好的解决方案是第一次编辑中提到的那个。

如果你需要能够在编译时生成一个不知道类的标识,那么第一个问题是,你怎么知道在运行时你想要什么类? 如果该类基于其他一些对象,那么我认为最干净的方法是在超类中定义一个方法,这意味着“获取一个类与其他一些对象相同的标识”。

 public GroupElement getIdentitySameClass(); 

必须在每个子类中重写。 覆盖可能不会使用该对象; 该对象仅用于选择正确的getIdentity以进行多态调用。 最有可能的是,你还需要在每个类中使用静态getIdentity (但我不知道编译器是否强制编写一个),因此子类中的代码可能看起来像

 public static GInteger getIdentity() { ... whatever } @Override public GInteger getIdentitySameClass() { return getIdentity(); } 

另一方面,如果您需要的类来自Class对象,我认为您需要使用以getMethod开头的reflection。 或者看看Giovanni的答案,我认为这个答案更好。

我们都同意,如果你想实现组,你将需要一个组接口和类。

 public interface Group{ public MyGroupElement getIdentity() } 

我们将组实现为单例,因此我们可以通过instance静态访问getIdentity

 public class GIntegerGroup implements Group{ // singleton stuff public final static instance = new GIntgerGroup(); private GIntgerGroup(){}; public GInteger getIdentity(){ return new GInteger(0); } } public class PosRealGroup implements Group{ // singleton stuff public final static instance = new PosRealGroup(); private PosRealGroup(){} public PosReal getIdentity(){ return new PosReal(1); } } 

如果我们还需要能够从组元素中获取标识,我将使用以下命令更新您的GroupElement接口:

 public Group getGroup(); 

和GInteger:

 public GIntegerGroup getGroup(){ return GIntegerGroup.getInstance(); } 

和PosReal:

 public PosRealGroup getGroup(){ return PosRealGroup.getInstance(); } 

“方法getIdentity不依赖于对象的任何实例,因此应该声明为static”

实际上,如果它不依赖于任何实例,它只能返回一些常量值,它不必是静态的。

仅仅因为静态方法不依赖于实例,并不意味着您应该始终将它用于此类情况。