Computer >> 컴퓨터 >  >> 프로그래밍 >> C++

C++ 템플릿 vs Java 제네릭: 개념부터 차이점까지 한눈에 보기

템플릿(Template)은 특정 데이터 타입에 의존하지 않고 코드를 작성하는 일반화 프로그래밍(Generic Programming)의 핵심 기반입니다.

템플릿은 범용 클래스나 함수를 만들기 위한 하나의 설계도이자 공식입니다. C++ STL의 반복자(iterator), 알고리즘(algorithm) 등 주요 라이브러리 컨테이너들이 모두 이 템플릿 개념을 바탕으로 개발되었습니다.

예를 들어 vector 컨테이너는 단 하나의 정의만 존재하지만, vector<int>, vector<string>처럼 서로 다른 타입의 vector를 필요에 따라 자유롭게 선언해 사용할 수 있습니다.

C++ 템플릿 예제 코드

#include <iostream>
#include <string>
using namespace std;
template <typename T>
inline T const& Max (T const& a, T const& b) {
    return a < b ? b : a;
}
int main () {
    int i = 39;
    int j = 20;
    cout << "Max(i, j): " << Max(i, j) << endl;
    double f1 = 13.5;
    double f2 = 20.7;
    cout << "Max(f1, f2): " << Max(f1, f2) << endl;
    string s1 = "Hello";
    string s2 = "World";
    cout << "Max(s1, s2): " << Max(s1, s2) << endl;
    return 0;
}

실행 결과

Max(i, j): 39
Max(f1, f2): 20.7
Max(s1, s2): World

Java의 제네릭(Generics)

반면 Java는 제네릭(Generics)이라는 문법을 통해 유사한 목표를 달성합니다. Java의 제네릭 메서드와 제네릭 클래스를 사용하면 프로그래머가 하나의 메서드 선언으로 관련된 여러 메서드 집합을, 하나의 클래스 선언으로 관련된 여러 타입 집합을 각각 정의할 수 있습니다.

또한 제네릭은 컴파일 타임 타입 안전성(compile-time type safety)을 제공하여, 잘못된 타입 사용을 컴파일 단계에서 미리 잡아낼 수 있게 해줍니다.

예를 들어 객체 배열을 정렬하는 제네릭 메서드를 하나 작성해두면, Integer 배열, Double 배열, String 배열 등 어떤 타입의 배열이든 동일한 메서드를 호출해 요소를 정렬할 수 있습니다.

제네릭 메서드(Generic Method) 작성 규칙

하나의 제네릭 메서드 선언만으로 서로 다른 타입의 인자를 전달하며 호출할 수 있으며, 전달된 인자의 타입에 따라 컴파일러가 각 호출을 적절하게 처리합니다. 제네릭 메서드를 정의할 때 지켜야 할 규칙은 다음과 같습니다.

  • 모든 제네릭 메서드 선언에는 메서드 반환 타입 앞에 꺾쇠괄호(< 와 >)로 감싼 타입 매개변수 섹션이 위치합니다 (다음 예제의 <E>).
  • 타입 매개변수 섹션에는 하나 이상의 타입 매개변수가 쉼표로 구분되어 포함됩니다. 타입 매개변수(type parameter)는 흔히 타입 변수(type variable)라고도 불리며, 제네릭 타입 이름을 지정하는 식별자입니다.
  • 타입 매개변수는 반환 타입을 선언하는 데 사용될 수 있으며, 제네릭 메서드에 실제로 전달되는 인자 타입(실 타입 인자, actual type argument)의 자리 표시자 역할을 합니다.
  • 제네릭 메서드의 본문은 일반 메서드와 동일한 방식으로 선언합니다. 단, 타입 매개변수는 참조 타입(reference type)만 표현할 수 있으며 int, double, char 같은 원시 타입(primitive type)은 사용할 수 없습니다.

Java 제네릭 예제 코드

public class GenericMethodTest {
   // generic method printArray
   public static < E > void printArray( E[] inputArray ) {
      // Display array elements
      for(E element : inputArray) {
         System.out.printf("%s ", element);
      }
      System.out.println();
   }
   public static void main(String args[]) {
      // Create arrays of Integer, Double and Character
      Integer[] intArray = { 1, 2, 3, 4, 5 };
      Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
      Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };
      System.out.println("Array integerArray contains:");
      printArray(intArray); // pass an Integer array
      System.out.println("\nArray doubleArray contains:");
      printArray(doubleArray); // pass a Double array
      System.out.println("\nArray characterArray contains:");
      printArray(charArray); // pass a Character array
   }
}

실행 결과

Array integerArray contains:
1 2 3 4 5
Array doubleArray contains:
1.1 2.2 3.3 4.4
Array characterArray contains:
H E L L O

C++ 템플릿과 Java 제네릭의 차이점

두 문법은 비슷한 목적을 가지지만 내부 동작 방식에는 중요한 차이가 있습니다.

  • 타입 소거(Type Erasure): Java 제네릭은 타입 소거 방식을 사용합니다. 컴파일러가 컴파일 시점에 타입 정보를 제거함으로써 더 엄격한 타입 검사를 수행하고, 불필요한 타입 캐스팅을 없애 컴파일 타임 안전성을 보장합니다. 이 과정은 Java 컴파일러의 프런트엔드에서 직접 처리됩니다.
  • C++의 코드 생성 방식: C++에서 템플릿을 사용하면 컴파일러가 제네릭 매개변수를 실제 지정된 타입으로 치환한 뒤 해당 템플릿 코드를 새로 생성합니다.
  • 타입 캐스팅 여부: Java에서는 함수 호출 시 객체 타입을 명시하더라도 C++처럼 실제 데이터 타입으로 캐스팅할 필요가 없습니다. 대신 원시 타입 대신 래퍼 클래스(wrapper class)를 사용해야 합니다.
  • 검사 시점과 코드 생성: Java 제네릭은 초기화 시점에 타입 검사를 수행하고, 제네릭이 적용되지 않은 코드와 동등한 바이트코드를 생성합니다. 반면 C++은 암묵적 타이핑(latent typing)과 템플릿 메타프로그래밍(template meta-programming)을 지원하며, 인스턴스화될 때마다 새로운 클래스를 생성합니다.