Skip to main content

Command Palette

Search for a command to run...

자바의 타입 소거

Published
자바의 타입 소거

원문: Dilip Thakkar, "Type erasure in Java"

자바 컴파일러는 제네릭 클래스나 함수를 일반 클래스나 함수로 변환합니다. 이 과정에서 타입 매개 변수를 타입 선언에 정의된 한정적 타입(bounded type)이나 Object 클래스로 대체하는데, 이를 타입 소거(Type Erasure)라고 합니다.

자바의 제네릭

선수지식

이 글에서 논의할 개념을 이해하려면 타입 시스템과 제네릭에 대한 기본 지식이 필요합니다. 타입 소거를 확실히 파악하기 위해서 기본 사항을 먼저 살펴보는 것이 좋습니다.

타입 소거가 중요한 이유는 무엇인가요?

  1. 타입 소거는 다른 타입을 위한 추가적인 클래스가 생성되지 않도록 보장합니다.

  2. 필요한 곳에 자동 형변환을 수행하여 타입 안정성을 유지합니다.

  3. 타입 안정성과 함께 다형성을 지원하기 위해 합성 함수를 생성합니다.

다음과 같은 상황에서의 타입 소거를 논의하려고 합니다.

  1. 특정 타입 매개변수를 갖는 제네릭 클래스나 함수

  2. 한정적 타입 매개변수를 갖는 제네릭 클래스나 함수

  3. 제네릭 클래스를 상속받는 클래스

특정 타입 매개변수를 갖는 제네릭 클래스나 함수에서의 타입 소거

모든 타입의 요소를 담을 수 있는 컨테이너 클래스를 만들어 보겠습니다.

// 어떤 타입의 요소든 저장할 수 있는 제네릭 클래스
class Container<T>{
  T value;

  Container(T value){this.value = value;}

  public void setValue(T value) {
      this.value = value;
  }
}

타입 소거를 하게 되면 컴파일러는 해당 클래스를 바이트 코드로 변환합니다. 이 과정에서 모든 타입 매개변수를 제거하고 Object 클래스로 대체합니다.

// 컴파일러에 의한 타입 소거 후의 클래스
class Container{
  Object value; // Object로 대체된 타입 T

  Container(Object value){this.value = value;}

  public void setValue(Object value) {
      this.value = value;
  }
}

컴파일러는 제네릭 함수에도 동일하게 작동합니다. 타입 소거 전후의 제네릭 함수를 살펴보겠습니다.

// 어떤 타입의 요소든 출력할 수 있는 제네릭 함수
public <T> void printValue(T value) {
   System.out.println(value);
}

타입 소거 후는 다음과 같습니다.

// 컴파일러에 의한 타입 소거 후의 함수
public void printValue(Object value) { // Object로 대체된 타입 T
   System.out.println(value);
}

한정적 타입 매개변수를 갖는 제네릭 클래스나 함수에서의 타입 소거

클래스나 함수 선언에 한정적 타입 매개변수가 있다면 컴파일러는 타입을 한정적 타입으로 대체합니다.

// 상한 타입을 갖는 제네릭 클래스
// 타입은 Number거나 Number의 하위 타입이어야 합니다.
class Container<T extends Number>{
   T value;

   Container(T value){this.value = value;}

   public void setValue(T value) {
       this.value = value;
   }
}

타입 소거 후는 다음과 같습니다.

// 컴파일러에 의한 타입 소거 후의 컨테이너 클래스
class Container {
   // 타입 T는 상한 타입인 Number로 대체되었습니다.
   Number value;

   Container(Number value) {
       this.value = value;
   }

   public void setValue(Number value) {
       this.value = value;
   }
}

제네릭 클래스를 상속받는 클래스에서의 타입 소거

다음 예시를 고려해 봅시다. 두 개의 클래스가 있습니다. 첫 번째는 어떤 타입의 데이터가 주어지더라도 저장할 수 있는 제네릭 클래스 Node입니다. 두 번째는 제네릭이 아닌 비제네릭 클래스인데, 문자열 매개변수만 저장할 수 있는 클래스 Node를 상속하고 있습니다.

