为什么我不能在Java接口中定义静态方法?

时间:2009-02-04 19:21:30

标签: java interface static-methods

编辑: 从Java 8开始,接口中现在允许使用静态方法。

以下是示例:

public interface IXMLizable<T>
{
  static T newInstanceFromXML(Element e);
  Element toXMLElement();
}

当然这不起作用。但为什么不呢?

可能的问题之一是,当您致电时会发生什么:

IXMLizable.newInstanceFromXML(e);

在这种情况下,我认为它应该只调用一个空方法(即{})。所有子类都将被强制实现静态方法,因此在调用静态方法时它们都会很好。那为什么不可能呢?

编辑:我想我正在寻找比“因为这就是Java的方式”更深层次的答案。

是否存在无法覆盖静态方法的特定技术原因?也就是说,为什么Java的设计者决定使实例方法可以覆盖而不是静态方法呢?

编辑:我设计的问题是我正在尝试使用接口来强制执行编码约定。

也就是说,界面的目标是双重的:

  1. 我希望IXMLizable接口允许我将实现它的类转换为XML元素(使用多态,工作正常)。

  2. 如果有人想要创建一个实现IXMLizable接口的类的新实例,他们将始终知道将有一个newInstanceFromXML(Element e)静态构造函数。

  3. 除了在界面中添加评论外,还有其他方法可以确保这一点吗?

24 个答案:

答案 0 :(得分:490)

Java 8允许静态接口方法

使用Java 8,接口可以具有静态方法。它们也可以具有具体的实例方法,但不具有实例字段。

这里确实有两个问题:

  1. 为什么在过去的糟糕时期,接口不能包含静态方法?
  2. 为什么不能覆盖静态方法?
  3. 接口中的静态方法

    没有强大的技术理由说明为什么接口在以前的版本中没有静态方法。这是重复问题的summed up nicely by the poster。静态接口方法最初被视为a small language change,,然后有an official proposal在Java 7中添加它们,但后来dropped due to unforeseen complications.

    最后,Java 8引入了静态接口方法,以及具有默认实现的覆盖实例方法。但它们仍然不能有实例字段。这些功能是lambda表达式支持的一部分,您可以在Part H of JSR 335.

    中阅读有关它们的更多信息

    覆盖静态方法

    第二个问题的答案有点复杂。

    静态方法在编译时可以解析。动态分派对于实例方法是有意义的,其中编译器无法确定对象的具体类型,因此无法解析要调用的方法。但是调用静态方法需要一个类,因为该类已知静态 -at编译时 - 动态调度是不必要的。

    有关实例方法如何工作的一些背景知识是必要的,以了解这里发生了什么。我确信实际的实现是完全不同的,但让我解释一下方法调度的概念,它可以准确地模拟观察到的行为。

    假设每个类都有一个哈希表,它将方法签名(名称和参数类型)映射到实际的代码块以实现该方法。当虚拟机尝试在实例上调用方法时,它会查询对象的类,并在类的表中查找请求的签名。如果找到方法体,则调用它。否则,获取该类的父类,并在那里重复查找。这一过程一直持续到找到方法,或者没有更多的父类 - 这导致NoSuchMethodError

    如果超类和子类在其表中都有相同方法签名的条目,则首先遇到子类的版本,并且永远不会使用超类的版本 - 这是“覆盖”。

    现在,假设我们跳过对象实例,并从子类开始。分辨率可以如上所述进行,为您提供一种“可覆盖的”静态方法。然而,解决方案都可以在编译时发生,因为编译器是从已知类开始的,而不是等到运行时查询其类的未指定类型的对象。 “覆盖”静态方法没有意义,因为总是可以指定包含所需版本的类。


    构造函数“interfaces”

    这里有一些材料可以解决最近编辑的问题。

    听起来您希望为IXMLizable的每个实现有效地强制使用类似构造函数的方法。忘记尝试使用接口强制执行此操作一分钟,并假装您有一些符合此要求的类。你会怎么用?

    class Foo implements IXMLizable<Foo> {
      public static Foo newInstanceFromXML(Element e) { ... }
    }
    
    Foo obj = Foo.newInstanceFromXML(e);
    

    由于在“构造”新对象时必须明确命名具体类型Foo,编译器可以验证它确实具有必要的工厂方法。如果没有,那么呢?如果我可以实现缺少“构造函数”的IXMLizable,并创建一个实例并将其传递给您的代码,那么 是一个IXMLizable,其中包含所有必需的接口。

    构建是实现的一部分,不是接口。任何与接口一起成功运行的代码都不关心构造函数。任何关心构造函数的代码都需要知道具体类型,并且可以忽略接口。

