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


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-8043275 및 JDK-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.