glenn-syj / more-effective-java

이펙티브 자바를 읽으며 자바를 더 효율적으로 공부합니다
4 stars 5 forks source link

[MEJ-006] 바이트코드를 통한 타입 소거 이해 #139

Closed glenn-syj closed 4 months ago

glenn-syj commented 5 months ago

Based on: #126 by @yngbao97

바이트코드를 통한 타입 소거 이해

들어가며

Oracle의 공식 자료를 참고해서 코드를 잘 작성해주셔서, 이번에는 java 소스코드가 바이트코드에서 어떻게 읽히는지 한 번 쯤 살펴보면 좋을 것 같았습니다.

자바 코드

package java_test;

public class Node<T> {

    private T data;
    private Node<T> next;

    public Node(T data, Node<T> next) {
        this.data = data;
        this.next = next;
    }

    public T getData() { return data; }
    // ...
}

Oracle 공식 문서에 기반하는, 제네릭을 이용한 클래스 구현입니다. 필드 datanext만이 아니라, 메서드 getData()의 반환 타입에서도 제네릭이 쓰이고 있습니다. 타입만이 아니라 메서드에서도 타입 소거는 이루어지고 있습니다.

바이트코드

// class version 61.0 (61)
// access flags 0x21
// signature <T:Ljava/lang/Object;>Ljava/lang/Object;
// declaration: java_test/Node<T>
public class java_test/Node {

  // compiled from: Node.java

  // access flags 0x2
  // signature TT;
  // declaration: data extends T
  private Ljava/lang/Object; data

  // access flags 0x2
  // signature Ljava_test/Node<TT;>;
  // declaration: next extends java_test.Node<T>
  private Ljava_test/Node; next

  // access flags 0x1
  // signature (TT;Ljava_test/Node<TT;>;)V
  // declaration: void <init>(T, java_test.Node<T>)
  public <init>(Ljava/lang/Object;Ljava_test/Node;)V
   L0
    LINENUMBER 8 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init>()V
   L1
    LINENUMBER 9 L1
    ALOAD 0
    ALOAD 1
    PUTFIELD java_test/Node.data : Ljava/lang/Object;
   L2
    LINENUMBER 10 L2
    ALOAD 0
    ALOAD 2
    PUTFIELD java_test/Node.next : Ljava_test/Node;
   L3
    LINENUMBER 11 L3
    RETURN
   L4
    LOCALVARIABLE this Ljava_test/Node; L0 L4 0
    // signature Ljava_test/Node<TT;>;
    // declaration: this extends java_test.Node<T>
    LOCALVARIABLE data Ljava/lang/Object; L0 L4 1
    // signature TT;
    // declaration: data extends T
    LOCALVARIABLE next Ljava_test/Node; L0 L4 2
    // signature Ljava_test/Node<TT;>;
    // declaration: next extends java_test.Node<T>
    MAXSTACK = 2
    MAXLOCALS = 3

  // access flags 0x1
  // signature ()TT;
  // declaration: T getData()
  public getData()Ljava/lang/Object;
   L0
    LINENUMBER 13 L0
    ALOAD 0
    GETFIELD java_test/Node.data : Ljava/lang/Object;
    ARETURN
   L1
    LOCALVARIABLE this Ljava_test/Node; L0 L1 0
    // signature Ljava_test/Node<TT;>;
    // declaration: this extends java_test.Node<T>
    MAXSTACK = 1
    MAXLOCALS = 1
}

간단하게 살펴보자면, declaration이나 signature에는 <T>가 명시되지만 실제 바이트코드에서는 T가 소멸되어 Object로 이용되고 있음을 알 수 있습니다. 대표적으로 private Ljava/lang/Object; data에서 확인할 수 있습니다.

또한, 생성자의 declaration에서 // declaration: next extends java_test.Node<T> 부분도 흥미롭습니다. JVM은 생성자에서 next가 단순히 Node<T> 타입이 아니라, 이를 상속하는 타입을 받도록 지시하고 있습니다. 즉, 하위 클래스인 인자가 자동으로 상위 클래스로 형변환되는 방식에 대한 서술으로 보입니다. 비록 깊게 알고 있지는 않지만, 단순히 구현하던 것과 달리 바이트코드에서는 동작 원리를 알게 되니 더욱 직관적으로 이해가 되네요.

References

https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-4.html#jvms-4.10.2.2

yngbao97 commented 5 months ago

java 소스코드가 바이트 코드로 어떻게 변환되는지 확인할 수 있는 곳을 찾지 못해 궁금했었는데, 첨부해주셔서 감사합니다! 제네릭 T가 Object로 사용되는 것은 몇몇 설명에서 발견할 수 있었지만, nextextends Node<T>로 표현된 것은 신기하네요 말씀하신 바와 같이 하위클래스도 해당 변수의 값으로 사용될 수 있다는 것이 명시적으로 보여지는 부분 같습니다.

글로만 읽을 때보다 코드를 직접 확인하니 보다 명확히 알수 있어 도움이 되었습니다. 부가설명 감사합니다!