答案 1 :(得分:46)

这已经被问及并回答了here

要复制我的答案:

在接口中声明静态方法永远不会有意义。它们不能通过正常调用MyInterface.staticMethod()执行。如果通过指定实现类MyImplementor.staticMethod()来调用它们,那么您必须知道实际的类,因此接口是否包含它是无关紧要的。

更重要的是,静态方法永远不会被覆盖,如果你尝试这样做:

MyInterface var = new MyImplementingClass();
var.staticMethod();

static的规则说必须执行在声明的var类型中定义的方法。由于这是一个界面,这是不可能的。

您无法执行“result = MyInterface.staticMethod()”的原因是它必须执行MyInterface中定义的方法版本。但MyInterface中不能定义版本,因为它是一个接口。根据定义,它没有代码。

虽然你可以说这相当于“因为Java就是这样做的”,但实际上这个决定是其他设计决策的逻辑结果,也是出于很好的理由。

答案 2 :(得分:36)

通常使用工厂模式

完成
public interface IXMLizableFactory<T extends IXMLizable> {
  public T newInstanceFromXML(Element e);
}

public interface IXMLizable {
  public Element toXMLElement();
}

答案 3 :(得分:33)

随着 Java 8 的出现,现在可以在界面中编写默认静态方法。 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

提示:调用静态接口方法并不需要由任何类实现。当然,这是因为超类中静态方法的相同规则适用于接口上的静态方法。

答案 4 :(得分:19)

因为静态方法不能在子类中重写,因此它们不能是抽象的。界面中的所有方法都是事实上,抽象。

答案 5 :(得分:10)

  

为什么我不能在Java接口中定义静态方法?

实际上你可以用Java 8。

根据Java doc

  

静态方法是与其中的类关联的方法   它被定义而不是任何对象。班级的每个实例   分享其静态方法

在Java 8中,界面可以包含默认方法静态方法。这使我们更容易在库中组织辅助方法。我们可以在同一个接口中保留特定于接口的静态方法,而不是在单独的类中。

默认方法示例:

list.sort(ordering);

而不是

Collections.sort(list, ordering);

静态方法示例(来自doc本身):

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));
    }    
}

答案 6 :(得分:6)

首先,所有语言决策都是语言创建者做出的决定。在软件工程或语言定义或编译/解释器编写领域,没有任何内容表明静态方法不能成为接口的一部分。我为他们创建了几种语言和编写的编译器 - 它们只是坐下来定义有意义的语义。我认为接口中静态方法的语义非常清楚 - 即使编译器必须将方法的解析推迟到运行时。

其次,我们一直使用静态方法,有一个有效的理由,有一个包含静态方法的接口模式 - 我不能代表任何人,但我定期使用静态方法。 / p>

最可能的正确答案是,在定义语言时,没有感觉到界面中的静态方法。多年来Java已经发展了很多,这个项目显然已经获得了一些兴趣。对Java 7进行了研究表明它已经上升到可能导致语言变化的兴趣水平。当我不再需要实例化一个对象时,我会很高兴我可以调用我的非静态getter方法来访问子类实例中的静态变量......

答案 7 :(得分:5)

接口与多态性有关,多态性本质上与对象实例相关,而不是类。因此,静态在接口的上下文中没有意义。

答案 8 :(得分:5)

静态方法不像实例方法那样虚拟,所以我想Java设计者认为他们不希望它们在接口中。

但是你可以把包含静态方法的类放在接口中。你可以试试!

public interface Test {
    static class Inner {
        public static Object get() {
            return 0;
        }
    }
}

