기본 메소드가있는 인터페이스는 언제 초기화됩니까?


94

대답 Java 언어 사양을 통해 검색하는 동안 이 질문을 , 나는 배운 것을

클래스가 초기화되기 전에 직접 수퍼 클래스를 초기화해야 하지만 클래스에 의해 구현 된 인터페이스는 초기화되지 않습니다. 마찬가지로 인터페이스의 수퍼 인터페이스는 인터페이스가 초기화되기 전에 초기화되지 않습니다.

호기심 때문에 시도해 보았지만 예상대로 인터페이스 InterfaceType가 초기화되지 않았습니다.

public class Example {
    public static void main(String[] args) throws Exception {
        InterfaceType foo = new InterfaceTypeImpl();
        foo.method();
    }
}

class InterfaceTypeImpl implements InterfaceType {
    @Override
    public void method() {
        System.out.println("implemented method");
    }
}

class ClassInitializer {
    static {
        System.out.println("static initializer");
    }
}

interface InterfaceType {
    public static final ClassInitializer init = new ClassInitializer();

    public void method();
}

이 프로그램은

implemented method

그러나 인터페이스가 default메서드를 선언하면 초기화가 발생합니다. InterfaceType주어진 인터페이스를 고려하십시오.

interface InterfaceType {
    public static final ClassInitializer init = new ClassInitializer();

    public default void method() {
        System.out.println("default method");
    }
}

위의 동일한 프로그램이 인쇄됩니다

static initializer  
implemented method

즉, static인터페이스 의 필드가 초기화되고 ( 상세 초기화 절차의 9 단계 ) static초기화중인 유형의 이니셜 라이저가 실행됩니다. 이는 인터페이스가 초기화되었음을 의미합니다.

JLS에서 이것이 발생해야 함을 나타내는 것을 찾을 수 없습니다. 오해하지 마십시오. 구현 클래스가 메서드에 대한 구현을 제공하지 않는 경우에 이런 일이 발생해야한다는 것을 이해합니다. 이 조건이 Java 언어 사양에서 누락되었거나 누락되었거나 잘못 해석하고 있습니까?


4
내 생각 엔-그러한 인터페이스는 초기화 순서 측면에서 추상 클래스로 간주됩니다. 나는이 올바른 문 :인지 모르겠어요으로 주석으로 쓴
알렉세이 레브

JLS의 섹션 12.4에 있어야하지만 거기에없는 것 같습니다. 누락되었다고 말하고 싶습니다.
Warren Dew 2014

1
신경 쓰지 마세요 .... 대부분 그들이 이해하지 못하거나 설명이 없을 때
비추천

interfaceJava에서는 구체적인 방법을 정의해서는 안된다고 생각했습니다 . 그래서 InterfaceType코드가 컴파일 된 것에 놀랐습니다 .
MaxZoom

답변:


85

이것은 매우 흥미로운 문제입니다!

JLS 섹션 12.4.1 이이를 확실히 다루어야 할 것 같습니다 . 그러나 Oracle JDK 및 OpenJDK (javac 및 HotSpot)의 동작은 여기에 지정된 것과 다릅니다. 특히이 섹션의 예제 12.4.1-3에서는 인터페이스 초기화를 다룹니다. 다음과 같은 예 :

interface I {
    int i = 1, ii = Test.out("ii", 2);
}
interface J extends I {
    int j = Test.out("j", 3), jj = Test.out("jj", 4);
}
interface K extends J {
    int k = Test.out("k", 5);
}
class Test {
    public static void main(String[] args) {
        System.out.println(J.i);
        System.out.println(K.j);
    }
    static int out(String s, int i) {
        System.out.println(s + "=" + i);
        return i;
    }
}

예상되는 출력은 다음과 같습니다.

1
j=3
jj=4
3

실제로 예상되는 출력을 얻습니다. 그러나 기본 메소드가 interface에 추가 I되면

interface I {
    int i = 1, ii = Test.out("ii", 2);
    default void method() { } // causes initialization!
}

출력은 다음과 같이 변경됩니다.

1
ii=2
j=3
jj=4
3

이는 인터페이스 I가 이전에 없었던 곳에서 초기화되고 있음을 명확하게 나타냅니다 ! 기본 메서드 만 있으면 초기화를 트리거 할 수 있습니다. 기본 메서드를 호출하거나 재정의하거나 언급 할 필요가 없으며 추상 메서드가 초기화를 트리거 할 필요도 없습니다.

