C++ template 과 Java generic 의 코드 생성 레벨의 차이점

mj·2023년 2월 16일
1
post-thumbnail

자바 제네릭을 공부하다가 제네릭이 C++ 의 template 과 비슷하다는 생각이 들었고, 문득 C++ template 과 자바 generic 이 코드 생성에서는 어떤 차이점이 있을지 궁금증이 생겼다.

C++ template 동작 원리

C++ template 에서는 템플릿을 사용한 함수나 클래스를 작성만 하고, main 함수에서 해당 함수를 호출하거나 객체를 생성하지 않으면 해당 함수나 클래스에 해당하는 어셈블리어 코드가 생성되지 않는다.
즉, C++ 에서 템플릿을 사용할 때는 컴파일 시점에서 해당 템블릿에 어떤 타입을 사용했는지 검사한 후 동적으로 코드를 생성하게 된다는 것이다.

일반 함수와 템플릿을 사용한 함수의 어셈블리 코드를 확인하여 실제 어떻게 코드가 생성되는지 알아보자

일반 코드 어셈블리어

템플릿을 사용하지 않은 일반 함수를 선언만 하고 호출하지 않았을 때 어셈블리 코드가 생성되는지 확인해보자

//gcc test.cpp -o output -lstdc++
#include <iostream>
using namespace std;

int sum(int a, int b) {
	return a + b;
}

int main() {
}

일반 함수 sum(int a, int b) 을 선언한 후 main 함수에서 호출을 하지 않고 컴파일 후 objdump 툴을 사용하여 어셈블리 코드를 확인해 보면 함수 이름은 조금 변경되었지만, sum 함수에 대한 코드가 있는 것을 확인할 수 있다.

$ ls -al
total 32
drwxr-xr-x 2 magan20 magan20  4096  2월 12 17:46 .
drwxr-xr-x 9 magan20 magan20  4096  2월  6 11:15 ..
-rwxrwxr-x 1 magan20 magan20 17064  2월 12 17:46 output
-rw-r--r-- 1 magan20 magan20   100  2월 12 17:46 test.cpp
$ objdump -d output | grep sum -A12
0000000000001169 <_Z3sumii>:
    1169:	f3 0f 1e fa          	endbr64 
    116d:	55                   	push   %rbp
    116e:	48 89 e5             	mov    %rsp,%rbp
    1171:	89 7d fc             	mov    %edi,-0x4(%rbp)
    1174:	89 75 f8             	mov    %esi,-0x8(%rbp)
    1177:	8b 55 fc             	mov    -0x4(%rbp),%edx
    117a:	8b 45 f8             	mov    -0x8(%rbp),%eax
    117d:	01 d0                	add    %edx,%eax
    117f:	5d                   	pop    %rbp
    1180:	c3                   	retq 

템플릿 코드 어셈블리어

이번에는 템플릿을 사용한 함수에 대한 어셈블리 코드를 확인해보자.

1. 함수 선언, 호출 X

템플릿 함수를 선언만 하고 main 함수에서 호출하지 않았을 때 어셈블리 코드는 어떻게 생성될까?

#include <iostream>
using namespace std;

template <typename T>
T sum(T num1, T num2) {
        return num1 + num2;
}

int main() {
}

템플릿 함수 sum을 선언한 후 main 함수에서 호출하지 않고 컴파일 했을 때 sum 함수에 대한 어셈블리 코드가 생성되지 않은 것을 확인할 수 있다.
또한, 위쪽에서 일반 함수를 선언한 코드의 실행 파일 output 의 크기와 아래의 output 파일의 크기를 비교했을 때도 아래쪽 파일의 크기가 더 작은 것을 확인할 수 있다.

$ ls -al
total 32
drwxr-xr-x 2 magan20 magan20  4096  2월 12 17:44 .
drwxr-xr-x 9 magan20 magan20  4096  2월  6 11:15 ..
-rwxrwxr-x 1 magan20 magan20 17032  2월 12 17:44 output
-rw-r--r-- 1 magan20 magan20   128  2월 12 17:44 test.cpp
$ objdump -d output | grep sum -A12
$