答案 9 :(得分:5)

  • “是否存在无法覆盖静态方法的特殊原因”。

让我通过填写定义为你重新提出这个问题。

  • “是否有特殊原因导致在编译时解析的方法无法在运行时解析。”

或者,更完整地说,如果我想调用没有实例的方法,但是知道类,我怎样才能根据我没有的实例来解析它。

答案 10 :(得分:3)

评论EDIT: As of Java 8, static methods are now allowed in interfaces.

这是正确的,因为Java 8在接口中是允许的,但是你的例子仍然无法工作。您不能只定义一个静态方法:您必须实现它,否则您将获得编译错误。

答案 11 :(得分:2)

有几个答案讨论了可覆盖静态方法概念的问题。然而,有时你会遇到一种模式,它似乎就是你想要使用的模式。

例如,我使用具有值对象的对象关系图层,但也有用于操作值对象的命令。由于各种原因,每个值对象类都必须定义一些静态方法,让框架找到命令实例。例如,要创建您要执行的人员:

cmd = createCmd(Person.getCreateCmdId());
Person p = cmd.execute();

并按ID加载一个人

cmd = createCmd(Person.getGetCmdId());
cmd.set(ID, id);
Person p = cmd.execute();

这很方便,但它有问题;值得注意的是,静态方法的存在不能在接口中强制执行。界面中的可覆盖静态方法正是我们所需要的,只要它能以某种方式工作。

EJB通过Home接口解决了这个问题;每个对象都知道如何找到它的Home和Home包含“静态”方法。这样就可以根据需要覆盖“静态”方法,并且不会使用不适用于bean实例的方法使正常(称为“远程”)接口混乱。只需使普通接口指定“getHome()”方法即可。返回Home对象的实例(我想这可能是单例),调用者可以执行影响所有Person对象的操作。

答案 12 :(得分:2)

嗯,没有泛型,静态接口是无用的,因为所有静态方法调用都在编译时解析。所以,他们没有真正的用途。

使用泛型,他们可以使用 - 有或没有默认实现。显然需要重写等等。但是,我的猜测是这种用法并不是非常好的(正如其他答案所指出的那样),因此不值得他们实施有用的努力。

答案 13 :(得分:2)

Why can't I define a static method in a Java interface?

接口中的所有方法都是显式抽象的,因此您无法将它们定义为静态,因为静态方法不能是抽象的。

答案 14 :(得分:1)

界面永远不能静态解除引用,例如ISomething.member。始终通过引用接口子类实例的变量取消引用接口。因此,如果没有子类的实例,接口引用永远不会知道它引用哪个子类。

因此,接口中与静态方法的最接近的近似是忽略“this”的非静态方法,即不访问实例的任何非静态成员。在低级抽象中,每个非静态方法(在任何vtable中查找之后)实际上只是一个具有类范围的函数,它将“this”作为隐式形式参数。请参阅Scala's singleton object and interoperability with Java作为该概念的证据。 因此,每个静态方法都是一个类范围不带“this”参数的函数。因此,通常静态方法可以静态调用,但如前所述,接口没有实现(是抽象的)。

因此,为了使接口中的静态方法最接近,就是使用非静态方法,然后不访问任何非静态实例成员。任何其他方式都没有可能的性能优势,因为没有办法静态链接(在编译时)ISomething.member()。我在接口中看到的静态方法的唯一好处是它不会输入(即忽略)隐式“this”,因此不允许访问任何非静态实例成员。这将隐含地声明不访问“this”的函数是不变的,甚至对于其包含的类也不是只读的。但是在接口ISomething中声明“静态”也会使尝试使用ISomething.member()访问它的人感到困惑,这会导致编译器错误。我想如果编译器错误是充分解释的,那么比尝试教育人们使用非静态方法来完成他们想要的东西(显然主要是工厂方法)更好,就像我们在这里做的那样(并且已经重复了3次) Q&amp; A次在这个网站上),所以这显然是一个对许多人来说不直观的问题。为了得到正确的理解,我不得不考虑一段时间。

在接口中获取可变静态字段的方法是在接口中使用非静态getter和setter方法来访问子类中的静态字段。旁注,显然是不可变的静态可以在static final的Java接口中声明。

