关于Java:为什么我不能在接口中声明静态方法?

关于Java:为什么我不能在接口中声明静态方法?

Why can't I declare static methods in an interface?

主题说明了其中的大部分——静态方法不能在接口中声明这一事实的原因是什么?

1
2
3
public interface ITest {
    public static String test();
}

上面的代码给出了以下错误(至少在Eclipse中):"接口方法itest.test()的非法修饰符;只允许使用public&abstract"。


这里有几个问题要讨论。第一个问题是声明一个静态方法而不定义它。这就是

1
2
3
public interface Foo {
  public static int bar();
}

1
2
3
4
5
public interface Foo {
  public static int bar() {
    ...
  }
}

第一个是不可能的,因为ESPO提到:您不知道哪个实现类是正确的定义。

Java可以允许后者;事实上,从Java 8开始,它可以!


在接口中不能使用静态方法的原因在于Java解析静态引用的方式。当尝试执行静态方法时,Java不会麻烦寻找类的实例。这是因为静态方法不依赖实例,因此可以直接从类文件执行。考虑到接口中的所有方法都是抽象的,虚拟机必须查找接口的特定实现,以便找到静态方法背后的代码,以便执行该方法。这就与静态方法解析的工作原理相矛盾,并将在语言中引入不一致性。


我举个例子回答你的问题。假设我们有一个带有静态方法add的数学类。您可以这样调用此方法:

1
Math.add(2, 3);

如果Math是接口而不是类,则它不能有任何已定义的函数。因此,说一些类似数学的话。加(2,3)是没有意义的。


其原因在于Java不允许多重继承的设计原则。多重继承的问题可以通过以下示例来说明:

1
2
3
4
5
6
7
public class A {
   public method x() {...}
}
public class B {
   public method x() {...}
}
public class C extends A, B { ... }

现在,如果调用c.x()会发生什么?将执行a.x()还是b.x()?每个具有多重继承的语言都必须解决这个问题。

接口允许Java中某种限制的多重继承。为了避免上述问题,不允许使用方法。如果我们研究接口和静态方法的相同问题:

1
2
3
4
5
6
7
public interface A {
   public static method x() {...}
}
public interface B {
   public static method x() {...}
}
public class C implements A, B { ... }

同样的问题,如果调用c.x()会发生什么?


静态方法不是实例方法。没有实例上下文,因此从接口实现它没有什么意义。


现在Java8允许我们在接口中定义甚至静态的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface X {
    static void foo() {
       System.out.println("foo");
    }
}

class Y implements X {
    //...
}

public class Z {
   public static void main(String[] args) {
      X.foo();
      // Y.foo(); // won't compile because foo() is a Static Method of X and not Y
   }
}

注意:如果我们不显式地使用关键字default/static使它们成为默认方法和静态方法resp,那么默认情况下,接口中的方法仍然是公共抽象的。


你的问题有一个非常好和简洁的答案。(我觉得这是一种非常直接的解释方式,我想从这里把它联系起来。)


在Java 8中,接口中的静态方法似乎可以得到支持,我的解决方案只是在内部类中定义它们。

1
2
3
4
5
6
7
8
interface Foo {
    // ...
    class fn {
        public static void func1(...) {
            // ...
        }
    }
}

同样的技术也可用于注释:

1
2
3
4
5
6
7
8
9
10
public @interface Foo {
    String value();

    class fn {
        public static String getValue(Object obj) {
            Foo foo = obj.getClass().getAnnotation(Foo.class);
            return foo == null ? null : foo.value();
        }
    }
}

内部类应该始终以Interface.fn...的形式访问,而不是以Class.fn...的形式访问,这样就可以消除不明确的问题。


接口用于多态性,它适用于对象,而不是类型。因此(如前所述),拥有一个静态接口成员是没有意义的。


Java 8已经改变了世界,你可以在界面中使用静态方法,但是它迫使你为它提供实现。

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
public interface StaticMethodInterface {
public static int testStaticMethod() {
    return 0;
}

/**
 * Illegal combination of modifiers for the interface method
 * testStaticMethod; only one of abstract, default, or static permitted
 *
 * @param i
 * @return
 */

// public static abstract int testStaticMethod(float i);

default int testNonStaticMethod() {
    return 1;
}

/**
 * Without implementation.
 *
 * @param i
 * @return
 */

int testNonStaticMethod(float i);

}


With Java 8, interfaces can now have static methods.

例如,Comparator有一个静态的naturalOrder()方法。

接口不能实现的要求也得到了放宽。接口现在可以声明"默认"方法实现,这与普通实现类似,但有一个例外:如果从接口继承默认实现,从超类继承普通实现,则超类的实现将始终优先。


因为静态方法不能被继承。所以把它放在接口中没用。接口基本上是所有订户都必须遵守的合同。在接口中放置静态方法将强制订阅服务器实现它。这与静态方法不能被继承的事实相矛盾。


修饰符非法组合:静态和抽象

如果一个类的成员被声明为静态的,那么它可以与限制在该类中的类名一起使用,而不创建对象。

如果某个类的成员声明为抽象的,则需要将该类声明为抽象的,并且需要在继承的类(子类)中提供该抽象成员的实现。

您需要为要更改静态方法行为的子类中的类的抽象成员提供一个实现,该静态方法的行为也被声明为抽象,该抽象仅限于基类,这是不正确的


也许一个代码示例会有所帮助,我将使用C,但您应该能够遵循。

假设我们有一个名为ipayable的接口

1
2
3
4
public interface IPayable
{
    public Pay(double amount);
}

现在,我们有两个具体的类来实现这个接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class BusinessAccount : IPayable
{
    public void Pay(double amount)
    {
        //Logic
    }
}

public class CustomerAccount : IPayable
{
    public void Pay(double amount)
    {
        //Logic
    }
}

现在,假设我们有各种帐户的集合,为此,我们将使用ipayable类型的通用列表

1
2
3
List<IPayable> accountsToPay = new List<IPayable>();
accountsToPay.add(new CustomerAccount());
accountsToPay.add(new BusinessAccount());

现在,我们要向所有这些账户支付50美元:

1
2
3
4
foreach (IPayable account in accountsToPay)
{
    account.Pay(50.00);
}

所以现在您看到了接口是如何非常有用的。

它们仅用于实例化对象。不在静态类上。

如果您将Pay设置为静态,则在通过iPayable的In AccountStopay循环时,将无法确定它是否应调用BusinessAccount或CustomerAccount上的Pay。


推荐阅读