2. 함수 선언, 호출 O, 타입 1개

이번에는 템플릿 함수를 선언한 후, 타입 1개에 대해 호출해보자

#include <iostream>
using namespace std;

template <typename T>
T sum(T num1, T num2) {
                return num1 + num2;
}

int main() {
        sum(10, 10);
        sum(10, 10);
        sum(10, 10);
}

템플릿 함수를 선언하고 int 타입에 대한 sum 함수를 3번 호출했다.
이 경우 Z3sumIiET_S0_S0 라는 이름의 함수에 대한 어셈블리코드가 생성된 것을 확인할 수 있다.

$ ls -al
total 32
drwxr-xr-x 2 magan20 magan20  4096  2월 12 17:52 .
drwxr-xr-x 9 magan20 magan20  4096  2월  6 11:15 ..
-rwxrwxr-x 1 magan20 magan20 17072  2월 12 17:52 output
-rw-r--r-- 1 magan20 magan20   170  2월 12 17:52 test.cpp
$ objdump -d output | grep sum -A12
000000000000120b <_Z3sumIiET_S0_S0_>:
    120b:       f3 0f 1e fa             endbr64
    120f:       55                      push   %rbp
    1210:       48 89 e5                mov    %rsp,%rbp
    1213:       89 7d fc                mov    %edi,-0x4(%rbp)
    1216:       89 75 f8                mov    %esi,-0x8(%rbp)
    1219:       8b 55 fc                mov    -0x4(%rbp),%edx
    121c:       8b 45 f8                mov    -0x8(%rbp),%eax
    121f:       01 d0                   add    %edx,%eax
    1221:       5d                      pop    %rbp
    1222:       c3                      retq
    1223:       66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)

3. 함수 선언, 호출 O, 타입 2개

이제 템플릿 함수는 호출할 때 컴파일러에서 검사 후 어셈블리 코드를 생성한다는 것을 알게 되었다.
그럼 여러개의 타입에 대해 함수가 호출되면 어셈블리 코드는 어떻게 생성이 될까?

#include <iostream>
using namespace std;

template <typename T>
T sum(T num1, T num2) {
                return num1 + num2;
}

int main() {
        sum(10, 10);
        sum(10, 10);
        sum(10.0f, 10.0f);
}

2번 실험과 동일한 템플릿 함수를 사용한 후 main 함수에서 마지막 함수를 호출할 때 int 가 아닌 float 값을 사용해보았다.
이 경우 Z3sumIiET_S0_S0 함수 이름와 별개로 Z3sumIfET_S0_S0 라는 이름의 어셈블리 코드가 생성되었으며, 2번 실험과 코드 줄 수는 동일함에도 불구하고 output의 크기가 17072 바이트에서 17112 로 커진 것을 확인할 수 있다.

$ ls -al
total 32
drwxr-xr-x 2 magan20 magan20  4096  2월 12 17:59 .
drwxr-xr-x 9 magan20 magan20  4096  2월  6 11:15 ..
-rwxrwxr-x 1 magan20 magan20 17112  2월 12 17:59 output
-rw-r--r-- 1 magan20 magan20   176  2월 12 17:59 test.cpp
$ objdump -d output | grep sum -A12
0000000000001211 <_Z3sumIiET_S0_S0_>:
    1211:       f3 0f 1e fa             endbr64
    1215:       55                      push   %rbp
    1216:       48 89 e5                mov    %rsp,%rbp
    1219:       89 7d fc                mov    %edi,-0x4(%rbp)
    121c:       89 75 f8                mov    %esi,-0x8(%rbp)
    121f:       8b 55 fc                mov    -0x4(%rbp),%edx
    1222:       8b 45 f8                mov    -0x8(%rbp),%eax
    1225:       01 d0                   add    %edx,%eax
    1227:       5d                      pop    %rbp
    1228:       c3                      retq

