🔖 INDEX
제너릭 (Generics)은 자바에서 매우 중요한 프로그래밍 개념으로, 코드의 유연성과 재사용성을 높이는 데 도움이 되는 기능입니다. 제너릭을 사용하면 클래스, 인터페이스, 메서드에 타입 매개 변수를 적용할 수 있습니다. 이를 통해 다양한 타입의 객체를 처리하면서, 타입 안전성을 유지할 수 있습니다.
제너릭의 주요 이점
제너릭의 주요 이점은 다음과 같습니다:
- 타입 안전성(Type Safety): 제너릭을 사용하면 컴파일 시점에서 타입 검사를 수행할 수 있으므로, 잘못된 타입의 객체가 사용되는 것을 방지할 수 있습니다. 이로 인해 런타임에 발생할 수 있는 ClassCastException 등의 예외를 줄일 수 있습니다.
- 코드 재사용성(Reusability): 제너릭을 사용하면 여러 타입의 객체를 처리하는 공통 코드를 작성할 수 있습니다. 이를 통해 코드 중복을 줄이고, 유지 보수를 편리하게 할 수 있습니다.
- 타입 체크 및 형 변환 감소: 제너릭을 사용하면 컴파일러가 타입 체크를 자동으로 수행하므로, 개발자가 명시적으로 형 변환을 수행할 필요가 줄어듭니다.
제너릭의 구문 정의 및 사용 예제
제너릭은 다음과 같은 구문으로 정의됩니다:
class ClassName<T> { ... }
여기서 'T'는 타입 매개 변수를 나타냅니다. 이를 사용하여 클래스 내에서 T 타입의 객체를 처리할 수 있습니다. 예를 들어, 아래와 같은 간단한 제너릭 클래스를 작성할 수 있습니다:
public class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
이제 Box 클래스는 다양한 타입의 객체를 저장할 수 있습니다. 예를 들어, Integer 객체를 저장하려면 다음과 같이 사용할 수 있습니다:
// Box 클래스를 사용하여 Integer 타입의 객체를 저장할 수 있는 새로운 Box 인스턴스를 생성합니다.
// 여기서 Integer는 Java의 기본 데이터 타입인 int를 래핑한 클래스입니다.
Box<Integer> integerBox = new Box<>();
// integerBox 인스턴스에 setContent 메서드를 호출하여 정수 값 10을 저장합니다.
// 여기서 Integer 객체로 자동 박싱(autoboxing)되어 저장됩니다.
integerBox.setContent(10);
// integerBox 인스턴스에서 getContent 메서드를 호출하여 저장된 Integer 값을 가져옵니다.
// 여기서 intValue 변수는 Integer 타입입니다.
Integer intValue = integerBox.getContent();
// 이제 intValue 변수에는 integerBox에 저장된 정수 값 10이 할당되어 있습니다.
제너릭을 사용하면, 코드가 더욱 간결하고 안전해집니다.
제너릭의 고급 기능
제너릭은 위의 기능 외에 다양한 고급 기능을 제공합니다. 예를 들어, 제너릭의 타입 매개 변수에는 와일드카드를 사용할 수 있으며, 제한된 타입 매개 변수(Bounded Type Parameter)를 통해 특정 클래스의 하위 클래스만 허용할 수도 있습니다.
이러한 기능들은 제너릭의 유연성을 더욱 높여줍니다.
1. 와일드카드(Wildcard)
와일드카드는 제너릭 타입의 범위를 지정할 수 있는 방법입니다. 이를 사용하면 특정 클래스의 하위 클래스 또는 상위 클래스에만 국한된 제너릭 타입을 허용할 수 있습니다. 와일드카드는 '?' 기호로 표시되며, 다음과 같이 사용할 수 있습니다.
// processElements라는 메서드를 정의합니다. 이 메서드는 제너릭 와일드카드를 사용하여
// 어떠한 타입의 요소도 포함할 수 있는 List를 매개변수로 받습니다.
public static void processElements(List<?> elements) {
// elements 리스트의 각 요소에 대해 반복문을 수행합니다.
// 여기서 element 변수는 Object 타입이며, elements 리스트의 요소를 차례로 할당받습니다.
for (Object element : elements) {
// 현재 반복에서 할당된 element 객체를 출력합니다.
// System.out.println 메서드는 element 객체의 toString 메서드를 호출하여
// 문자열 표현을 출력합니다.
System.out.println(element);
}
// 반복문이 종료되면, elements 리스트의 모든 요소가 출력되고 메서드가 종료됩니다.
}
위의 예제에서, processElements 메서드는 어떤 타입의 요소도 포함할 수 있는 List를 매개 변수로 받습니다.
2. 제한된 타입 매개 변수(Bounded Type Parameter)
제한된 타입 매개 변수를 사용하면 특정 클래스의 하위 클래스나 인터페이스를 구현한 클래스만을 타입 매개 변수로 허용할 수 있습니다. 이를 사용하여 제너릭 코드의 범위를 제한하고, 관련된 타입끼리만 처리하도록 할 수 있습니다. 제한된 타입 매개 변수는 'extends' 키워드를 사용하여 정의합니다.
// Container라는 제너릭 클래스를 정의합니다. 이 클래스는 Number 클래스를 상속한 타입 T를 받습니다.
// 이로 인해 Container는 Number 클래스와 그 하위 클래스 (예: Integer, Double 등)의 인스턴스를 저장할 수 있습니다.
public class Container<T extends Number> {
// 제너릭 타입 T의 객체를 저장할 private 멤버 변수 value를 선언합니다.
private T value;
// setValue 메서드를 정의합니다. 이 메서드는 제너릭 타입 T의 객체를 매개변수로 받아서
// value 멤버 변수에 저장합니다.
public void setValue(T value) {
this.value = value;
}
// getValue 메서드를 정의합니다. 이 메서드는 value 멤버 변수에 저장된
// 제너릭 타입 T의 객체를 반환합니다.
public T getValue() {
return value;
}
}
// 이제 Container 클래스를 사용하여 Number 클래스의 하위 클래스 인스턴스를 저장하고 처리할 수 있습니다.
위의 예제에서, Container 클래스는 Number 클래스의 하위 클래스만을 타입 매개 변수로 허용합니다. 이로 인해 Container는 Integer, Double 등의 숫자 타입만을 저장할 수 있게 됩니다.
제너릭은 자바 프로그래밍에서 매우 중요한 개념으로, 코드의 안전성과 재사용성을 높이는 데 큰 도움이 됩니다. 제너릭을 이해하고 적절히 활용하면, 더욱 견고하고 유연한 코드를 작성할 수 있습니다.
참고로, C++에서는 템플릿(Template)이라는 기능을 사용하여 제너릭과 유사한 프로그래밍을 할 수 있습니다.
제너릭과 함께 알아두면 좋은 내용
자바에서 제너릭과 함께 알아두면 좋은 내용들은 다음과 같습니다:
1. 타입 인자 (Type Argument)
제너릭 클래스나 인터페이스를 사용할 때 실제 타입을 지정해주는 것입니다.
List<String> stringList = new ArrayList<>();
여기에서 String이 타입 인자입니다.
2. 타입 파라미터 (Type Parameter)
제너릭 클래스나 인터페이스를 정의할 때 사용하는 것으로, 실제 타입을 받기 위한 플레이스홀더입니다.
public class Container<T> {
private T value;
//...
}
여기에서 T가 타입 파라미터입니다.
3. 제한된 타입 파라미터 (Bounded Type Parameter)
타입 파라미터에 대해 특정 클래스 또는 인터페이스를 상속/구현한 타입만 허용하고 싶을 때 사용합니다.
public class NumericContainer<T extends Number> {
private T value;
//...
}
여기에서 T는 Number 클래스를 상속한 타입만 허용합니다.
4. 와일드카드 (Wildcard)
제너릭 타입의 인스턴스를 처리할 때 타입에 대한 제약을 완화할 수 있는 기능입니다. 와일드카드를 사용하면 특정 타입이 아닌 모든 타입을 처리할 수 있습니다.
public static void printList(List<?> list) {
for (Object item : list) {
System.out.println(item);
}
}
여기에서 List<?>는 모든 타입의 리스트를 처리할 수 있습니다.
5. 타입 소거 (Type Erasure)
자바에서 제너릭 코드는 컴파일 시점에 타입 정보가 소거되고, 런타임에는 제너릭 정보가 없습니다. 이로 인해 제너릭 코드는 런타임에 타입 변환을 수행할 수 없습니다.
6. 제너릭 메서드 (Generic Method)
메서드 내에서도 타입 파라미터를 사용하여 유연하게 처리할 수 있는 메서드입니다. 제너릭 메서드는 클래스의 제너릭 타입과 독립적으로 작동할 수 있습니다.
public static <T> void printArray(T[] array) {
for (T item : array) {
System.out.print(item + " ");
}
}
여기에서 <T>는 제너릭 메서드를 정의할 때 사용하는 타입 파라미터입니다.
7. 제너릭 상속 (Generic Inheritance)
제너릭 클래스 또는 인터페이스가 다른 제너릭 클래스 또는 인터페이스를 상속/구현할 때, 타입 파라미터를 전달하여 상속 관계를 구성할 수 있습니다.
public interface MyList<T> extends List<T> {
//...
}
여기에서 MyList<T>가 List<T>를 상속합니다.
8. 불변성 (Invariance)
자바에서 제너릭은 불변성을 따릅니다. 불변성은 특정 클래스의 인스턴스와 해당 클래스의 제너릭 인스턴스 간의 할당을 제한합니다. 즉, 서로 다른 타입 인자를 가진 제너릭 타입은 서로 호환되지 않습니다.
import java.util.List;
import java.util.ArrayList;
class Animal {}
class Mammal extends Animal {}
public class InvarianceExample {
public static void main(String[] args) {
List<Animal> animalList = new ArrayList<>();
List<Mammal> mammalList = new ArrayList<>();
// 불변성 때문에 컴파일 오류
// animalList = mammalList;
// List<Animal>과 List<Mammal>은 호환되지 않음
// 정상적인 리스트 생성 및 할당
animalList.add(new Animal());
animalList.add(new Mammal());
mammalList.add(new Mammal());
}
}
위 예제에서 List과 List은 서로 호환되지 않으며, 불변성 때문에 컴파일 오류가 발생합니다. 이는 List이 Animal 타입의 객체뿐만 아니라 Mammal 타입의 객체도 저장할 수 있기 때문입니다. 따라서, List을 List에 할당하는 것은 안전하지 않습니다. 불변성이 적용되는 경우에도 공변성과 반공변성을 사용하여 제너릭 코드를 유연하게 만들 수 있습니다. 이는 위에서 설명한 공변성과 반공변성의 예제 코드에서 볼 수 있습니다. 이를 통해 개발자들은 불변성, 공변성, 그리고 반공변성을 적절하게 활용하여 제너릭 코드를 안전하고 유연하게 작성할 수 있습니다.
9. 공변성과 반공변성 (Covariance and Contravariance)
이 속성들은 제너릭 타입들 간의 상속 관계를 지정할 때 사용되는 개념입니다. 이 두 가지 개념을 이해하려면 먼저 자바에서의 상속 관계를 이해해야 합니다. 자바에서는 클래스와 인터페이스간에 상속 관계가 존재할 수 있습니다. 예를 들어, A가 B를 상속한다면 B는 A의 하위 타입이 됩니다.
2023.05.02 - [JAVA] - 초보 자바 프로그래밍(36) - 공변성과 반공변성 (Covariance and Contravariance)
10. 제너릭 배열 생성 제한
자바에서는 제너릭 배열을 직접 생성할 수 없습니다. 이는 타입 소거로 인해 런타임에 타입 변환을 수행할 수 없기 때문입니다. 하지만 제너릭 배열을 간접적으로 사용할 수 있는 방법들이 있습니다.
이러한 내용들은 자바의 제너릭을 사용하면서 다양한 문제에 대응하고, 코드의 유연성과 재사용성을 높이기 위한 기본적인 개념들입니다. 제너릭에 대해 이해하고 활용하면 자바 프로그래밍에서 좀 더 안전하고 효율적인 코드를 작성할 수 있습니다.
'프로그래밍 > JAVA' 카테고리의 다른 글
초보 자바 프로그래밍(37) - 문자열과 String 클래스 (0) | 2023.05.02 |
---|---|
초보 자바 프로그래밍(36) - 공변성과 반공변성 (Covariance and Contravariance) (0) | 2023.05.02 |
초보 자바 프로그래밍(34) - 배열과 ArrayList 비교 (0) | 2023.05.02 |
초보 자바 프로그래밍(33) - 가변 인자를 사용한 메서드 작성 (0) | 2023.05.02 |
초보 자바 프로그래밍(32) - 배열을 반환하는 메서드 작성 (0) | 2023.05.02 |
댓글