编辑:从 Java 8 开始,接口中现在允许使用静态方法。
这是示例:
public interface IXMLizable<T>
{
static T newInstanceFromXML(Element e);
Element toXMLElement();
}
这当然行不通。但为什么不呢?
可能的问题之一是,当你打电话时会发生什么:
IXMLizable.newInstanceFromXML(e);
在这种情况下,我认为它应该只调用一个空方法(即{})。所有子类都将被迫实现静态方法,因此在调用静态方法时它们都可以。那么为什么这不可能呢?
编辑:我想我正在寻找比“因为 Java 就是这样”更深的答案。
静态方法不能被覆盖是否有特定的技术原因?也就是说,为什么 Java 的设计者决定让实例方法可覆盖而不是静态方法?
编辑:我的设计问题是我正在尝试使用接口来强制执行编码约定。
也就是说,接口的目标是双重的:
我希望 IXMLizable 接口允许我将实现它的类转换为 XML 元素(使用多态,工作正常)。如果有人想创建一个实现 IXMLizable 接口的类的新实例,他们总是知道会有一个 newInstanceFromXML(Element e) 静态构造函数。
除了在界面中添加评论之外,还有其他方法可以确保这一点吗?
Java 8 允许静态接口方法
在 Java 8 中,接口可以有静态方法。它们也可以有具体的实例方法,但不能有实例字段。
这里真的有两个问题:
为什么在过去糟糕的日子里,接口不能包含静态方法?为什么不能覆盖静态方法?
接口中的静态方法
没有强有力的技术原因说明接口在以前的版本中不能有静态方法。这是重复问题的 summed up nicely by the poster。静态接口方法最初被认为是 a small language change,,然后在 Java 7 中有 an official proposal 来添加它们,但后来是 dropped due to unforeseen complications.
最后,Java 8 引入了静态接口方法,以及具有默认实现的可覆盖实例方法。他们仍然不能有实例字段。这些功能是 lambda 表达式支持的一部分,您可以在 Part H of JSR 335. 中阅读有关它们的更多信息
覆盖静态方法
第二个问题的答案有点复杂。
静态方法在编译时是可解析的。动态分派对实例方法有意义,其中编译器无法确定对象的具体类型,因此无法解析要调用的方法。但是调用静态方法需要一个类,并且由于该类是静态已知的——在编译时——动态调度是不必要的。
了解实例方法如何工作的一些背景知识对于理解这里发生的事情是必要的。我确信实际的实现是完全不同的,但让我解释一下我的方法分派的概念,它可以准确地模拟观察到的行为。
假设每个类都有一个哈希表,该哈希表将方法签名(名称和参数类型)映射到实际的代码块以实现该方法。当虚拟机尝试调用实例上的方法时,它会查询对象的类并在类的表中查找请求的签名。如果找到方法体,则调用它。否则,获取该类的父类,并在那里重复查找。这将继续进行,直到找到该方法,或者没有更多的父类 - 这会导致 NoSuchMethodError
。
如果超类和子类在其表中都有相同方法签名的条目,则首先遇到子类的版本,并且永远不会使用超类的版本——这是“覆盖”。
现在,假设我们跳过对象实例并从子类开始。解决方案可以如上进行,为您提供一种“可覆盖”的静态方法。然而,解析都可以在编译时发生,因为编译器是从一个已知的类开始的,而不是等到运行时为它的类查询一个未指定类型的对象。 “覆盖”静态方法没有意义,因为总是可以指定包含所需版本的类。
构造函数“接口”
这里有更多材料来解决最近对该问题的编辑。
听起来您想为 IXMLizable
的每个实现有效地强制使用类似构造函数的方法。暂时忘记尝试使用接口强制执行此操作,并假装您有一些满足此要求的类。你会如何使用它?
class Foo implements IXMLizable<Foo> {
public static Foo newInstanceFromXML(Element e) { ... }
}
Foo obj = Foo.newInstanceFromXML(e);
由于在“构造”新对象时必须显式命名具体类型 Foo
,编译器可以验证它确实具有必要的工厂方法。如果没有,那又如何?如果我可以实现缺少“构造函数”的 IXMLizable
,并创建一个实例并将其传递给您的代码,那么它是一个具有所有必要接口的 IXMLizable
。
构造是实现的一部分,而不是接口。任何与接口一起成功工作的代码都不关心构造函数。任何关心构造函数的代码无论如何都需要知道具体类型,接口可以忽略。
这已经被问及回答了,here
复制我的答案:
在接口中声明静态方法是没有意义的。它们不能通过正常调用 MyInterface.staticMethod() 来执行。如果您通过指定实现类 MyImplementor.staticMethod() 来调用它们,那么您必须知道实际的类,因此接口是否包含它是无关紧要的。
更重要的是,静态方法永远不会被覆盖,如果你尝试这样做:
MyInterface var = new MyImplementingClass();
var.staticMethod();
static 的规则说必须执行在声明的 var 类型中定义的方法。因为这是一个接口,所以这是不可能的。
无法执行“result=MyInterface.staticMethod()”的原因是它必须执行 MyInterface 中定义的方法版本。但是在 MyInterface 中不能定义一个版本,因为它是一个接口。它没有定义的代码。
虽然您可以说这相当于“因为 Java 这样做”,但实际上该决定是其他设计决策的逻辑结果,也是有充分理由做出的。
随着 Java 8 的出现,现在可以在接口中编写 default 和 static 方法。 docs.oracle/staticMethod
例如:
public interface Arithmetic {
public int add(int a, int b);
public static int multiply(int a, int b) {
return a * b;
}
}
public class ArithmaticImplementation implements Arithmetic {
@Override
public int add(int a, int b) {
return a + b;
}
public static void main(String[] args) {
int result = Arithmetic.multiply(2, 3);
System.out.println(result);
}
}
结果:6
提示:调用静态接口方法不需要由任何类实现。当然,这是因为超类中静态方法的相同规则适用于接口上的静态方法。
通常这是使用工厂模式完成的
public interface IXMLizableFactory<T extends IXMLizable> {
public T newInstanceFromXML(Element e);
}
public interface IXMLizable {
public Element toXMLElement();
}
因为静态方法不能在子类中被覆盖,因此它们不能是抽象的。接口中的所有方法实际上都是抽象的。
为什么我不能在 Java 接口中定义静态方法?
实际上你可以在 Java 8 中。
根据 Java doc:
静态方法是与定义它的类相关联的方法,而不是与任何对象相关联的方法。类的每个实例共享其静态方法
在 Java 8 中,接口可以有默认方法和静态方法。这使我们更容易在我们的库中组织辅助方法。我们可以将特定于接口的静态方法保留在同一个接口中,而不是在单独的类中。
默认方法示例:
list.sort(ordering);
代替
Collections.sort(list, ordering);
静态方法示例(来自文档本身):
public interface TimeClient {
// ...
static public ZoneId getZoneId (String zoneString) {
try {
return ZoneId.of(zoneString);
} catch (DateTimeException e) {
System.err.println("Invalid time zone: " + zoneString +
"; using default time zone instead.");
return ZoneId.systemDefault();
}
}
default public ZonedDateTime getZonedDateTime(String zoneString) {
return ZonedDateTime.of(getLocalDateTime(), getZoneId(zoneString));
}
}
接口与多态性有关,多态性本质上与对象实例相关,而不是类。因此静态在接口的上下文中没有意义。
首先,所有语言决策都是由语言创建者做出的决定。在软件工程或语言定义或编译器/解释器编写的世界中,没有任何东西说静态方法不能成为接口的一部分。我已经创建了几种语言并为它们编写了编译器——这一切都只是坐下来定义有意义的语义。我认为接口中静态方法的语义非常清楚——即使编译器必须将方法的解析推迟到运行时。
其次,我们完全使用静态方法意味着拥有包含静态方法的接口模式是有正当理由的——我不能代表你们任何人,但我经常使用静态方法。
最可能的正确答案是,在定义语言时,接口中的静态方法并不需要。 Java 多年来发展了很多,这显然引起了人们的兴趣。对 Java 7 的研究表明它已上升到可能导致语言更改的程度。一方面,当我不再需要实例化一个对象时,我会很高兴,这样我就可以调用我的非静态 getter 方法来访问子类实例中的静态变量......
“是否有特定原因不能覆盖静态方法”。
让我通过填写定义为您重新提出这个问题。
“是否有特殊原因导致在编译时解析的方法无法在运行时解析。”
或者,更完整地说,如果我想调用一个没有实例但知道类的方法,如何根据我没有的实例来解决它。
静态方法不像实例方法那样是虚拟的,所以我想 Java 设计者决定他们不希望它们出现在接口中。
但是您可以将包含静态方法的类放在接口中。你可以试试!
public interface Test {
static class Inner {
public static Object get() {
return 0;
}
}
}
评论 EDIT: As of Java 8, static methods are now allowed in interfaces.
没错,接口中允许使用 Java 8 以来的静态方法,但您的示例仍然无法正常工作。你不能只定义一个静态方法:你必须实现它,否则你会得到一个编译错误。
几个答案讨论了可覆盖静态方法概念的问题。但是,有时您会遇到一种看起来正是您想要使用的模式。
例如,我使用具有值对象的对象关系层,但也具有用于操作值对象的命令。由于各种原因,每个值对象类都必须定义一些静态方法,让框架找到命令实例。例如,要创建一个 Person 你会做:
cmd = createCmd(Person.getCreateCmdId());
Person p = cmd.execute();
并通过 ID 加载 Person 你会做
cmd = createCmd(Person.getGetCmdId());
cmd.set(ID, id);
Person p = cmd.execute();
这相当方便,但也有问题;值得注意的是静态方法的存在不能在接口中强制执行。接口中的一个可覆盖的静态方法正是我们所需要的,只要它能够以某种方式工作。
EJB 通过 Home 接口解决了这个问题;每个对象都知道如何找到它的 Home 并且 Home 包含“静态”方法。这样,可以根据需要覆盖“静态”方法,并且您不会将普通(称为“远程”)接口与不适用于 bean 实例的方法混淆。只需让普通接口指定一个“getHome()”方法即可。返回 Home 对象的一个实例(我想它可能是一个单例),调用者可以执行影响所有 Person 对象的操作。
好吧,没有泛型,静态接口就毫无用处,因为所有静态方法调用都是在编译时解决的。所以,它们没有真正的用处。
使用泛型,它们可以使用——有或没有默认实现。显然需要覆盖等等。但是,我的猜测是,这种用法不是很 OO(正如其他答案所指出的那样),因此不值得他们为有效实现所需付出的努力。
Why can't I define a static method in a Java interface?
接口中的所有方法都是显式抽象的,因此您不能将它们定义为静态,因为静态方法不能是抽象的。
永远不能静态地取消引用接口,例如 ISomething.member
。接口总是通过引用接口子类实例的变量来取消引用。因此,如果没有子类的实例,接口引用永远无法知道它引用的是哪个子类。
因此,与接口中的静态方法最接近的方法是忽略“this”的非静态方法,即不访问实例的任何非静态成员。在低级抽象中,每个非静态方法(在任何 vtable 中查找之后)实际上只是一个具有类范围的函数,它将“this”作为隐式形式参数。请参阅 Scala's singleton object and interoperability with Java 作为该概念的证据。因此,每个静态方法都是一个具有类范围的函数,它不带“this”参数。因此,通常可以静态调用静态方法,但如前所述,接口没有实现(是抽象的)。
因此,要最接近接口中的静态方法,就是使用非静态方法,然后不要访问任何非静态实例成员。任何其他方式都不可能带来性能优势,因为无法静态链接(在编译时)ISomething.member()
。我在接口中看到静态方法的唯一好处是它不会输入(即忽略)隐式“this”,因此不允许访问任何非静态实例成员。这将隐式声明不访问“this”的函数是不可变的,甚至就其包含的类而言甚至不是只读的。但是在接口 ISomething
中声明“静态”也会使试图使用 ISomething.member()
访问它的人感到困惑,这会导致编译器错误。我想如果编译器错误足够解释,它会比试图教育人们使用非静态方法来完成他们想要的(显然主要是工厂方法)更好,就像我们在这里所做的那样(并且已经重复了 3这个网站上的问答时间),所以这显然是一个对很多人来说并不直观的问题。我不得不考虑一段时间才能得到正确的理解。
在接口中获取可变静态字段的方法是在接口中使用非静态 getter 和 setter 方法来访问子类中的静态字段。旁注,显然不可变的静态可以用 static final
在 Java 接口中声明。
接口只是提供一个类将提供的东西的列表,而不是这些东西的实际实现,这就是你的静态项目。
如果您想要静态,请使用抽象类并继承它,否则,请删除静态。
希望有帮助!
您不能在接口中定义静态方法,因为静态方法属于类而不属于类的实例,并且接口不是类。 Read more here.
但是,如果你愿意,你可以这样做:
public class A {
public static void methodX() {
}
}
public class B extends A {
public static void methodX() {
}
}
在这种情况下,您拥有的是两个具有 2 个不同的静态方法的类,称为 methodX()。
假设你能做到;考虑这个例子:
interface Iface {
public static void thisIsTheMethod();
}
class A implements Iface {
public static void thisIsTheMethod(){
system.out.print("I'm class A");
}
}
class B extends Class A {
public static void thisIsTheMethod(){
System.out.print("I'm class B");
}
}
SomeClass {
void doStuff(Iface face) {
IFace.thisIsTheMethod();
// now what would/could/should happen here.
}
}
A.thisIsTheMethod()
,它将打印“我是 B 级”。
B.thisIsTheMethod()
,它将打印“我是 B 级”。
可以实现的是静态接口(而不是接口中的静态方法)。所有实现给定静态接口的类都应该实现相应的静态方法。您可以使用从任何 Class clazz 获取静态接口 SI
SI si = clazz.getStatic(SI.class); // null if clazz doesn't implement SI
// alternatively if the class is known at compile time
SI si = Someclass.static.SI; // either compiler errror or not null
那么您可以调用 si.method(params)
。这将很有用(例如对于工厂设计模式),因为您可以从编译时未知类中获取(或检查)SI 静态方法的实现!动态调度是必要的,您可以通过扩展类的静态方法(如果不是最终方法)(通过静态接口调用时)来覆盖它。显然,这些方法只能访问其类的静态变量。
虽然我意识到 Java 8 解决了这个问题,但我想我会加入我目前正在处理的场景(锁定使用 Java 7),其中能够在接口中指定静态方法会很有帮助。
我有几个枚举定义,其中我定义了“id”和“displayName”字段以及出于各种原因评估值的辅助方法。实现一个接口可以让我确保 getter 方法就位,但不是静态辅助方法。作为枚举,确实没有一种干净的方法可以将辅助方法卸载到继承的抽象类或类似的东西中,因此必须在枚举本身中定义方法。也因为它是一个枚举,你永远不能真正将它作为实例对象传递并将其视为接口类型,但是能够通过接口要求存在静态帮助方法是我喜欢的Java 8 支持它。
这是说明我的观点的代码。
接口定义:
public interface IGenericEnum <T extends Enum<T>> {
String getId();
String getDisplayName();
//If I was using Java 8 static helper methods would go here
}
一个枚举定义的示例:
public enum ExecutionModeType implements IGenericEnum<ExecutionModeType> {
STANDARD ("Standard", "Standard Mode"),
DEBUG ("Debug", "Debug Mode");
String id;
String displayName;
//Getter methods
public String getId() {
return id;
}
public String getDisplayName() {
return displayName;
}
//Constructor
private ExecutionModeType(String id, String displayName) {
this.id = id;
this.displayName = displayName;
}
//Helper methods - not enforced by Interface
public static boolean isValidId(String id) {
return GenericEnumUtility.isValidId(ExecutionModeType.class, id);
}
public static String printIdOptions(String delimiter){
return GenericEnumUtility.printIdOptions(ExecutionModeType.class, delimiter);
}
public static String[] getIdArray(){
return GenericEnumUtility.getIdArray(ExecutionModeType.class);
}
public static ExecutionModeType getById(String id) throws NoSuchObjectException {
return GenericEnumUtility.getById(ExecutionModeType.class, id);
}
}
通用枚举实用程序定义:
public class GenericEnumUtility {
public static <T extends Enum<T> & IGenericEnum<T>> boolean isValidId(Class<T> enumType, String id) {
for(IGenericEnum<T> enumOption : enumType.getEnumConstants()) {
if(enumOption.getId().equals(id)) {
return true;
}
}
return false;
}
public static <T extends Enum<T> & IGenericEnum<T>> String printIdOptions(Class<T> enumType, String delimiter){
String ret = "";
delimiter = delimiter == null ? " " : delimiter;
int i = 0;
for(IGenericEnum<T> enumOption : enumType.getEnumConstants()) {
if(i == 0) {
ret = enumOption.getId();
} else {
ret += delimiter + enumOption.getId();
}
i++;
}
return ret;
}
public static <T extends Enum<T> & IGenericEnum<T>> String[] getIdArray(Class<T> enumType){
List<String> idValues = new ArrayList<String>();
for(IGenericEnum<T> enumOption : enumType.getEnumConstants()) {
idValues.add(enumOption.getId());
}
return idValues.toArray(new String[idValues.size()]);
}
@SuppressWarnings("unchecked")
public static <T extends Enum<T> & IGenericEnum<T>> T getById(Class<T> enumType, String id) throws NoSuchObjectException {
id = id == null ? "" : id;
for(IGenericEnum<T> enumOption : enumType.getEnumConstants()) {
if(id.equals(enumOption.getId())) {
return (T)enumOption;
}
}
throw new NoSuchObjectException(String.format("ERROR: \"%s\" is not a valid ID. Valid IDs are: %s.", id, printIdOptions(enumType, " , ")));
}
}
假设接口中允许使用静态方法: * 它们将强制所有实现类声明该方法。 * 接口通常会通过对象使用,因此唯一有效的方法是非静态方法。 * 任何知道特定接口的类都可以调用其静态方法。因此,将在下面调用实现类的静态方法,但调用者类不知道是哪个。怎么知道?它没有实例化来猜测!
接口被认为是在处理对象时使用的。这样,一个对象是从一个特定的类中实例化的,所以最后一个问题就解决了。调用类不需要知道哪个特定类是因为实例化可以由第三个类完成。所以调用类只知道接口。
如果我们希望将其扩展到静态方法,我们应该可以在之前指定一个实现类,然后将引用传递给调用类。这可以通过接口中的静态方法使用该类。但是这个引用和一个对象有什么区别呢?我们只需要一个对象来表示它是什么类。现在,该对象代表旧类,并且可以实现包括旧静态方法的新接口——这些现在是非静态的。
元类用于此目的。你可以试试 Java 类。但问题是 Java 对此不够灵活。您不能在接口的类对象中声明方法。
这是一个元问题 - 当你需要做的时候
..胡说八道
无论如何,您有一个简单的解决方法 - 使该方法具有相同的逻辑非静态。但是你必须首先创建一个对象来调用该方法。
解决这个问题:错误:缺少方法体,或声明抽象静态无效主(字符串[] args);
interface I
{
int x=20;
void getValue();
static void main(String[] args){};//Put curly braces
}
class InterDemo implements I
{
public void getValue()
{
System.out.println(x);
}
public static void main(String[] args)
{
InterDemo i=new InterDemo();
i.getValue();
}
}
输出:20
现在我们可以在接口中使用静态方法
我认为 java 没有静态接口方法,因为您不需要它们。你可能认为你这样做了,但是......你会如何使用它们?如果你想这样称呼他们
MyImplClass.myMethod()
那么你不需要在接口中声明它。如果你想这样称呼他们
myInstance.myMethod()
那么它不应该是静态的。如果您实际上要使用第一种方式,但只是想强制每个实现都具有这样的静态方法,那么它实际上是一种编码约定,而不是实现接口的实例和调用代码之间的约定。
接口允许您在实现接口的类实例和调用代码之间定义契约。而java可以帮助你确保这个契约没有被违反,所以你可以依赖它,不用担心什么类实现了这个契约,只要“签署契约的人”就足够了。如果是静态接口,您的代码
MyImplClass.myMethod()
不依赖每个接口实现都有这个方法,所以不需要java帮你确定。
接口中需要静态方法是什么,当您不必创建对象的实例时基本上使用静态方法接口的整个想法是引入OOP概念并引入静态方法,您正在转移概念。
RESET()
?你会写SomeClass.RESET()
。所以你不需要一个接口来描述那个 API;它的静态。当您在编译时不知道具体类型时,将使用接口。静态方法绝不会出现这种情况。T
的情况下静态创建T
的新实例,因为我在接口中承诺某个构造函数(或静态方法)将在运行时存在。无法在 Java 中指定生成这一事实并不意味着这不是一件有意义的事情。