0000000000001229 <_Z3sumIfET_S0_S0_>:
    1229:       f3 0f 1e fa             endbr64
    122d:       55                      push   %rbp
    122e:       48 89 e5                mov    %rsp,%rbp
    1231:       f3 0f 11 45 fc          movss  %xmm0,-0x4(%rbp)
    1236:       f3 0f 11 4d f8          movss  %xmm1,-0x8(%rbp)
    123b:       f3 0f 10 45 fc          movss  -0x4(%rbp),%xmm0
    1240:       f3 0f 58 45 f8          addss  -0x8(%rbp),%xmm0
    1245:       5d                      pop    %rbp
    1246:       c3                      retq
    1247:       66 0f 1f 84 00 00 00    nopw   0x0(%rax,%rax,1)
    124e:       00 00

4. 함수 선언, 호출 O, 타입 3

#include <iostream>
using namespace std;

template <typename T>
T sum(T num1, T num2) {
                return num1 + num2;
}

int main() {
        sum(10, 10);
        sum(10L, 10L);
        sum(10.0f, 10.0f);
}

long 타입에 대한 인자를 추가로 사용했을 때도 어셈블리 코드가 추가로 생성되고, 실행파일의 바이트 수도 늘어난 것을 확인할 수 있다.

$ ls -al
total 32
drwxr-xr-x 2 magan20 magan20  4096  2월 12 18:05 .
drwxr-xr-x 9 magan20 magan20  4096  2월  6 11:15 ..
-rwxrwxr-x 1 magan20 magan20 17152  2월 12 18:05 output
-rw-r--r-- 1 magan20 magan20   178  2월 12 18:05 test.cpp
$ objdump -d output | grep sum -A12
0000000000001211 <_Z3sumIiET_S0_S0_>:
    1211:       f3 0f 1e fa             endbr64
    1215:       55                      push   %rbp
    1216:       48 89 e5                mov    %rsp,%rbp
    1219:       89 7d fc                mov    %edi,-0x4(%rbp)
    121c:       89 75 f8                mov    %esi,-0x8(%rbp)
    121f:       8b 55 fc                mov    -0x4(%rbp),%edx
    1222:       8b 45 f8                mov    -0x8(%rbp),%eax
    1225:       01 d0                   add    %edx,%eax
    1227:       5d                      pop    %rbp
    1228:       c3                      retq

0000000000001229 <_Z3sumIlET_S0_S0_>:
    1229:       f3 0f 1e fa             endbr64
    122d:       55                      push   %rbp
    122e:       48 89 e5                mov    %rsp,%rbp
    1231:       48 89 7d f8             mov    %rdi,-0x8(%rbp)
    1235:       48 89 75 f0             mov    %rsi,-0x10(%rbp)
    1239:       48 8b 55 f8             mov    -0x8(%rbp),%rdx
    123d:       48 8b 45 f0             mov    -0x10(%rbp),%rax
    1241:       48 01 d0                add    %rdx,%rax
    1244:       5d                      pop    %rbp
    1245:       c3                      retq

0000000000001246 <_Z3sumIfET_S0_S0_>:
    1246:       f3 0f 1e fa             endbr64
    124a:       55                      push   %rbp
    124b:       48 89 e5                mov    %rsp,%rbp
    124e:       f3 0f 11 45 fc          movss  %xmm0,-0x4(%rbp)
    1253:       f3 0f 11 4d f8          movss  %xmm1,-0x8(%rbp)
    1258:       f3 0f 10 45 fc          movss  -0x4(%rbp),%xmm0
    125d:       f3 0f 58 45 f8          addss  -0x8(%rbp),%xmm0
    1262:       5d                      pop    %rbp
    1263:       c3                      retq
    1264:       66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
    126b:       00 00 00
    126e:       66 90                   xchg   %ax,%ax

결론

앞서 말한것과 같이 C++에서 템플릿을 사용할 경우에는 선언만 할 시, 어셈블리어는 생성되지 않고, 해당 템플릿 함수를 호출할 때 사용한 타입에 해당하는 어셈블리 코드가 각각 생성되는 것을 확인할 수 있었다.