내 추측은 HotSpot 구현이 invokevirtual호출 의 중요한 경로에 클래스 / 인터페이스 초기화 검사를 추가하지 않기를 원한다는 것 입니다. Java 8 및 기본 메소드 이전에는 invokevirtual인터페이스에서 코드를 실행할 수 없었기 때문에 이런 일이 발생하지 않았습니다. 이것이 메소드 테이블과 같은 것을 초기화 하는 클래스 / 인터페이스 준비 단계 ( JLS 12.3.2 )의 일부라고 생각할 수 있습니다. 그러나 아마도 이것은 너무 멀리 가서 실수로 전체 초기화를 대신했습니다.

나는 한 이 질문을 제기 오픈 JDK 컴파일러-dev 메일 링리스트를. 가있었습니다 알렉스 버클리에서 응답 그는 더 JVM에 지시 질문과 람다 구현 팀을 제기하는합니다 (JLS의 편집자). 그는 또한 "T는 클래스이고 T에 의해 선언 된 정적 메서드가 호출됩니다"라는 사양의 버그가 T가 인터페이스 인 경우에도 적용되어야한다고 언급합니다. 따라서 여기에 사양 및 HotSpot 버그가 모두있을 수 있습니다.

공개 : 저는 OpenJDK에서 Oracle에서 일합니다. 사람들이 이것이이 질문에 대한 현상금을받는 데 불공정 한 이점을 준다고 생각한다면, 나는 그것에 대해 유연하게 기꺼이 할 것입니다.


6
나는 공식 출처를 물었다. 이것보다 더 공식적이지 않다고 생각합니다. 모든 상황을 보려면 이틀을주세요.
Sotirios Delimanolis 2014

48
@StuartMarks " 만약 사람들이 이것이 나에게 불공정 한 이점을 준다고 생각한다면 "=> 우리는 질문에 대한 답을 얻기 위해 여기에 있으며 이것은 완벽한 답입니다!
assylias 2014

2
참고 사항 : JVM 사양에는 JLS와 유사한 설명이 포함되어 있습니다. docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.5 이것도 업데이트해야합니다. .
Marco13

2
@assylias 및 Sotirios, 귀하의 의견에 감사드립니다. assylias의 의견에 대한 14 개의 찬성표 (이 글 현재)와 함께 잠재적 인 불공정성에 대한 나의 우려를 완화했습니다.
Stuart Marks

1
@SotiriosDelimanolis JDK-8043275JDK-8043190 이라는 두 가지 버그가 있으며 8u40에서 수정 된 것으로 표시됩니다. 그러나 동작은 동일하게 보입니다. 이와 관련하여 일부 JVM 사양 변경 사항도 있었으므로 수정은 "이전 초기화 순서 복원"이외의 것일 수 있습니다.
Stuart Marks

13

상수 InterfaceType.init가 아닌 값 (메소드 호출)으로 초기화되는 상수 필드 는 어디에도 사용되지 않기 때문에 인터페이스가 초기화 되지 않습니다.

컴파일 타임에는 인터페이스의 상수 필드가 어디에도 사용되지 않고 인터페이스에 기본 메소드 (java-8)가 포함되어 있지 않기 때문에 인터페이스를 초기화하거나로드 할 필요가 없음이 알려져 있습니다.

다음과 같은 경우 인터페이스가 초기화됩니다.

  • 상수 필드는 코드에서 사용됩니다.
  • 인터페이스에 기본 메소드가 포함됨 (Java 8)

의 경우 기본 방법 , 당신은 구현하고 있습니다 InterfaceType. 따라서 If InterfaceType는 기본 메서드를 포함하며 클래스를 구현할 때 INHERITED (사용) 됩니다. 그리고 초기화는 그림에있을 것입니다.

단, 인터페이스의 상수 필드 (정상 초기화)에 접근하는 경우에는 인터페이스 초기화가 필요하지 않습니다.

다음 코드를 고려하십시오.

public class Example {
    public static void main(String[] args) throws Exception {
        InterfaceType foo = new InterfaceTypeImpl();
        System.out.println(InterfaceType.init);
        foo.method();
    }
}

class InterfaceTypeImpl implements InterfaceType {
    @Override
    public void method() {
        System.out.println("implemented method");
    }
}

class ClassInitializer {
    static {
        System.out.println("static initializer");
    }
}

interface InterfaceType {
    public static final ClassInitializer init = new ClassInitializer();

    public void method();
}

위의 경우 필드를 사용하고 있기 때문에 Interface가 초기화되고로드 InterfaceType.init됩니다.

귀하의 질문에서 이미 제공 한 기본 방법 예제를 제공하지 않습니다.

Java 언어 사양 및 예제는 JLS 12.4.1에 제공됩니다 (예에는 기본 메서드가 포함되지 않음).


기본 방법에 대한 JLS를 찾을 수 없습니다. 두 가지 가능성이있을 수 있습니다.

  • Java 사람들은 기본 방법의 경우를 고려하는 것을 잊었습니다. (사양 문서 버그)
  • 기본 메소드를 인터페이스의 상수가 아닌 멤버로 참조합니다. (하지만 어디에서도 사양 문서 버그를 언급하지 않았습니다.)