// 모든 타입의 값을 저장할 수 있는 제네릭 클래스
class Node<T>{
   T value;

   Node(T value){
       this.value = value;
   }

   //제네릭 클래스의 setter 메서드
   public void setValue(T value) {
       this.value = value;
   }
}

// 문자열 타입 값만 저장할 수 있는 비제네릭 클래스
class StringNode extends Node<String>{

   StringNode(String value) {
       super(value);
   }

   // node 값을 설정하는 setter 메서드
   @Override
   public void setValue(String value) {
       super.setValue(value);
   } 
}

이전 섹션에서 타입 소거의 몇 가지 예시에 대해 논의해 보았습니다. 이제 여태까지 해왔던 대로 타입 소거 후의 클래스 구조를 살펴보겠습니다.

컴파일러는 클래스 Node와 클래스 StringNode에서 타입을 소거한 후 다음과 같은 비 제네릭 클래스를 생성하게 됩니다.

// 컴파일러에 의한 타입 소거 후의 클래스 Node
class Node{
   Object value;

   Node(Object value){
       this.value = value;
   }

   // 타입 소거 후 제네릭 클래스의 setter 메서드
   public void setValue(Object value) {
       this.value = value;
   }
}

// 컴파일러에 의한 타입 소거 후의 클래스 StringNode
class StringNode extends Node{

   StringNode(String value) {
       super(value);
   }

   // node 값을 설정하는 setter 메서드
   public void setValue(String value) {
       super.setValue(value);
   } 
}

보시다시피 클래스 StringNodesetValue(String value) 메서드는 클래스 NodesetValue(Object value) 메서드를 오버라이딩하지 않습니다. 두 메서드의 매개변수 타입이 다르기 때문입니다. 이는 다형성 규칙을 위반하는 것입니다.

예시의 도움을 받아 이것이 실질적으로 어떤 문제로 이어지는지 이해해 봅시다.

StringNode stringNode = new StringNode("Hello");
Node node = stringNode;
node.setValue(5);

StringNode의 인스턴스를 생성하여 Node의 원시 타입 참조에 할당하였습니다. 이는 NodeStringNode의 부모 클래스이기 때문에 문제가 되지 않습니다. 문자열 타입이 아닌 객체를 삽입하려고 한다고 해도 클래스 NodesetValue(Object value) 메서드가 있기 때문에 이 또한 허용됩니다. 그러나 사실 객체 삽입이 허용되어서는 안 됩니다. 우리가 생성한 StringNode 인스턴스는 오직 문자열만 저장해야 하기 때문입니다.

이 문제는 타입 소거 후 StringNodesetValue가 부모의 메서드를 위임하지 않기 때문에 발생하는 것입니다. 타입 소거 후 클래스 구조를 생성하는 과정에서 무언가를 놓쳤다는 것을 의미하는 것이죠. 컴파일러는 이러한 문제를 합성 메서드를 만들어 해결하는데, 이를 Bridge 메서드라고 합니다.

매개변수화된 클래스를 상속하거나 매개변수화된 인터페이스를 구현하는 클래스 또는 인터페이스를 컴파일할 때, 컴파일러는 타입 소거 과정의 한 부분으로 브리지 메서드라는 합성 메서드를 생성할 수 있습니다.

클래스 StringNode의 실제 클래스 구조는 다음과 같이 Bridge 메서드를 포함하는 형태로 나타날 것입니다.

// 컴파일러에 의한 타입 소거 후의 클래스 StringNode
class StringNode extends Node{

   StringNode(String value) {
       super(value);
   }

   // 다형성을 유지하도록 하는 Bridge 메서드
   @Override
   public void setValue(Object value){
       super.setValue((String)value);
   }

   // node 값을 설정하는 setter 메서드
   public void setValue(String value) {
       super.setValue(value);
   } 
}

이제 Object 매개변수를 가진 setValue 메서드 호출은 매개변수를 문자열로 변환하려고 시도하고 그것이 실패하면 ClassCastException을 던집니다.

결론