자바 Generic 동작 원리

C++은 템플릿을 적용한 함수를 사용할 때 컴파일러에서 동적으로 코드를 생성해준다. 그럼 과연 자바는 코드를 어떻게 생성할까?
만약 객체를 생성하지 않아도 코드가 생성된다면 여러 타입을 사용할 때는 과연 C++과 동일하게 각각의 타입에 대한 코드가 생성될까? 아니면 말 그대로 하나의 코드를 Generic 하게 사용을 하는 것일까?

일반 클래스 바이트 코드

class Member {
        String member;

        void setMember(String member) {
                this.member = member;
        }

        String getMember() {
                return this.member;
        }
}

public class Test {
        public static void main(String[] args) {
                Member m = new Member();
        }
}

사실 javac 명령어로 컴파일 하자마자 깨달은 건데 자바는 한개의 클래스를 한개의 .class 파일로 관리하기 때문에 제네릭을 사용하든 말든 한개의 .class 파일만 생성되는게 당연했다.
어셈블리 레벨의 코드 생성은 또 다를수도 있겠지만 기계어 생성은 JVM 에서 하기 때문에 확인이 어려울 듯 하다.

일단 생성된 .class 파일의 바이트 코드를 확인하면 다음과 같다.

$ javac Test.java
$ ls -al;
total 20
drwxrwxr-x 2 magan20 magan20 4096  2월 12 18:30 .
drwxrwxr-x 3 magan20 magan20 4096  2월 12 18:10 ..
-rw-rw-r-- 1 magan20 magan20  394  2월 12 18:30 Member.class
-rw-rw-r-- 1 magan20 magan20  282  2월 12 18:30 Test.class
-rw-rw-r-- 1 magan20 magan20  237  2월 12 18:30 Test.java

Test 클래스 바이트코드

$ javap -v -p -s Test.class
Classfile /home/magan20/java/test/Test.class
  Last modified Feb 12, 2023; size 282 bytes
  MD5 checksum 1493c20476c1c8761f5ddc3f4e9f9342
  Compiled from "Test.java"
public class Test
  minor version: 0
  major version: 55
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #4                          // Test
  super_class: #5                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #5.#14         // java/lang/Object."<init>":()V
   #2 = Class              #15            // Member
   #3 = Methodref          #2.#14         // Member."<init>":()V
   #4 = Class              #16            // Test
   #5 = Class              #17            // java/lang/Object
   #6 = Utf8               <init>
   #7 = Utf8               ()V
   #8 = Utf8               Code
   #9 = Utf8               LineNumberTable
  #10 = Utf8               main
  #11 = Utf8               ([Ljava/lang/String;)V
  #12 = Utf8               SourceFile
  #13 = Utf8               Test.java
  #14 = NameAndType        #6:#7          // "<init>":()V
  #15 = Utf8               Member
  #16 = Utf8               Test
  #17 = Utf8               java/lang/Object
{
  public Test();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 13: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #2                  // class Member
         3: dup
         4: invokespecial #3                  // Method Member."<init>":()V
         7: astore_1
         8: return
      LineNumberTable:
        line 15: 0
        line 16: 8
}
SourceFile: "Test.java"

Member 클래스 바이트코드

$ javap -v -p -s Member.class
Classfile /home/magan20/java/test/Member.class
  Last modified Feb 12, 2023; size 394 bytes
  MD5 checksum 9a944371a9d03ec277a3ab2a48638581
  Compiled from "Test.java"
class Member
  minor version: 0
  major version: 55
  flags: (0x0020) ACC_SUPER
  this_class: #3                          // Member
  super_class: #4                         // java/lang/Object
  interfaces: 0, fields: 1, methods: 3, attributes: 1
