关于Java:为什么接口中没有静态方法,但静态字段和内部类可以吗?[前Java8]

Why no static methods in Interfaces, but static fields and inner classes OK? [pre-Java8]

这里有几个问题问你为什么不能在接口中定义静态方法,但它们都没有解决一个基本的不一致性:为什么你能在接口中定义静态字段和静态内部类型,而不是静态方法?

静态内部类型可能不是一个公平的比较,因为这只是生成一个新类的语法糖,但是为什么字段而不是方法?

反对接口内静态方法的一个论点是,它破坏了JVM使用的虚拟表解析策略,但这不应该同样适用于静态字段,即编译器可以直接内联它吗?

一致性是我所希望的,Java应该在接口中不支持任何形式的静态,或者它应该是一致的并允许它们。


已经提出了一个官方建议,允许在Java 7中的接口中使用静态方法。这项提议是根据Coin项目提出的。

我个人认为这是个好主意。在实现过程中没有技术困难,这是一件非常合乎逻辑、合理的事情。有几个项目硬币的建议,我希望永远不会成为Java语言的一部分,但这是一个可以清理很多API。例如,Collections类具有用于操作任何List实现的静态方法;这些方法可以包括在List接口中。

更新:在JAVA POSSE播客234中,乔D'ARCY简要地提到了这个提议,说它是"复杂的",并且可能不会在项目硬币下进行。

更新:虽然他们没有把它投向Java 7的Project Engin,但是Java 8确实支持接口中的静态功能。


我要用我最喜欢的理论来解释这一点,也就是说,在这种情况下,缺乏一致性是一个方便的问题,而不是设计或必要性的问题,因为我没有听到任何令人信服的论据表明这两者都是。

静态字段存在于(a)中,因为它们在JDK 1中存在,并且在JDK 1中做出许多轻率的决定,以及(b)接口中的静态最终字段是Java在时间上最接近的常数。

接口中的静态内部类是允许的,因为这纯粹是语法上的甜头——内部类实际上与父类没有任何关系。

因此,不允许使用静态方法,因为没有令人信服的理由这样做;一致性不足以改变现状。

当然,这在将来的JLS版本中是允许的,而且不会破坏任何东西。


在接口中声明静态方法从来没有意义。它们不能由普通调用MyInterface.StaticMethod()执行。(编辑:由于最后一句话让一些人困惑,调用myClass.staticMethod()会精确地在myClass上执行staticMethod的实现,如果myClass是接口,就不可能存在!)如果您通过指定实现类myImplementor.staticMethod()来调用它们,那么您必须知道实际的类,因此无论接口是否包含它都是不相关的。

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

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

静态规则表示必须执行在声明的var类型中定义的方法。因为这是一个接口,所以这是不可能的。

当然,您可以从方法中移除静态关键字。一切都会好起来的。如果从实例方法调用某些警告,则可能需要取消这些警告。

要回答下面的一些注释,您不能执行"result=myInterface.staticMethod()"的原因是它必须执行在myInterface中定义的方法版本。但MyInterface中不能定义版本,因为它是一个接口。它没有定义的代码。


这是一条旧线,但这对所有人来说都是一个非常重要的问题。因为我今天注意到这一点,所以我试图用更清晰的方式来解释:

接口的主要目的是提供一些不可实现的东西,因此如果它们提供

static methods to be allowed

然后可以使用interfacename.staticMethodName()调用该方法,但这是未实现的方法,不包含任何内容。因此,允许静态方法是无用的。因此,他们根本不提供这一点。

static fields are allowed

因为字段不可实现,所以我的意思是您不能在字段中执行任何逻辑操作,您可以在字段上执行操作。所以你不能改变场的行为,这就是它们被允许的原因。

Inner classes are allowed

内部类是允许的,因为编译后创建了内部类的不同类文件,比如interfacename$innerclassname.class,所以基本上您是在不同的实体中一起提供实现,而不是在接口中。因此提供了内部类中的实现。

我希望这会有所帮助。


接口的目的是在不提供实现的情况下定义合同。因此,您不能拥有静态方法,因为它们必须在接口中已有一个实现,因为您不能重写静态方法。对于字段,只允许静态final fields,本质上是常量(在1.5+中,接口中也可以有枚举)。这些常量有助于定义没有幻数的接口。

顺便说一句,没有必要为接口中的字段显式地指定static final修饰符,因为只允许静态最终字段。


在接口中没有静态方法没有真正的理由:Java语言设计者不希望这样。从技术角度来看,允许他们这样做是有道理的。毕竟抽象类也可以拥有它们。我假设但没有测试它,你可以在接口有静态方法的地方"手工制作"字节代码,并且它应该可以正常地调用方法和/或使用接口,而不会有任何问题。


实际上,有时有一些原因可以让人从静态方法中获益。它们可以用作实现接口的类的工厂方法。例如,这就是我们现在在OpenJDK中拥有Collection接口和Collections类的原因。因此,仍然有解决方法-为另一个类提供一个私有的构造函数,该构造函数将作为静态方法的"命名空间"。


在Java 5之前,静态字段的常用用法是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface HtmlConstants {
    static String OPEN ="<";
    static String SLASH_OPEN ="</";
    static String CLOSE =">";
    static String SLASH_CLOSE =" />";
    static String HTML ="html";
    static String BODY ="body";
    ...
}

