소개
Java 의 제네릭 (Generics) 은 추가적인 추상화 계층을 제공하여 유연하고 재사용 가능한 코드를 작성할 수 있도록 해줍니다. 제네릭을 사용하면 여러 유형의 객체에 대해 단일 알고리즘을 생성할 수 있습니다. 제네릭은 타입 안전성을 제공하고 런타임 오류를 방지합니다. 이 랩에서는 Java 의 제네릭의 다양한 측면을 이해할 것입니다.
제네릭 클래스 생성
Java 에서 다이아몬드 연산자 (<>) 를 사용하여 제네릭 클래스를 생성할 수 있습니다. 다음 코드 블록에서는 모든 유형의 객체를 수용할 수 있는 MyGenericClass라는 제네릭 클래스를 생성합니다. 또한 정수 및 문자열 유형을 매개변수로 사용하여 MyGenericClass 유형의 객체를 생성합니다.
// ~/project/MyGenericClass.java
class MyGenericClass<T> {
T field1;
public MyGenericClass(T field1) {
this.field1 = field1;
}
public T getField1() {
return field1;
}
}
public class Main {
public static void main(String[] args) {
MyGenericClass<Integer> myIntObj = new MyGenericClass<Integer>(100);
MyGenericClass<String> myStringObj = new MyGenericClass<String>("Hello World");
System.out.println(myIntObj.getField1());
System.out.println(myStringObj.getField1());
}
}
코드를 실행하려면 터미널을 열고 ~/project 디렉토리로 이동합니다. 다음 명령으로 코드를 컴파일합니다.
javac MyGenericClass.java
컴파일이 성공적으로 완료되면 다음 명령으로 코드를 실행합니다.
java Main
코드의 출력은 다음과 같습니다.
100
Hello World
제네릭 메서드 생성
Java 에서 제네릭 메서드도 생성할 수 있습니다. 제네릭 메서드는 다양한 유형의 객체와 함께 작동할 수 있습니다. 다음 코드 블록에서는 모든 유형의 배열을 출력할 수 있는 printArray라는 제네릭 메서드를 생성합니다.
// ~/project/Main.java
public class Main {
public static <T> void printArray(T[] array) {
for (T element : array)
System.out.println(element);
}
public static void main(String[] args) {
Integer[] intArray = {1, 2, 3, 4, 5};
Double[] doubleArray = {1.1, 2.2, 3.3, 4.4};
String[] stringArray = {"Hello", "World"};
printArray(intArray);
printArray(doubleArray);
printArray(stringArray);
}
}
코드를 실행하려면 터미널을 열고 ~/project 디렉토리로 이동합니다. 다음 명령으로 코드를 컴파일합니다.
javac Main.java
컴파일이 성공적으로 완료되면 다음 명령으로 코드를 실행합니다.
java Main
코드의 출력은 다음과 같습니다.
1
2
3
4
5
1.1
2.2
3.3
4.4
Hello
World
제한된 제네릭 메서드 생성
경계가 있는 제네릭 메서드도 생성할 수 있습니다. 경계가 있는 제네릭 (Bounded Generics) 은 메서드가 허용하는 유형의 범위를 제한합니다. extends 키워드를 사용하여 경계를 설정합니다. 다음 코드 블록에서는 Number 유형과 해당 하위 클래스 유형의 객체 숫자를 출력할 수 있는 printNumbers라는 경계가 있는 제네릭 메서드를 생성합니다.
// ~/project/Main.java
public class Main {
public static <T extends Number> void printNumbers(T[] numbers) {
for (T number : numbers)
System.out.println(number);
}
public static void main(String[] args) {
Integer[] intNumbers = {1, 2, 3, 4, 5};
Double[] doubleNumbers = {1.1, 2.2, 3.3, 4.4};
printNumbers(intNumbers);
printNumbers(doubleNumbers);
}
}
코드를 실행하려면 터미널을 열고 ~/project 디렉토리로 이동합니다. 다음 명령으로 코드를 컴파일합니다.
javac Main.java
컴파일이 성공적으로 완료되면 다음 명령으로 코드를 실행합니다.
java Main
코드의 출력은 다음과 같습니다.
1
2
3
4
5
1.1
2.2
3.3
4.4
제네릭 타입 안전성 이해
제네릭은 타입 안전성 (type safety) 을 제공하여 한 번에 하나의 유형의 객체만 사용하도록 보장합니다. 다음 코드 블록에서는 타입 매개변수를 정의하지 않고 정수의 ArrayList를 생성합니다. 타입 매개변수 없이 제네릭을 사용하는 것은 좋지 않은 방법입니다. 코드는 컴파일되지만, 다른 유형의 요소를 접근하려고 할 때 런타임 오류를 반환합니다.
// ~/project/Main.java
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList integerList = new ArrayList();
integerList.add(1);
integerList.add(2);
integerList.add(3);
integerList.add("Hello World");
for (Object element : integerList) {
System.out.println((Integer)element);
}
}
}
코드를 실행하려면 터미널을 열고 ~/project 디렉토리로 이동합니다. 다음 명령으로 코드를 컴파일합니다.
javac Main.java
컴파일이 성공적으로 완료되면 다음 명령으로 코드를 실행합니다.
java Main
코드의 출력은 다음과 같습니다.
1
2
3
Exception in thread "main" java.lang.ClassCastException: java.base/java.lang.String cannot be cast to java.base/java.lang.Integer
at Main.main(Main.java:13)
integerList에 Integer 유형과 String 유형의 요소가 포함되어 있기 때문에 런타임 오류가 발생합니다. 이는 integerList를 생성할 때 타입 매개변수를 정의하지 않았기 때문입니다.
제네릭 타입 소거 이해
Java 는 런타임에 추가적인 오버헤드가 필요하지 않도록 제네릭에서 타입 소거를 사용합니다. 컴파일러는 타입 소거 과정에서 타입 매개변수를 Object 클래스 또는 상위 클래스 (경계가 있는 제네릭의 경우) 로 대체합니다. 다음 코드 블록에서는 제네릭이 컴파일 시간과 런타임에 기본 유형 (primitive types) 과 어떻게 작동하는지 살펴보겠습니다.
// ~/project/Main.java
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<Integer> integerList = new ArrayList<Integer>();
integerList.add(1);
integerList.add(2);
integerList.add(3);
int sum = 0;
for (int i = 0; i < integerList.size(); i++) {
sum += integerList.get(i);
}
System.out.println(sum);
}
}
코드를 실행하려면 터미널을 열고 ~/project 디렉토리로 이동합니다. 다음 명령으로 코드를 컴파일합니다.
javac Main.java
컴파일이 성공적으로 완료되면 다음 명령으로 코드를 실행합니다.
java Main
코드의 출력은 다음과 같습니다.
6
위의 코드 블록에서 integerList의 요소를 더하려고 했습니다. 아시다시피, int와 같은 기본 유형은 제네릭과 함께 사용할 수 없습니다. 타입 소거로 인해 타입 매개변수 Integer는 컴파일 시간에 Object 클래스로 대체됩니다. 그러나 오토박싱 (autoboxing) 과 언박싱 (unboxing) 을 통해 컴파일 시간에 정수를 더할 수 있었습니다. 런타임에는 코드가 추가적인 오버헤드 없이 일반적인 코드 실행처럼 수행되었습니다.
요약
이 랩에서는 Java 제네릭에 대해 배웠습니다. 제네릭 클래스 (Generic classes), 제네릭 메서드 (Generic methods), 경계가 있는 제네릭 메서드 (Bounded Generic methods), 제네릭의 타입 안전성 (Type Safety in Generics), 그리고 제네릭의 타입 소거 (Type Erasure in Generics) 와 관련된 기본적인 개념을 다루었습니다. 또한 제네릭을 사용하여 유연하고 재사용 가능한 코드를 작성하는 방법을 살펴보았습니다. 이 랩이 Java 에서 제네릭을 이해하는 데 도움이 되었기를 바랍니다.