Constant pool:
   #1 = Methodref          #4.#17         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#18         // Member.member:Ljava/lang/String;
   #3 = Class              #19            // Member
   #4 = Class              #20            // java/lang/Object
   #5 = Utf8               member
   #6 = Utf8               Ljava/lang/String;
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               setMember
  #12 = Utf8               (Ljava/lang/String;)V
  #13 = Utf8               getMember
  #14 = Utf8               ()Ljava/lang/String;
  #15 = Utf8               SourceFile
  #16 = Utf8               Test.java
  #17 = NameAndType        #7:#8          // "<init>":()V
  #18 = NameAndType        #5:#6          // member:Ljava/lang/String;
  #19 = Utf8               Member
  #20 = Utf8               java/lang/Object
{
  java.lang.String member;
    descriptor: Ljava/lang/String;
    flags: (0x0000)

  Member();
    descriptor: ()V
    flags: (0x0000)
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  void setMember(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    flags: (0x0000)
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: putfield      #2                  // Field member:Ljava/lang/String;
         5: return
      LineNumberTable:
        line 5: 0
        line 6: 5

  java.lang.String getMember();
    descriptor: ()Ljava/lang/String;
    flags: (0x0000)
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field member:Ljava/lang/String;
         4: areturn
      LineNumberTable:
        line 9: 0
}
SourceFile: "Test.java"

Member.class 의 바이트 코드 내용을 보면 Member 클래스의 멤버와 생성자, getter, setter 에 대한 내용이 있는 것을 확인할 수 있다.

setMember 메소드의 경우에는 java.lang.String 값을 인자로 받고 있고, putfield 부분을 보면 Ljava/lang/String 타입인 member 가 설정되어 있는것을 확인할 수 있다.

Ljava/lang/String

  • Ljava/lang/String 의미는 원본 String Value 값의 참조형을 의미

getMember 메소드는 java.lang.String 값을 반환하는 걸로 표현이 되어 있다. getfield 부분은 Ljava/lang/String 타입인 member 로 설정되어 있는것을 확인할 수 있다.

제네릭 클래스 바이트 코드

class Member<T> {
        T member;

        void setMember(T member) {
                this.member = member;
        }

        T getMember() {
                return this.member;
        }
}

public class Test {
        public static void main(String[] args) {
                Member<String> m = new Member<>();
        }
}
$ javac Test.java
$ ls -al
total 20
drwxrwxr-x 2 magan20 magan20 4096  2월 16 16:14 .
drwxrwxr-x 3 magan20 magan20 4096  2월 12 18:10 ..
-rw-rw-r-- 1 magan20 magan20  504  2월 16 16:14 Member.class
-rw-rw-r-- 1 magan20 magan20  282  2월 16 16:14 Test.class
-rw-rw-r-- 1 magan20 magan20  235  2월 16 16:14 Test.java

Test 클래스 바이트 코드

$ javap -v -p -s Test.class
Classfile /home/magan20/java/test/Test.class
  Last modified Feb 16, 2023; size 282 bytes
  MD5 checksum 1493c20476c1c8761f5ddc3f4e9f9342
  Compiled from "Test.java"
public class Test
  minor version: 0
  major version: 55
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #4                          // Test
  super_class: #5                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #5.#14         // java/lang/Object."<init>":()V
   #2 = Class              #15            // Member
   #3 = Methodref          #2.#14         // Member."<init>":()V
   #4 = Class              #16            // Test
   #5 = Class              #17            // java/lang/Object
   #6 = Utf8               <init>
   #7 = Utf8               ()V
   #8 = Utf8               Code
   #9 = Utf8               LineNumberTable
  #10 = Utf8               main
  #11 = Utf8               ([Ljava/lang/String;)V
  #12 = Utf8               SourceFile
  #13 = Utf8               Test.java
  #14 = NameAndType        #6:#7          // "<init>":()V
  #15 = Utf8               Member
  #16 = Utf8               Test
  #17 = Utf8               java/lang/Object
{
  public Test();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 13: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #2                  // class Member
         3: dup
         4: invokespecial #3                  // Method Member."<init>":()V
         7: astore_1
         8: return
      LineNumberTable:
        line 15: 0
        line 16: 8
}
SourceFile: "Test.java"

Member 클래스 바이트 코드

$ javap -v -p -s Member.class
Classfile /home/magan20/java/test/Member.class
  Last modified Feb 16, 2023; size 504 bytes
  MD5 checksum d152f3cac1d3458683cc3f2d4ede86db
  Compiled from "Test.java"
class Member<T extends java.lang.Object> extends java.lang.Object
  minor version: 0
  major version: 55
  flags: (0x0020) ACC_SUPER
  this_class: #3                          // Member
  super_class: #4                         // java/lang/Object
  interfaces: 0, fields: 1, methods: 3, attributes: 2
Constant pool:
   #1 = Methodref          #4.#22         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#23         // Member.member:Ljava/lang/Object;
   #3 = Class              #24            // Member
   #4 = Class              #25            // java/lang/Object
   #5 = Utf8               member
   #6 = Utf8               Ljava/lang/Object;
   #7 = Utf8               Signature
   #8 = Utf8               TT;
   #9 = Utf8               <init>
  #10 = Utf8               ()V
  #11 = Utf8               Code
  #12 = Utf8               LineNumberTable
  #13 = Utf8               setMember
  #14 = Utf8               (Ljava/lang/Object;)V
  #15 = Utf8               (TT;)V
  #16 = Utf8               getMember
  #17 = Utf8               ()Ljava/lang/Object;
  #18 = Utf8               ()TT;
  #19 = Utf8               <T:Ljava/lang/Object;>Ljava/lang/Object;
  #20 = Utf8               SourceFile
  #21 = Utf8               Test.java
  #22 = NameAndType        #9:#10         // "<init>":()V
  #23 = NameAndType        #5:#6          // member:Ljava/lang/Object;
  #24 = Utf8               Member
  #25 = Utf8               java/lang/Object
{
  T member;
    descriptor: Ljava/lang/Object;
    flags: (0x0000)
    Signature: #8                           // TT;

  Member();
    descriptor: ()V
    flags: (0x0000)
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  void setMember(T);
    descriptor: (Ljava/lang/Object;)V
    flags: (0x0000)
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: putfield      #2                  // Field member:Ljava/lang/Object;
         5: return
      LineNumberTable:
        line 5: 0
        line 6: 5
    Signature: #15                          // (TT;)V

  T getMember();
    descriptor: ()Ljava/lang/Object;
    flags: (0x0000)
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #2                  // Field member:Ljava/lang/Object;
         4: areturn
      LineNumberTable:
        line 9: 0
    Signature: #18                          // ()TT;
}
Signature: #19                          // <T:Ljava/lang/Object;>Ljava/lang/Object;
SourceFile: "Test.java"

Member.class 의 바이트 코드 내용을 확인해보면 일반 Member 클래스와 동일하게 멤버와 생성자, getter, setter 에 대한 내용이 있는 것을 확인할 수 있다.

setMember 메소드를 확인해보면 Ljava/lang/Object 타입의 T 값을 받아서 putfieldLjava/lang/Object 타입의 member로 설정하는 것을 볼 수 있다.

getMember 메소드는 T 값을 반환하는 걸로 표현이 되어 있고, getfield 부분은 Ljava/lang/Object 타입인 member 로 설정되어 있다.

결론

자바는 C++과 달리 제네릭 생성시 java.lang.Object 로 타입을 설정한 후 일반적인(제네릭한) .class 코드 하나를 생성한다. Object 클래스는 모든 객체의 부모이므로 어떤 객체든 인자로 받을 수가 있다. 그러므로 제네릭 클래스를 사용해서 객체 생성 시 각 타입마다 여러 코드가 만들어지는 것이 아닌 Object 타입에 다른 타입(클래스)가 대입되는 것이라고 이해하면 편할 것 같다.

이 때문에 기본 타입인 int, float 등의 타입은 제네릭에서 사용할 수 없고, Integer, Float 등의 wrapper 클래스를 사용해야 한다.

profile
사는게 쉽지가 않네요

2개의 댓글

comment-user-thumbnail
2023년 2월 16일

늘 잘읽고 있읍니다...

1개의 답글