자바 제네릭(Generic) 정리

dahn·2024년 7월 21일
post-thumbnail

자바 제네릭(Generic)을 사용하는 이유

코드의 재사용성과 타입 안전성을 높이기 위해

1. 타입 안전성(Type Safety)

  • 제네릭을 사용하면 컴파일 시점에 타입 검사를 할 수 있어, 런타임 시 발생할 수 있는 ClassCastException을 방지할 수 있음. 예를 들어, 제네릭을 사용하지 않으면 객체를 컬렉션에서 꺼내올 때 일일이 캐스팅을 해야 하지만, 제네릭을 사용하면 컴파일러가 타입을 체크해주기 때문에 불필요한 캐스팅을 피할 수 있음

2. 코드 재사용성(Code Reusability)

  • 제네릭을 사용하면 데이터 타입에 의존하지 않고도 동일한 코드를 다양한 타입으로 사용할 수 있음. 예를 들어, List<T>와 같은 제네릭 클래스를 정의하면 List<String>, List<Integer> 등 여러 타입의 리스트를 하나의 클래스 정의로 사용할 수 있음

3. 가독성 및 유지보수성 향상

  • 제네릭을 사용하면 코드가 더 명확해지고 가독성이 좋아짐. 또한 타입관련 버그를 컴파일 시점에 발견할 수 있어 유지보수 비용이 줄어듬

👉 제네릭을 사용하지 않은 경우

  List list = new ArrayList();
  list.add("Hello");
  String str = (String) list.get(0);

👉 제네릭을 사용한 경우

  List<String> list = new ArrayList<>();
  list.add("Hello");
  String str = list.get(0); // 타입 캐스팅 불필요

자바 표준 라이브러리에서 널리 사용되는 제네릭의 예

1. java.util.Map 인터페이스

  Map<String, Integer> map = new HashMap<>();
  map.put("one", 1);
  map.put("two", 2);
  Integer value = map.get("one"); // 타입 캐스팅 불필요

2. java.util.Optional 클래스

Optional<String> optional = Optional.of("Hello");
if(optional.isPresent()) {
	String value = optional.get();
}

와일드카드를 이용한 제네릭 메서드

메서드의 인자 타입을 더 유연하게 받아들이기 위해 사용

와일드카드의 종류와 용도

  1. 무제한 와일드카드(<?>) : 임의의 타입을 허용하지만, 구체적인 타입에 대한 정보는 제공하지 않음. 주로 메서드의 매개변수가 어떤 타입이어도 상관없을 때 사용함

  2. 상한 경계 와일드카드(<? extends T>) : T타입 또는 그 하위 타입을 허용함. 주로 메서드에서 읽기 작업만 할 때 사용

  3. 하한 경계 와일드카드(<? super T>) : T타입 또는 그 상위 타입을 허용함. 주로 메서드에서 쓰기 작업만 할 때 사용

import java.util.Arrays;
import java.util.List;

public Class WildcardExample {
	public static void main(String[] args){
		List<Integer> intList = Arrays.asList(1,2,3,4,5);
		List<Double> doubleList = Arrays.asList(1.1,2.2,3.3,4.4,5.5);
		List<Number> numberList = new ArrayList<>();

		// 무제한 와일드카드 사용
		System.out.println("List of Integers:");
		printList(intList); 
		System.out.println("\nList of Doubles:");
		printList(doubleList);

		// 상한경계 와일드카드 사용
		System.out.println("\nProcessed Numbers:");
		processElements(intList); 
		processElements(doubleList);

		// 하한경계 와일드카드 사용
		System.out.println("\nAdding numbers to list:");
		addNumbers(numberList);

		printList(numberList); 
	}

	public static void printList(List<?> list){
    	for(Object elem : list){
        	System.out.print(elem + ", ");
        }
        System.out.println();
    }
    
    public static void processElements(List<? extends Number> list){
		for(Number num : list){
			System.out.println(num.doubleValue());
		}
	}

	public static void addNumbers(List<? super Integer> list){
		list.add(1);
		list.add(2);
	}
}

위 코드에서 볼 수 있듯이, printList(List<?> list)의 경우에는 무제한 와일드 카드로 인자를 받기 때문에 어떤 값이 들어가도 최상위 객체인 Object로 변환하여 보여줄 수 있다.

processElements(List<? extends Number> list) 함수의 경우 타입을 Number 클래스 또는 그 자식클래스로 제한하고 있다(Number클래스가 들어갈 수 있는 상한이기 때문에 상한경계 와일드카드). 이유는 해당 함수 내에서 Number클래스 또는 해당 클래스를 상속하고 있는 클래스만 사용가능한 doubleValue()(값을 2배로 증가) 기능을 사용하기 때문이다.

addNumbers(List<? super Integer> list) 함수의 경우 내부를 살펴보면 인자로 받은 List<Number> 리스트에 숫자를 add하고 있는 것을 볼 수 있다. 적어도 Integer 클래스보다는 같거나 "높은"(ex. Number, Object) 타입이 들어와야 한다. 이처럼 가장 하한선인 Integer를 기준으로 super라는 키워드를 통해 하한경계 와일드카드를 사용하였다.

이렇게 특정 타입만 한정짓는 것이 아닌 다양한 타입의 데이터를 처리하는 메서드를 작성할 수 있으며, 각 메서드는 와일드카드를 사용하여 유연하고 타입 안전한 방식으로 데이터를 처리할 수 있다.

자바 리플렉션과 사용된 와일드카드 제네릭 메서드의 예

아래는 리플렉션을 사용하여 클래스의 메서드를 호출하는 간단한 예제이다.

import java.lang.reflect.Method;
import java.util.List;

public class ReflectionWildcardExample {
	public static void main(String[] args) throws Exception {
		// 원본 객체 생성
		List<String> stringList = List.of("Hello", "World");
		List<Integer> intList = List.of(1,2,3);

		// 리스트 출력 메서드 호출
		printListUsingReflection(stringList);
		printListUsingReflection(intList);

		// 와일드카드 제네릭을 사용하여 리스트를 출력하는 리플렉션 메서드
		public static void printListUsingReflection(List<?> list) throws Exception {
        	// 리스트 클래스의 get메서드 가져오기
            // List의 get메서드는 인덱스를 받기 때문에 int타입의 파라미터가 필요
            Method getMethod = List.class.getMethod("get", int.class); 
            
            // 리스트의 모든 요소 출력
            for (int i=0; i<list.size(); i++){
            	// 호출하려는 메서드를 소유한 객체인 list를 첫번째 인자로 줌
            	Object element = getMethod.invoke(list, i);
                System.out.println(element);
            }
        }
	}
}

printListUsingReflection 메서드는 와일드카드 제네릭을 사용하여 다양한 타입의 리스트를 받아 처리할 수 있다. 리플렉션을 사용하여 리스트의 get메서드를 호출하고, 각 요소를 출력하는 함수이다.

이처럼 리플렉션과 와일드카드 제네릭을 함께 사용하여 다양한 타입의 리스트를 유연하고 안전하게 처리할 수 있음을 알 수 있다.

profile

0개의 댓글