答案 15 :(得分:0)

假设接口中允许使用静态方法: *它们会强制所有实现类声明该方法。 *接口通常会通过对象使用,因此唯一有效的方法是非静态方法。 *任何知道特定接口的类都可以调用其静态方法。因此,一个实施班级&#39;静态方法将在下面调用,但是调用者类不知道哪个。怎么知道呢?没有实例可以猜测!

在使用对象时,可以使用接口。这样,一个对象就从一个特定的类中实例化,所以最后的问题就解决了。调用类不需要知道哪个特定类是因为实例化可以由第三类完成。所以调用类只知道接口。

如果我们希望将它扩展为静态方法,我们应该可以在之前指定一个实现类,然后将引用传递给调用类。这可以通过接口中的静态方法使用该类。但是这个引用和一个对象之间的区别是什么?我们只需要一个表示它是什么类的对象。现在,该对象代表旧类,并且可以实现一个新的接口,包括旧的静态方法 - 那些现在是非静态的。

元类用于此目的。您可以尝试使用Java类。但问题是Java对此不够灵活。您不能在接口的类对象中声明方法。

这是一个元问题 - 当你需要做屁股时

......等等等等

无论如何,你有一个简单的解决方法 - 使用相同的逻辑使方法非静态。但是你必须首先创建一个对象来调用该方法。

答案 16 :(得分:0)

虽然我意识到Java 8解决了这个问题,但我认为我已经开始使用我正在处理的场景(锁定使用Java 7),其中能够在界面中指定静态方法会有所帮助

我有几个枚举定义我已定义&#34; id&#34;和&#34; displayName&#34;字段以及辅助方法由于各种原因评估值。实现接口允许我确保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, " , ")));
    }
}

答案 17 :(得分:0)

解决这个问题: 错误:缺少方法体,或声明抽象         static void main(String [] 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

现在我们可以在接口

中使用静态方法

答案 18 :(得分:0)

可以实现的是静态接口(而不是接口中的静态方法)。实现给定静态接口的所有类都应实现相应的静态方法。您可以使用

从任何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静态方法实现! 动态调度是必要的,您可以通过扩展它来覆盖类的静态方法(如果不是最终的)(当通过静态接口调用时)。 显然,这些方法只能访问其类的静态变量。

答案 19 :(得分:0)

假设你能做到;考虑这个例子:

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.
  }

}

答案 20 :(得分:0)

您无法在接口中定义静态方法,因为静态方法属于不属于类实例的类,接口不属于类。 Read more here.

但是,如果你想要,你可以这样做:

public class A {
  public static void methodX() {
  }
}

public class B extends A {
  public static void methodX() {
  }
}

在这种情况下,你拥有的是两个带有2个不同静态方法的类,名为methodX()。

答案 21 :(得分:0)

接口只提供一个类将提供的东西列表,而不是这些东西的实际实现,这是你的静态项目。

如果需要静态,请使用抽象类并继承它,否则,删除静态。

希望有所帮助!

答案 22 :(得分:-2)

我认为java没有静态接口方法,因为你不需要它们。你可能认为你这样做,但...... 你会怎么用它们?如果你想打电话给他们

MyImplClass.myMethod()

然后你不需要在界面中声明它。如果你想打电话给他们

myInstance.myMethod()

然后它不应该是静态的。 如果您实际上要使用第一种方法,但只是想强制每个实现都有这样的静态方法,那么它实际上是一种编码约定,而不是实现接口和调用代码的实例之间的契约。

接口允许您在实现接口的类实例和调用代码之间定义契约。并且java帮助您确保不违反此合同,因此您可以依赖它并且不必担心哪个类实现此合同,只需“签署合同的人”就足够了。如果是静态接口你的代码

MyImplClass.myMethod()

不依赖于每个接口实现都有这个方法的事实,所以你不需要java来帮助你确保它。

答案 23 :(得分:-5)

接口中静态方法的需要是什么,静态方法基本上是在你不必创建对象的实例时使用整个接口的想法是引入OOP概念,引入静态方法你要转移概念