기본 방법에 대한 참조를 찾고 있습니다. 필드는 인터페이스가 초기화되었는지 여부를 보여주기위한 것입니다.
Sotirios Delimanolis 2014

@SotiriosDelimanolis 나는 기본 방법에 대한 답변에서 이유를 언급했지만 불행히도 아직 기본 방법에 대한 JLS를 찾을 수 없습니다.
아니 버그

불행히도 그것이 제가 찾고있는 것입니다. 나는 당신의 대답이 내가 이미 질문에서 말한 것을 반복하는 것 같습니다. 인터페이스가 default메소드를 포함 하고 인터페이스를 구현하는 클래스가 초기화되면 인터페이스가 초기화됩니다.
Sotirios Delimanolis 2014

나는 자바 사람들이 기본 메소드의 경우를 고려하는 것을 잊었거나 기본 메소드를 인터페이스의 상수가 아닌 멤버로 참조한다고 생각합니다 (내 가정은 어떤 문서에서도 찾을 수 없습니다).
버그 아님

1
@KishanSarsechaGajjar : 인터페이스에서 상수가 아닌 필드 란 무엇을 의미합니까? 인터페이스의 모든 변수 / 필드는 기본적으로 정적 최종입니다.
Lokesh

10

OpenJDK 의 instanceKlass.cpp 파일에는 JVM 사양 의 초기화 섹션에있는 JLS InstanceKlass::initialize_impl세부 초기화 절차 에 해당하는 초기화 메서드 가 포함되어 있습니다 .

여기에는 JLS에 언급되지 않고 코드에서 참조되는 JVM 책에는없는 새로운 단계가 포함되어 있습니다.

// refer to the JVM book page 47 for description of steps
...

if (this_oop->has_default_methods()) {
  // Step 7.5: initialize any interfaces which have default methods
  for (int i = 0; i < this_oop->local_interfaces()->length(); ++i) {
    Klass* iface = this_oop->local_interfaces()->at(i);
    InstanceKlass* ik = InstanceKlass::cast(iface);
    if (ik->has_default_methods() && ik->should_be_initialized()) {
      ik->initialize(THREAD);
    ....
    }
  }
}

따라서이 초기화는 새로운 7.5 단계명시 적 으로 구현되었습니다 . 이는이 구현이 일부 사양을 따 랐음을 나타내지 만 웹 사이트에 작성된 사양이 그에 따라 업데이트되지 않은 것 같습니다.

편집 : 참조로, 각 단계가 구현에 포함 된 커밋 (2012 년 10 월부터) : http://hg.openjdk.java.net/jdk8/build/hotspot/rev/4735d2c84362

EDIT2 : 우연히도 마지막에 흥미로운 사이드 노트가 포함 된 핫스팟의 기본 방법에 대한문서를 찾았습니다 .

3.7 기타

이제 인터페이스에 바이트 코드가 있으므로 구현 클래스가 초기화 될 때 초기화해야합니다.


1
찾아 주셔서 감사합니다. (+1) 새로운 "7.5 단계"가 사양에서 실수로 생략되었거나 제안 및 거부되었으며 구현이이를 제거하도록 수정되지 않았을 수 있습니다.
Stuart Marks

1

나는 인터페이스 초기화가 하위 유형이 의존하는 부 채널 부작용을 일으키지 않아야하는 경우를 만들려고 노력할 것입니다. 따라서 이것이 버그인지 아닌지 또는 Java가 수정하는 방식에 상관없이 중요하지 않습니다. 순서 인터페이스가 초기화되는 애플리케이션

의 경우 class하위 클래스가 의존하는 부작용을 일으킬 수 있다는 것이 잘 알려져 있습니다. 예를 들면

class Foo{
    static{
        Bank.deposit($1000);
...

의 모든 하위 클래스 Foo는 하위 클래스 코드의 어느 곳에서나 은행에서 $ 1000를 볼 것으로 예상합니다. 따라서 수퍼 클래스는 서브 클래스보다 먼저 초기화됩니다.

superintefaces에도 똑같이해야하지 않나요? 안타깝게도 수퍼 인터페이스의 순서는 중요하지 않으므로 초기화 할 순서가 잘 정의되어 있지 않습니다.

따라서 인터페이스 초기화에서 이런 종류의 부작용을 설정하지 않는 것이 좋습니다. 결국, interface편의를 위해 쌓아 두는 이러한 기능 (정적 필드 / 방법)을위한 것은 아닙니다.

따라서이 원칙을 따르면 인터페이스가 초기화되는 순서는 상관이 없습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.