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

时间:2014-03-17 15:44:58

标签: java oop interface static abstract

关于包含静态抽象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元素,而无需为该实现实例化一个对象。
  3. 条件(1)本质上是说'是抽象'而条件(2)是'静态',我知道staticabstract在Java中是不兼容的。那么语言中是否有一些相关的概念可以用来做到这一点?

7 个答案:

答案 0 :(得分:5)

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

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

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

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

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

所以你已经非常接近了。

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

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

答案 1 :(得分:3)

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

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

这样的东西
public interface Operation<E> {
   public E apply(E left, E right);
}

有了这个,你可以建立你的小组:

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

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

答案 2 :(得分:3)

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

所以,在编程的上下文中,我会将其成员的Group形式的定义(class)分开,可能使用泛型:

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

更多的分析定义是:

/** T1: left operand type, T2: right ..., R: result type */
interface BinaryOperator<T1, T2, R> {
    R operate(T1 a, T2 b);
}

/** BinaryOperator<T,T,T> is a function TxT -> T */
interface Group<T, BinaryOperator<T,T,T>> {
    void setOperator(BinaryOperator<T,T,T> op);
    BinaryOperator<T,T,T> getOperator();
    T getIdentity();
    T compose(T, T); // uses the operator
}

这一切都是一个想法;我很久没碰到过数学,所以我可能会非常错误。

玩得开心!

答案 3 :(得分:2)

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

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

public static <T extends GroupElement> 
  T identity(Class<T> type){ /* implementation omitted */ }

static <T extends GroupElement> 
  void registerIdentity(Class<T> type, T identity){ /* implementation omitted */ }

您可以使用class to instance map或自行选择的解决方案轻松实现这些方法。关键是你要保留一个静态的身份元素图,每个GroupElement实现一个。

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

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

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

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

public interface GroupElementFactory<T extends GroupElement>{
  T instance();
  T identity();
}

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

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

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

答案 4 :(得分:1)

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

public GroupElement getIdentitySameClass();

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

public static GInteger getIdentity() { ... whatever }

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

另一方面,如果您需要的课程来自Class<T>对象,我认为您需要使用以getMethod开头的反射。或者看看Giovanni的答案,我觉得这样更好。

答案 5 :(得分:1)

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

public interface Group<MyGroupElement extends GroupElement>{
    public MyGroupElement getIdentity()
}

我们将这些群组实施为singletons,以便我们可以通过getIdentity静态访问instance

public class GIntegerGroup implements Group<GInteger>{

    // singleton stuff
    public final static instance = new GIntgerGroup();
    private GIntgerGroup(){};

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

public class PosRealGroup implements Group<PosReal>{

    // singleton stuff
    public final static instance = new PosRealGroup();
    private PosRealGroup(){}        

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

如果我们还需要能够从组元素中获取标识,我会更新您的GroupElement界面:

public Group<GroupElement> getGroup();

和GInteger:

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

和PosReal:

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

答案 6 :(得分:0)

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

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

仅仅因为静态方法不依赖于实例,并不意味着你应该总是在这种情况下使用它。