제네릭은 타입 안정성을 제공하는 데 사용되며 자바 컴파일러는 코드를 컴파일한 후 적절한 타입으로 매개변수 타입을 제거합니다. 컴파일러가 타입 안정성과 다형성을 지원하기 위한 다른 모든 작업들을 수행하는 것입니다.

제네릭의 내부 작동 방식을 명확히 이해함으로써 예상치 못한 런타임 문제를 일으키는 실수를 방지하며 보다 효율적인 코드를 작성할 수 있습니다.

More from this blog

나의 오픈 소스 시작 이야기

원문: TkDoDo, “My Open Source Origin Story“ 가끔씩 제가 받는 질문이 하나 있는데, 바로 오픈 소스와 리액트 쿼리(React Query)를 어떻게 시작하게 되었는지입니다. 저의 기본 원칙은 어떤 질문을 세 번 받으면 더 이상 답변할 필요가 없도록 질문에 대해 글로 쓴다는 것입니다. 하지만 이 질문은 주로 직접 만났을 때 받는 질문이라 글로 작성할 생각을 한 적이 없었습니다. 최근에 오프라인 컨퍼런스에 더 많이 참...

Jul 30, 2025
나의 오픈 소스 시작 이야기

이더넷이란?

원문: baeldung, “What Is Ethernet?“ 1. 소개 이 튜토리얼에서는 이더넷(Ethernet)과 이를 통해 이루어지는 데이터 전송에 대해 알아보겠습니다. 2. 이더넷이란? 이더넷은 근거리 통신망(LAN) 또는 광역 네트워크(WAN) 내에서 장치들이 데이터를 주고받고 통신하기 쉽게 만들어 주는 널리 사용되는 기술입니다. 컴퓨터, 프린터, 서버는 물론 스마트 홈 기기까지도 이더넷으로 연결됩니다. 가정이나 사무실처럼 제한된 공간...

Jul 20, 2025
이더넷이란?

포스트 개발자 시대

원문: Josh W. Comeau, "The Post-Developer Era" 2년 전 2023년 3월, "프런트엔드 개발의 종말"이라는 제목의 블로그 글을 발행했습니다. 이는 OpenAI가 GPT-4 쇼케이스를 발표한 직후였고, 당시 업계 분위기는 머지않아 인간 소프트웨어 개발자는 필요 없어지고 앞으로는 소프트웨어 개발을 AI가 전담하게 될 것이라는 전망이 지배적이었습니다. 저는 이런 주장에 회의적이었고 그 블로그 글에서 소프트웨어 개발...

Jul 10, 2025
포스트 개발자 시대

널리 사용되는 네트워크 프로토콜

원문: Subham Datta, "Popular Network Protocols" 1. 개요 이 튜토리얼에서는 가장 널리 사용되고 인기 있는 네트워크 프로토콜들을 소개합니다. 2. 네트워크 프로토콜 소개 의사소통과 정보 교환은 현대 사회에서 가장 중요하고 강력한 역량입니다. 컴퓨터 네트워킹이란 여러 대의 컴퓨터와 장치를 케이블이나 위성을 통해 서로 연결하여, 거리와 상관없이 정보·자원·데이터베이스 등을 공유할 수 있게 하는 것을 말합니다. 네...

Jun 20, 2025
널리 사용되는 네트워크 프로토콜

커맨드 라인에 편해지는 법

원문: Julia Evans, "What helps people get comfortable on the command line?" 가끔 커맨드 라인을 써야 하는 친구들과 이야기하다 보면 많은 이들이 여전히 터미널을 두려워하고 있다는 걸 느낍니다. 그럴 때마다 어떤 조언을 할지 잘 모르겠더라고요. 저는 워낙 오래전부터 터미널을 써왔기 때문이죠. 그래서 Mastodon에 이렇게 물어봤습니다. 최근 1~3년 사이에 터미널 공포(?)를 극복한 분...

Jun 10, 2025
커맨드 라인에 편해지는 법
C

CodeSnap

84 posts

한국어로 전달하는 웹 개발 번역 매거진