问题 如何在Java中实现抽象静态方法?
关于包含静态抽象Java方法的不可能性存在许多问题。关于此的解决方法(设计缺陷/设计强度)也有很多。但我找不到任何关于我即将陈述的具体问题。
在我看来,制作Java的人和很多使用它的人并没有像我和其他许多人那样思考静态方法 - 作为类函数或属于类的方法而不是任何对象。那么还有其他一些实现类函数的方法吗?
这是我的例子:在数学中,a 组 是一组对象,可以使用某些操作*以某种合理的方式相互组合 - 例如,正实数在正常乘法下形成一个组(X * ÿ = X × ÿ),并且整数组形成一个组,其中'乘法'操作是加法(米 * ñ = 米 + ñ)。
在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());
}
}
但是,团队还有另外一个重要的属性:每个团队都有一个 身份元素 - 一个元素 Ë 这样的 X * Ë = X 对全部 X 在小组中。例如,乘法下正实数的标识元素是 1,以及添加的整数的标识元素是 0。在这种情况下,为每个实现类创建一个方法是有意义的,如下所示:
public PosReal getIdentity()
{
return new PosReal(1);
}
public GInteger getIdentity()
{
return new GInteger(0);
}
但在这里我们遇到了问题 - 方法 getIdentity
不依赖于对象的任何实例,因此应该声明 static
(实际上,我们可能希望从静态背景中引用它)。但是,如果我们把 getIdentity
进入接口的方法然后我们不能声明它 static
在界面中,所以不可能 static
在任何实施类中。
有没有办法实现这个 getIdentity
方法:
- 强制所有实现的一致性
GroupElement
,以便每次执行 GroupElement
被迫包括一个 getIdentity
功能。
- 静态地行事;即,我们可以获得给定实现的标识元素
GroupElement
没有实例化该实现的对象。
条件(1)实质上是说'是抽象'而条件(2)是'是静态',我知道 static
和 abstract
在Java中是不兼容的。那么语言中是否有一些可用于执行此操作的相关概念?
10600
2018-03-17 15:44
起源
答案:
基本上你要求的是能够在编译时强制一个类定义一个具有特定签名的给定静态方法。
你不能用Java真正做到这一点,但问题是:你真的需要吗?
所以,假设您采用当前实现静态的选项 getIdentity()
在每个子类中。考虑到在你之前你实际上不需要这种方法 使用 它,当然,如果你试图使用它,但它没有定义,你 将 得到编译器错误提醒您定义它。
如果您定义它但签名不是“正确”,并且您尝试使用它而不是您定义它,那么您也将遇到编译器错误(关于使用无效参数调用它,或返回类型问题等)。 )。
因为你不能通过基类型调用子类静态方法,所以你是 总是 必须明确地打电话给他们,例如 GInteger.getIdentity()
。而且,如果你试着打电话,编译器就会抱怨 GInteger.getIdentity()
什么时候 getIdentity()
没有定义,或者如果你使用不正确,你基本上获得编译时检查。当然,您唯一缺少的是能够强制定义静态方法,即使您从未在代码中使用过静态方法。
所以你已经非常接近了。
你的例子就是一个很好的例子 什么 你想要,但我会挑战你想出一个例子,其中有一个关于缺少静态函数的编译时警告是必要的;我唯一能想到的就是如果你正在创建一个供别人使用的库,你想确保你不要忘记实现一个特定的静态函数 - 但是对所有子类进行适当的单元测试也可以在编译期间捕获它(你无法测试 getIdentity()
如果它不存在)。
注意:查看您的新问题评论:如果您要求能力 呼叫 给出一个静态方法 Class<?>
,你不能,本身(没有反射) - 但你仍然可以得到你想要的功能,如中所述 Giovanni Botta的回答;您将牺牲运行时检查的编译时检查,但能够使用标识编写通用算法。所以,这实际上取决于你的最终目标。
4
2018-03-17 15:58
数学组只有一个特征操作,但Java类可以有任意数量的操作。因此这两个概念不匹配。
我可以想象像Java类这样的东西 Group
由a组成 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();
}
我知道这并不完全是你的想法,但正如我上面所说,一个数学小组是一个有点不同的概念而不是一个类。
3
2018-03-17 16:00
你的推理可能会有一些误解。你看到一个数学“组”就像你定义它(如果我能记得很清楚);但它的要素并不是因为它们属于这一群体。我的意思是整数(或实数)是一个独立的实体,也属于组XXX(在其他属性中)。
那么,在编程的上下文中,我会将定义分开(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
2018-03-17 16:01
没有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 */ }
您可以使用a轻松实现这些方法 类到实例映射 或者是自己选择的家庭解决方案。关键是你要保留一个静态的身份元素图,每个元素一个 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
,正如一些人所说。这种方法的缺点是不允许针对该组编写通用算法。再次,最好的解决方案是第一次编辑中提到的那个。
2
2018-03-17 16:01
如果你需要能够在编译时生成一个不知道类的标识,那么第一个问题是,你怎么知道在运行时你想要什么类?如果该类基于其他一些对象,那么我认为最干净的方法是在超类中定义一个方法,这意味着“获取一个类与其他一些对象相同的标识”。
public GroupElement getIdentitySameClass();
必须在每个子类中重写。覆盖可能不会使用该对象;该对象仅用于选择正确的对象 getIdentity
多态调用。最有可能的是,你也想要一个静态的 getIdentity
在每个类中(但我不知道编译器强制写一个),所以子类中的代码可能看起来像
public static GInteger getIdentity() { ... whatever }
@Override
public GInteger getIdentitySameClass() { return getIdentity(); }
另一方面,如果你需要的课程来自a Class<T>
对象,我认为你需要使用反射开始 getMethod
。或者看看Giovanni的答案,我认为这个答案更好。
1
2018-03-17 16:11
我们都同意,如果你想实现组,你将需要一个组接口和类。
public interface Group<MyGroupElement extends GroupElement>{
public MyGroupElement getIdentity()
}
我们将这些组实施为 单身 所以我们可以访问 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();
}
1
2018-03-17 15:58
“方法getIdentity不依赖于对象的任何实例,因此应该声明为static”
实际上,如果它不依赖于任何实例,它只能返回一些常量值,它不必是静态的。
仅仅因为静态方法不依赖于实例,并不意味着您应该始终将它用于此类情况。
0