接口中的静态初始化

49

当我试图写出这样的东西时:

public interface MyInterface {
    static {
        System.out.println("Hello!");
    }
}
编译器无法编译它。
但是,当我写了类似这样的内容时:
interface MyInterface {
    Integer iconst = Integer.valueOf(1);
}

我反编译了它,看到了静态初始化:

public interface MyInterface{
    public static final java.lang.Integer i;

    static {};
      Code:
      0:   iconst_1
      1:   invokestatic    #1; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      4:   putstatic       #2; //Field i:Ljava/lang/Integer;
      7:   return
}

你能否向我解释一下这种行为?

5个回答

27

接口不应该有副作用,即使是静态初始化器也是如此。它们会具有高度依赖于JVM实现的行为。看下面的代码

public class InterfaceSideEffects {
  public static void main(String[] args) {
    System.out.println("InterfaceSideEffects.main()");
    Impl i=new Impl();
    System.out.println("Impl initialized");
    i.bla();
    System.out.println("Impl instance method invoked");
    Foo f=new Impl();
    System.out.println("Impl initialized and assigned to Foo");
    f.bla();
    System.out.println("Foo interface method invoked");
  }
}
interface Foo {
  int dummy=Bar.haveSideEffect();
  void bla();
}
class Bar {
  static int haveSideEffect() {
    System.out.println("interface Foo initialized");
    return 0;
  }
}
class Impl implements Foo {
  public void bla() {
  }
}

当你运行代码后,你认为何时会打印出interface Foo initialized?尝试猜测并运行代码。答案可能会让你惊讶。


2
我过去5年一直担任Java开发人员,答案真的让我感到意外。太遗憾了。谢谢! - hsestupin
5
@Dolda2000:你无法使一种编程语言变得完美无缺,至少不会在不大幅降低其实用性的情况下。因此,在设计语言时,你所能尝试的只有让正确的事情变得更容易,让错误的事情更难以实现。 - Holger
1
@Dolda2000:也许你在接口中过度使用了静态字段... - Holger
2
@Holger,顺便问一下,这个惊喜应该从哪里来呢?是因为接口中的静态字段初始化只有在实际使用时才会被延迟吗? - Eugene
2
@Eugene,不存在单独的“静态字段初始化”,而只有整个类/接口的初始化。字段未被初始化的事实表明整个接口初始化未发生(在这个Q&A的上下文中,这意味着假设的static { ... }块也不会被执行)。因此,可能会让开发人员感到惊讶的行为是,既使用实现接口的类,也不会触发接口初始化,甚至调用其上的接口方法也不会触发(更不直观的是,default方法会改变这一点)。 - Holger
显示剩余3条评论

25
你可以进行静态初始化,但你不能有静态块。事实上,静态初始化需要使用静态代码块来实现,这确实改变了Java语法。
重点是,在Java 8之前,接口中不应该有代码,但是允许初始化字段。
顺便说一句,你可以拥有一个嵌套类或枚举,其中包含任意数量的代码,并且可以在初始化字段时调用它。 ;)

谢谢回答。也许您知道为什么要这样做(在接口中禁用静态块)? - Sergey Morozov
5
接口不应该具有副作用。 - Holger
2
@frostjogla 接口只是用来定义契约的,而不提供任何实现。常量是允许的,因为它们可能会在方法中使用。 - Peter Lawrey
3
有时候接口包含公共的静态常量...如果你有一个 Set 或 Map 等内容,为了可读性的初始化可能需要一个静态块,而不引入任何副作用。 - Kip

10

如果你认为这是一个问题,你可以通过在同一文件中添加第二个非公共类来解决它。

public interface ITest {
  public static final String hello = Hello.hello();
}

// You can have non-public classes in the same file.
class Hello {
  static {
    System.out.println("Static Hello");
  }
  public static String hello() {
    System.out.println("Hello again");
    return "Hello";
  }
}

使用以下内容进行测试:

public class Test {
  public void test() {
    System.out.println("Test Hello");
    System.out.println(ITest.hello);
  }

  public static void main(String args[]) {
    try {
      new Test().test();
    } catch (Throwable t) {
      t.printStackTrace(System.err);
    }
  }

}

打印:

Test Hello
Static Hello
Hello again
Hello

Java是一种非常聪明的语言 - 它让做愚蠢的事情变得困难但不是不可能。 :)


0

接口没有任何初始化块。下面的代码片段可能会有所帮助。

public interface MyInterface {
public static final int a;// Compilation error as there is no way for 
                          // explicit initialization

}

public class MyClass {
public static final int a;// Still no error as there is another way to 
                          //initialize variable even though they are final.
 static{
    a=10;
   }

}

1
你的例子可能会让人感到困惑,因为你的第二个代码片段展示了一个 class MyInterface 的声明,而不是一个 interface - Sergey Morozov
@Sergey,谢谢你提醒我。那应该是一个类,我已经改了。 - Siddappa Walake

-2

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

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

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

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

当然,您可以从该方法中删除静态关键字。一切都会正常工作。如果它从实例方法调用,则可能需要抑制一些警告。

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


5
整个回答没有抓住问题的重点。这个问题不是关于静态方法,而是关于静态初始化器。 - Holger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接