public class HtmlBuilder implements HtmlConstants { // implements ?!?
    public String buildHtml() {
       StringBuffer sb = new StringBuffer();
       sb.append(OPEN).append(HTML).append(CLOSE);
       sb.append(OPEN).append(BODY).append(CLOSE);
       ...
       sb.append(SLASH_OPEN).append(BODY).append(CLOSE);
       sb.append(SLASH_OPEN).append(HTML).append(CLOSE);
       return sb.toString();
    }
}

这意味着htmlbuilder不必限定每个常量,因此它可以使用open而不是htmlconstants.open。

以这种方式使用工具最终会令人困惑。

现在使用Java 5,我们有导入静态语法来实现相同的效果:

1
2
3
4
5
6
7
8
9
private final class HtmlConstants {
    ...
    private HtmlConstants() { /* empty */ }
}

import static HtmlConstants.*;
public class HtmlBuilder { // no longer uses implements
    ...
}


我经常想知道为什么静态方法?它们确实有自己的用途,但包/命名空间级别的方法可能涵盖了静态方法的80种用途。


静态方法绑定到类。在Java中,接口在技术上不是一个类,它是一个类型,而不是一个类(因此,关键字实现,而接口不扩展对象)。因为接口不是类,所以不能有静态方法,因为没有要附加到的实际类。

您可以调用IlFruteNe.class来获得与接口对应的类对象,但是类类具体地表示它代表Java应用程序中的类和接口。但是,接口本身不被视为类,因此不能附加静态方法。


引起人们注意的两个主要原因:

  • Java中的静态方法不能被子类重写,这是比静态字段更大的方法。实际上,我甚至不想重写子类中的字段,但我一直在重写方法。因此,拥有静态方法可以防止实现接口的类提供自己的方法实现,这在很大程度上破坏了使用接口的目的。

  • 接口不应该有代码;这就是抽象类的用途。接口的全部要点是让您讨论可能不相关的对象,这些对象恰好都有一组特定的方法。实际上,提供这些方法的实现超出了接口的预期范围。


  • Java 1.8接口静态方法只对接口方法可见,如果我们从接口实例类中移除MultStudio()方法,我们无法将其用于InterfaceExample对象。但是,与其他静态方法一样,我们可以使用使用类名的接口静态方法。例如,有效的语句将是:exp1.methodSta1();

    因此,在下面的例子中,我们可以说:1)Java接口静态方法是接口的一部分,我们不能用它来实现类对象。

    2)Java接口静态方法有助于提供实用方法,例如空校验、集合排序、日志等。

    3)Java接口静态方法帮助我们通过不允许实现类(IdFraseExcel)来覆盖它们来提供安全性。

    4)我们不能为对象类方法定义接口静态方法,我们会得到编译错误,因为"这个静态方法不能从对象中隐藏实例方法"。这是因为它不允许在Java中,因为对象是所有类的基类,我们不能有一个类级静态方法和另一个具有相同签名的实例方法。

    5)我们可以使用Java接口静态方法来移除诸如集合之类的实用类,并将其所有静态方法移到相应的接口,这很容易找到和使用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    public class InterfaceExample implements exp1 {

        @Override
        public void method() {
            System.out.println("From method()");
        }

        public static void main(String[] args) {
            new InterfaceExample().method2();
            InterfaceExample.methodSta2();      //  <---------------------------    would not compile
            // methodSta1();                        //  <---------------------------    would not compile
            exp1.methodSta1();
        }

        static void methodSta2() {          //          <-- it compile successfully but it can't be overridden in child classes
            System.out.println("========= InterfaceExample :: from methodSta2() ======");
        }
    }


    interface exp1 {

        void method();
        //protected void method1();         //          <--      error
        //private void method2();           //          <--      error
        //static void methodSta1();         //          <--      error it require body in java 1.8

        static void methodSta1() {          //          <-- it compile successfully but it can't be overridden in child classes
            System.out.println("========= exp1:: from methodSta1() ======");
        }

        static void methodSta2() {          //          <-- it compile successfully but it can't be overridden in child classes
            System.out.println("========= exp1:: from methodSta2() ======");
        }

        default void method2() { System.out.println("---  exp1:: from method2() ---");}
        //synchronized default void method3() { System.out.println("---");}             // <-- Illegal modifier for the interface method method3; only public, abstract, default, static
                                                                                    // and strictfp are permitted
        //final default void method3() { System.out.println("---");} //             <--      error
    }


    我相信静态方法可以在不创建对象的情况下访问,并且接口不允许创建对象,因为这样会限制程序员直接使用接口方法,而不是从其实现的类。但是,如果您在一个接口中定义了一个静态方法,您可以直接访问它,而不需要它的实现。因此,接口中不允许使用静态方法。我不认为一致性是一个问题。


    原因是,无论您是否显式声明修饰符,接口中定义的所有方法都是抽象的。抽象静态方法不是允许的修饰符组合,因为静态方法不能被重写。

    至于为什么接口允许静态字段。我有一种感觉应该被认为是一个"特色"。我能想到的唯一可能性是对接口实现感兴趣的常量进行分组。

    我同意一致性是更好的方法。接口中不允许有静态成员。


    在一个接口中只能声明静态的final字段(很像方法,这些方法是公共的,即使您不包含"public"关键字,静态字段也是带有或不带关键字的"final"。

    这些只是值,在编译时无论在何处使用它们,都将被逐字复制,因此您在运行时实际上从不"调用"静态字段。有一个静态方法不会有相同的语义,因为它涉及调用一个没有Java实现的接口,而Java是不允许的。