1. 개요

이 예제에서는 불충분 한 가시성 또는 누락 된 참조로 인해 도달 할 수없는 멤버에 대한 액세스를 투명하게 처리하기 위해 컴파일러가 도입 한 코드 인 Java의 합성 구조를 살펴볼 것입니다.

참고 : JDK 11 부터 합성 메서드 및 생성자는 중첩 기반 액세스 제어로 대체되므로 더 이상 생성되지 않습니다 .

2. 자바의 합성

우리가 찾을 수있는 가장 좋은 합성 정의 는 Java 언어 사양 ( JLS 13.1.7 ) 에서 직접 가져온 것입니다 .

기본 생성자, 클래스 초기화 메서드, Enum 클래스의 값 및 valueOf 메서드를 제외하고 소스 코드에 해당 구조가없는 Java 컴파일러에 의해 도입 된 모든 구조는 합성으로 표시되어야합니다.

여러 종류의 컴파일 구성, 즉 필드, 생성자 및 메서드가 있습니다. 반면에 중첩 된 클래스는 컴파일러 (즉, 익명 클래스)에 의해 변경 될 수 있지만 합성으로 간주되지 않습니다 .

더 이상 고민하지 않고 각각에 대해 자세히 살펴 보겠습니다.

3. 합성 분야

간단한 중첩 클래스부터 시작하겠습니다.

public class SyntheticFieldDemo {
    class NestedClass {}
}

컴파일되면 모든 내부 클래스에는  최상위 클래스를 참조 하는 합성 필드가 포함됩니다 . 우연히도 이것은 중첩 된 클래스에서 둘러싸는 클래스 멤버에 액세스 할 수있게합니다.

이것이 일어나고 있는지 확인하기 위해 리플렉션에 의해 중첩 된 클래스 필드를 가져오고 isSynthetic () 메서드를 사용하여 확인하는 테스트를 구현합니다 .

public void givenSyntheticField_whenIsSynthetic_thenTrue() {
    Field[] fields = SyntheticFieldDemo.NestedClass.class
      .getDeclaredFields();
    assertEquals("This class should contain only one field",
      1, fields.length);

    for (Field f : fields) {
        System.out.println("Field: " + f.getName() + ", isSynthetic: " +
          f.isSynthetic());
        assertTrue("All the fields of this class should be synthetic", 
          f.isSynthetic());
    }
}

이를 확인할 수있는 또 다른 방법은 javap 명령을 통해 디스어셈블러를 실행하는 것 입니다. 두 경우 모두 출력에 this $ 0 이라는 합성 필드가 표시  됩니다.

4. 합성 방법

다음으로 중첩 클래스에 private 필드를 추가합니다.

public class SyntheticMethodDemo {
    class NestedClass {
        private String nestedField;
    }

    public String getNestedField() {
        return new NestedClass().nestedField;
    }

    public void setNestedField(String nestedField) {
        new NestedClass().nestedField = nestedField;
    }
}

이 경우 컴파일은 변수에 대한 접근자를 생성합니다. 이러한 메서드가 없으면 둘러싸는 인스턴스에서 개인 필드에 액세스 할 수 없습니다.

다시 한 번 access $ 0access $ 1 이라는 두 가지 합성 메서드를 보여주는 동일한 기술로이를 확인할 수 있습니다 .

public void givenSyntheticMethod_whenIsSynthetic_thenTrue() {
    Method[] methods = SyntheticMethodDemo.NestedClass.class
      .getDeclaredMethods();
    assertEquals("This class should contain only two methods",
      2, methods.length);

    for (Method m : methods) {
        System.out.println("Method: " + m.getName() + ", isSynthetic: " +
          m.isSynthetic());
        assertTrue("All the methods of this class should be synthetic",
          m.isSynthetic());
    }
}

통지 코드를 생성하기 위해, 필드가 실제로에서 읽을 수 있어야합니다 또는 기록은 , 그렇지 않으면, 방법은 멀리 최적화됩니다 . 이것이 우리가 getter와 setter를 추가 한 이유입니다.

위에서 언급했듯이 이러한 합성 방법은 더 이상 JDK 11부터 생성되지 않습니다.

4.1. 브리지 방법

합성 메서드의 특별한 경우는 제네릭의 유형 삭제를 처리하는 브리지 메서드입니다.

예를 들어 간단한  비교기를 생각해 봅시다 .

public class BridgeMethodDemo implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {
        return 0;
    }
}

하지만  () 비교  이 개 소요 정수 소스에 인수를 컴파일하면 두 개의 할게요 개체 삭제를 입력하기 때문에 대신 인수를.

이를 관리하기 위해 컴파일러는 인수 캐스팅을 처리하는 합성 브리지를 만듭니다 .

public int compare(Object o1, Object o2) {
    return compare((Integer) o1, (Integer) o2);
}

이전 테스트 외에도 이번에는 Method 클래스  에서 isBridge () 를  호출합니다  .

public void givenBridgeMethod_whenIsBridge_thenTrue() {
    int syntheticMethods = 0;
    Method[] methods = BridgeMethodDemo.class.getDeclaredMethods();
    for (Method m : methods) {
        System.out.println("Method: " + m.getName() + ", isSynthetic: " +
          m.isSynthetic() + ", isBridge: " + m.isBridge());
        if (m.isSynthetic()) {
            syntheticMethods++;
            assertTrue("The synthetic method in this class should also be a bridge method",
              m.isBridge());
        }
    }
    assertEquals("There should be exactly 1 synthetic bridge method in this class",
      1, syntheticMethods);
}

5. 합성 생성자

마지막으로 개인 생성자를 추가합니다.

public class SyntheticConstructorDemo {
    private NestedClass nestedClass = new NestedClass();

    class NestedClass {
        private NestedClass() {}
    }
}

이번에는 테스트 또는 디스어셈블러를 실행하면 실제로 두 개의 생성자가 있으며 그중 하나는 합성입니다.

public void givenSyntheticConstructor_whenIsSynthetic_thenTrue() {
    int syntheticConstructors = 0;
    Constructor<?>[] constructors = SyntheticConstructorDemo.NestedClass
      .class.getDeclaredConstructors();
    assertEquals("This class should contain only two constructors",
      2, constructors.length);

    for (Constructor<?> c : constructors) {
        System.out.println("Constructor: " + c.getName() +
          ", isSynthetic: " + c.isSynthetic());

        if (c.isSynthetic()) {
            syntheticConstructors++;
        }
    }

    assertEquals(1, syntheticConstructors);
}

합성 필드와 마찬가지로이 생성 된 생성자는 둘러싸는 인스턴스의 전용 생성자로 중첩 된 클래스를 인스턴스화하는 데 필수적입니다.

위에서 언급했듯이 합성 생성자는 더 이상 JDK 11부터 생성되지 않습니다.

6. 결론

이 기사에서는 Java 컴파일러에 의해 생성 된 합성 구조에 대해 논의했습니다. 이를 테스트하기 위해 리플렉션을 사용 했습니다 . 여기에서 자세히 알아볼 수  있습니다 .

항상 그렇듯이 모든 코드는 GitHub에서 사용할 수  있습니다 .