为什么接口中没有静态方法,但静态字段和内部类可以?[Java8 之前] [重复]

StackOverflow https://stackoverflow.com/questions/129267

  •  02-07-2019
  •  | 
  •  

这个问题在这里已经有答案了:

这里提出了一些关于为什么不能在接口中定义静态方法的问题,但它们都没有解决基本的不一致问题:为什么可以在接口中定义静态字段和静态内部类型,但不能定义静态方法?

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

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

一致性是我所希望的,Java 应该要么不支持接口内任何形式的静态,要么应该保持一致并允许它们。

有帮助吗?

解决方案

一个 官方提案 Java 7 中已允许在接口中使用静态方法。该提案是根据 项目币.

我个人的意见是这是一个好主意。实施起来没有技术难度,而且是一件非常合乎逻辑、合理的事情。我希望 Project Coin 中有几个提案能够 绝不 成为 Java 语言的一部分,但这是一种可以清理大量 API 的语言。例如, Collections 类有静态方法 用于操纵任何 List 执行;这些可以包括在 List 界面。


更新: 在里面 Java Posse 播客#234, Joe D'arcy 简短地提到了该提案,称其“复杂”,可能不会纳入 Project Coin。


更新: 虽然他们没有将其纳入 Java 7 的 Project Coin,但 Java 8 确实支持 接口中的静态函数。

其他提示

我将继续我的宠物理论,即在这种情况下缺乏一致性是一个方便问题,而不是设计或必要性,因为我没有听到任何令人信服的论据表明它是这两者中的任何一个。

静态字段的存在(a)是因为它们存在于 JDK 1.0 中,并且在 JDK 1.0 中做出了许多狡猾的决定,并且(b)接口中的静态最终字段是当时 java 所拥有的最接近常量的东西。

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

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

当然,未来的 JLS 版本可能会允许这样做,而不会破坏任何东西。

在接口中声明静态方法没有任何意义。它们不能通过正常调用 MyInterface.staticMethod() 来执行。(编辑:由于最后一句话让一些人感到困惑,调用 MyClass.staticMethod() 会精确执行 MyClass 上的 staticMethod 的实现,如果 MyClass 是一个接口,则该实现不可能存在!)如果您通过指定实现类 MyImplementor.staticMethod() 来调用它们那么你必须知道实际的类,因此接口是否包含它并不重要。

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

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

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

当然,您始终可以从方法中删除 static 关键字。一切都会很好。如果从实例方法调用它,您可能必须抑制一些警告。

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

接口的目的是定义契约而不提供实现。因此,您不能拥有静态方法,因为它们必须在接口中已有实现,因为您无法覆盖静态方法。对于字段,只有静态的 final fields 是允许的,本质上是常量(在 1.5+ 中,你还可以在接口中使用枚举)。这些常量可以帮助定义没有幻数的接口。

顺便说一句,不需要明确指定 static final 接口中字段的修饰符,因为只允许静态最终字段。

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

接口的主要目的是提供一些无法实现的东西,所以如果它们提供

允许使用静态方法

然后你可以使用调用该方法 接口名称.staticMethodName(), ,但这是未实现的方法并且不包含任何内容。所以允许静态方法是没有用的。因此他们根本不提供这个。

允许静态字段

因为字段不可实现,可实现是指您不能在字段中执行任何逻辑操作,您可以在字段上执行操作。因此,您不会改变字段的行为,这就是允许它们的原因。

允许内部类

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

我希望这会有所帮助。

实际上,有时有人可以从静态方法中受益是有原因的。它们可以用作实现该接口的类的工厂方法。例如,这就是我们现在在 openjdk 中拥有 Collection 接口和 Collections 类的原因。因此,一如既往地有解决方法 - 提供另一个带有私有构造函数的类,该构造函数将用作静态方法的“命名空间”。

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

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

这意味着 html生成器 不必限定每个常量,因此可以使用 打开 代替 Html常量.OPEN

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

现在有了 Java 5,我们有了 导入静态 达到相同效果的语法:

private final class HtmlConstants {
    ...
    private HtmlConstants() { /* empty */ }
}

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

接口中没有静态方法没有真正的原因,除了:Java 语言的设计者并不希望这样。从技术角度来看,允许它们是有意义的。毕竟抽象类也可以拥有它们。我假设但没有测试它,您可以“手工制作”字节代码,其中接口具有静态方法,并且恕我直言,它应该可以毫无问题地调用该方法和/或像平常一样使用该接口。

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

我想到了两个主要原因:

  1. Java 中的静态方法不能被子类覆盖,这对于方法来说比静态字段要重要得多。在实践中,我什至从来不想重写子类中的字段,但我一直重写方法。因此,静态方法会阻止实现接口的类提供该方法自己的实现,这在很大程度上违背了使用接口的目的。

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

静态方法与类绑定。在Java中,接口在技术上并不是一个类,它是一个类型,但不是一个类(因此,关键字implements,并且接口不扩展Object)。因为接口不是类,所以它们不能有静态方法,因为没有实际的类可以附加。

您可以调用InterfaceName.class来获取该接口对应的Class对象,但是Class类明确指出它代表Java应用程序中的类和接口。但是,接口本身不被视为类,因此您不能附加静态方法。

只有静态最终字段可以在接口中声明(很像方法,即使不包含“public”关键字,静态字段也是公共的,无论有或没有关键字,静态字段都是“最终”)。

这些只是值,并且将在编译时使用它们的任何地方逐字复制,因此您实际上永远不会在运行时“调用”静态字段。静态方法不会具有相同的语义,因为它会涉及调用没有实现的接口,而 Java 不允许这样做。

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

至于为什么接口允许静态字段。我有一种感觉,应该算是一个“特色”。我能想到的唯一可能性是将接口实现感兴趣的常量分组。

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

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

Java 1.8接口静态方法仅对接口方法可见,如果我们从InterfaceExample类中删除MethodSta1()方法,我们将无法将其用于Interfaceexample对象。然而,与其他静态方法一样,我们可以使用类名来使用接口静态方法。例如,一个有效的语句是:exp1.methodSta1();

所以在看了下面的例子之后我们可以说:1)Java接口静态方法是接口的一部分,我们不能将其用于实现类对象。

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

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

4)我们不能为Object类方法定义接口静态方法,我们会得到编译器错误“此静态方法无法隐藏Object的实例方法”。这是因为 Java 中不允许这样做,因为 Object 是所有类的基类,我们不能拥有一个类级静态方法和另一个具有相同签名的实例方法。

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

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
}
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top