제네릭(Generics)은 Java 프로그래밍 언어의 중요한 기능 중 하나로, 코드의 재사용성과 유연성을 향상시키기 위해 도입되었습니다.
제네릭을 사용하면 클래스나 메서드를 작성할 때, 컴파일 시점에 타입을 지정하지 않고 추상적인 형태로 유지할 수 있습니다. 즉, 제네릭을 사용하여 결정되지 않은 타입을 파라미터로 처리할 수 있습니다. 이렇게 선언된 제네릭은 실제 사용할 때 구체적인 타입으로 대체되어 컴파일됩니다.
예를 들어, 제네릭을 사용하지 않는 경우에는 Object 타입을 사용하여 모든 종류의 객체를 다룰 수 있습니다. 하지만 이 경우에는 객체의 타입 안정성이 보장되지 않으며, 다운캐스팅 등의 추가 작업이 필요합니다. 반면에 제네릭을 사용하면 컴파일 시점에서 타입 안정성이 보장되며, 형변환에 대한 번거로움을 피할 수 있습니다.
import java.util.List;
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
// 제네릭을 사용하여 문자열을 저장하는 리스트 생성
List<String> stringList = new ArrayList<>();
// 리스트에 요소 추가
stringList.add("Hello");
stringList.add("World");
// 리스트에서 요소 가져오기
String firstElement = stringList.get(0);
String secondElement = stringList.get(1);
System.out.println(firstElement); // 출력: Hello
System.out.println(secondElement); // 출력: World
}
}
이처럼 제네릭을 사용하면 코드의 가독성과 유지보수성을 향상시키며, 타입 안정성을 보장할 수 있습니다.
제네릭을 사용하여 선언된 클래스나 인터페이스의 타입 파라미터는 구체적인 타입으로 대체될 수 있습니다. 이것은 클래스나 인터페이스를 사용할 때, 제네릭으로 정의된 타입 파라미터에 실제 타입을 지정하여 사용할 수 있다는 의미입니다.
// 제네릭 클래스
class Box<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
// 제네릭 인터페이스
interface Pair<K, V> {
K getKey();
V getValue();
}
위의 코드에서 Box 클래스와 Pair 인터페이스는 각각 하나 이상의 타입 파라미터를 가지고 있습니다. 이제 이 클래스와 인터페이스를 사용할 때 실제 타입을 지정하여 대체할 수 있습니다.
public class Main {
public static void main(String[] args) {
// Box 클래스 사용
Box<Integer> integerBox = new Box<>();
integerBox.setValue(10);
System.out.println("Integer Value: " + integerBox.getValue());
// Pair 인터페이스 사용
Pair<String, Integer> pair = new Pair<String, Integer>() {
@Override
public String getKey() {
return "Key";
}
@Override
public Integer getValue() {
return 100;
}
};
System.out.println("Key: " + pair.getKey());
System.out.println("Value: " + pair.getValue());
}
}
제네릭 메서드(Generic Method)는 메서드에 타입 파라미터를 사용하여 여러 타입의 인수를 받을 수 있도록 하는 Java 프로그래밍 언어의 기능입니다. 제네릭 메서드를 사용하면 메서드를 호출할 때마다 다양한 타입의 인수를 전달할 수 있으며, 컴파일러는 해당 타입에 맞는 코드를 생성합니다.
제네릭 메서드를 정의할 때는 메서드의 반환 타입 앞에 타입 파라미터를 선언하고, 메서드의 매개변수나 로컬 변수에서도 이 타입 파라미터를 사용할 수 있습니다.
public class Main {
// 제네릭 메서드
public static <T> boolean isEqual(T first, T second) {
return first.equals(second);
}
public static void main(String[] args) {
Integer firstInt = 10;
Integer secondInt = 20;
String firstStr = "Hello";
String secondStr = "Hello";
// 제네릭 메서드 호출
boolean intResult = isEqual(firstInt, secondInt); // 두 정수가 같은지 확인
boolean strResult = isEqual(firstStr, secondStr); // 두 문자열이 같은지 확인
System.out.println("Integer Result: " + intResult); // 출력: false
System.out.println("String Result: " + strResult); // 출력: true
}
}
제네릭 메서드를 사용하면 코드의 재사용성을 높이고, 타입 안정성을 유지할 수 있습니다. 이는 컴파일 시점에서 타입 체크를 수행하여 잘못된 타입의 인수를 전달하는 오류를 방지할 수 있게 해줍니다.
제한된 타입 파라미터(Bounded Type Parameter)는 특정 타입 또는 그 하위 타입만을 받아들일 수 있는 제네릭 타입 파라미터입니다. 이를 사용하면 제네릭 클래스나 메서드가 특정한 타입이나 그 하위 타입만을 다룰 수 있도록 제한할 수 있습니다.
제한된 타입 파라미터를 사용하는 가장 일반적인 방법은 특정 클래스를 확장한 클래스만을 받아들이도록 지정하는 것입니다.
public class Main {
// 제한된 타입 파라미터를 사용한 제네릭 메서드
public static <T extends Comparable<T>> int compare(T first, T second) {
return first.compareTo(second);
}
public static void main(String[] args) {
int result1 = compare(10, 20); // 정수형 비교
System.out.println("Result1: " + result1); // 출력: -1
int result2 = compare("Hello", "World"); // 문자열 비교
System.out.println("Result2: " + result2); // 출력: -15
}
}
위의 예시에서 <T extends Comparable<T>>
는 Comparable 인터페이스를 구현한 클래스만을 받아들일 수 있는 제한된 타입 파라미터를 선언한 것입니다. 이제 compare 메서드는 Comparable 인터페이스를 구현한 클래스의 객체만을 비교할 수 있습니다.
이러한 제한된 타입 파라미터를 사용하면 프로그램의 안정성을 높일 수 있습니다. 예를 들어, 제한된 타입 파라미터를 사용하면 메서드나 클래스의 사용 방법을 명확히 제한할 수 있으며, 컴파일 시점에서 타입 안전성을 보장할 수 있습니다.
와일드 카드 타입 파라미터(Wildcard Type Parameter)는 ?
기호를 사용하여 표시되며, 이를 통해 범위에 있는 모든 타입으로 대체할 수 있음을 나타냅니다. 와일드 카드 타입 파라미터는 제한된 타입 파라미터와는 달리 특정한 타입이나 그 하위 타입에 대한 제한 없이 다양한 타입을 다룰 수 있도록 해줍니다.
와일드 카드는 주로 제네릭 메서드에서 다양한 타입의 컬렉션을 다룰 때 사용됩니다. 세 가지 종류의 와일드 카드가 있습니다.
<?>
: 비한정적 와일드 카드(Unbounded Wildcard)<? extends T>
: 상한 와일드 카드(Upper Bounded Wildcard)<? super T>
: 하한 와일드 카드(Lower Bounded Wildcard)비한정적 와일드 카드(<?>
)는 모든 타입을 대체할 수 있으며, 상한 와일드 카드(<? extends T>
)는 특정 타입 T
또는 T
의 하위 타입을 대체할 수 있습니다. 반면에 하한 와일드 카드(<? super T>
)는 특정 타입 T
또는 T
의 상위 타입을 대체할 수 있습니다.
import java.util.List;
public class Main {
public static void printList(List<?> list) {
for (Object obj : list) {
System.out.print(obj + " ");
}
System.out.println();
}
public static void main(String[] args) {
List<Integer> intList = List.of(1, 2, 3, 4, 5);
List<String> strList = List.of("Hello", "World");
// 제네릭 메서드 호출
printList(intList); // 출력: 1 2 3 4 5
printList(strList); // 출력: Hello World
}
}
printList
메서드는 비한정적 와일드 카드(<?>
)를 사용하여 모든 타입의 컬렉션을 출력합니다. 이를 통해 다양한 타입의 컬렉션을 다룰 수 있으며, 코드의 재사용성과 유연성을 향상시킬 수 있습니다.