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

2022-09-04 20:55:55

关于是否不可能包含静态抽象 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);
}

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

有没有办法实现此方法:getIdentity

  1. 强制 在所有实现上保持一致性,以便 每个实现都强制包含一个函数。GroupElementGroupElementgetIdentity
  2. 静态行为;也就是说,我们可以获取给定实现的标识元素,而无需为该实现实例化对象。GroupElement

条件(1)本质上是说“是抽象的”,条件(2)是说“是静态的”,我知道这一点,并且在Java中是不兼容的。那么,语言中是否有一些相关的概念可以用来做到这一点呢?staticabstract


答案 1

从本质上讲,您所要求的是在编译时强制类使用特定签名定义给定静态方法的能力。

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

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

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

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

所以你已经拥有的已经非常接近了。

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

注意:看看你的新问题评论:如果你要求调用给定的静态方法的能力,你本身(没有反射)无法 - 但你仍然可以获得你想要的功能,如Giovanni Botta的答案中所述;您将牺牲运行时检查的编译时检查,但获得使用标识编写通用算法的能力。所以,这真的取决于你的最终目标。Class<?>


答案 2

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

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

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

这样,您就可以构建自己的组:

public abstract class Group<E, O extends Operation<E>> {
    public abstract E getIdentityElement();
}

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