inblog logo
|
LifeLog, DevLog
    Java

    Generic - 2

    KYJTHEYJ's avatar
    KYJTHEYJ
    Dec 08, 2025
    Generic - 2

    Wild Card 의 용도

    • 제네릭의 유연성을 높이는 도구

      ‘알 수 없는 타입’ 자체를 나타냄 → ? 키워드 사용

    • 1편에서도 서술한 제네릭의 ‘불공변’ 때문에 엄격한 상황을 해결하기 위해 등장

    // 이러한 상황이 잦을텐데 제네릭으로는 불공변 덕에 
    // 처리 하기 힘든 상황이 잦음
    
    // 컬렉션에 다양한 타입을 저장 하려함
    
    // 잘못된 사용
    // Object는 최상위 타입이니까 다 되겠지 뭐!
    public List<Object> returnToList(List<Object> list) {
    	List<Object> processingToList = list;
    
    	// 다양한 타입을 받아서 처리과정 진행
    	...
    
    	return processingToList;
    }
    // 제네릭으로는 불공변 덕에 정말 List<Object>만 사용가능함!
    // List<String>, 기타등등.. 못 받는 상황임
    
    // 올바른 와일드 카드 사용
    public List<?> returntoList2(List<?> list) {
    	List<?> processingToList = list;
    	// 불공변을 해결하기 위해 나온 와일드 카드지만 받는 것의 불공변을 해결한거지
    	// 쓰기에 대해서는 해결하지 못함
    	// processingToList.add(10);
    
    	for(Object obj : processingToList) {
    		// 다양한 타입을 받아서 처리하기!
    		...
    		
    	}
    
    	return processingToList;
    }

    제네릭, 와일드 카드의 사용 패턴들, 타입 파라미터 명명 관례

    💡

    와일드카드는 상한,하한선 지정이 전부 가능

    제네릭은 상한선만 지정 가능

    → 와일드카드 자체가 “모르는 타입”을 받아오는 것이라 그럼

    → 와일드 카드의 하한선 지정도 메서드에서만 가능
    (생각을 다시 한번 해보면 클래스 하한선이 의미가 없음)
    (클래스는 지정한 타입에 대한 객체를 생성하기 위해 만드는 것)
    (클래스에서 뭘 하한해서 뭘 가져올건데는 그 누구도 모름)
    (? super Integer) → ??????? 뭐가 Integer의 밑인데..?

     → 이 생각을 좀 더 정리하면 사실 하한선의 용도는 뭐가 올지 모르지만
    최소한 지정한 타입은 받을 수 있다의 용도로 쓰는게 맞음

    메서드는 소비만 하는 것이니 이게 가능 한 것임

    • 제네릭 클래스

      // T.P -> 타입 파라미터, W.C -> 와일드 카드
      // 접근자 class 클래스명<T.P>
      public class Box<T>
      
      // 제한을 둘 때
      // 접근자 class 클래스명<T.P extends 상한선 & ... & ...>
    • 제네릭 변수

      // 접근자 타입 필드명;
      public T ingridient;
    • 제네릭 메서드

      
      // 접근자 static여부 <타입파라미터> 반환타입 메서드명(타입파라미터 매개변수)
      public static <T> T cooking(T[] ingridient)
      
      // 타입파라미터에 상한선을 둘 경우
      // 접근자 static여부 <타입파라미터 extends 상한선> 반환타입 
      // 메서드명(타입파라미터 매개변수)
    • 와일드카드

      // 접근자 static여부 반환타입 메서드명(?파라미터 매개변수)
      public static void print(List<?> list)
      public static double sum(List<? extends Number> numbers)
      
      // 하한선 지정
      public static integer sumInteger(List<? super Integer> numbers)
      public static <T> T addSuperT(List<? super T> list, T... items)
    • 타입 파라미터 명명 관례

      • <T> → 타입

      • <E> → 요소

      • <K, V> → 키, 밸류

      • <R> → 리턴 타입

      • <S> → 소스 타입

    사용시 체크리스트

    • 대체 언제 제네릭을 쓸건지?

      • 꼭 필요한가? → 정수형이 필요하면 굳이 제네릭 사용 말고 타입 고정시켜 사용

      • 어디에 쓸까? → 클래스에 선언해서 전역으로? 메서드로 선언해서 기능만?

      • 타입 제한이 필요할까? → extends, super

        • 너무 광범위하면 와일드카드 고려하기

      • 읽을 때인가 쓸 때인가? → 읽기시 extends, 쓸 땐 super 로 타입 제한 (PECS)

        • 둘 다 해당되면 정말 타입 파라미터를 제네릭으로 받을 수 있는지 생각

          • 둘 다 해당되면 제네릭 타입에 하한과 상한을 두지 않고 을 그냥 T 로 사용

        • 타입이 무관하면 와일드카드

    PECS의 개념

    • Producer → <? extends T>

      • extends 라는 의미

        public void method1<List<? extends T> list {
         // 이 메서드의 list는 Producer
         // 나는 이 메서드를 소비함 (list에서 데이터를 받아 사용함)
        }
        • 컬렉션이 나에게 데이터를 주는 것

        • list에서 데이터를 꺼내서 사용함

        • 코드적 측면으로 T 를 상한선으로 둔 아래 타입을 전부 사용 가능함
          그러니 주로 읽는 타입에 사용하는 게 맞는 것

    • Consumer → <? super T>

      • super 라는 의미

        public void method2<List<? super T> list {
         // 이 메서드의 list는 Consumer
         // 나는 이 메서드를 생산함 (list에 데이터를 넣어주고 있음)
        }
        • 컬렉션이 나로부터 데이터를 제공 받음

        • list에 데이터를 넣어줘야 함

        • 코드적 측면은 T 를 하한선으로 둔 아래 타입을 사용함

          그러니 쓰는 것에 사용하는 것에 맞는 것
          읽으려하면 다양한 타입에 대해 타입이 일치 못할 가능성이 큼

    타입 소거

    • 우리가 제네릭을 사용하여 컬렉션을 지정할 때

      List<String> strList = new ArrayList<String>;
      List<Integer> integerList = new ArrayList<Integer>;

      사실 상 컴파일러만 타입에 대해 아는 것, 런타임 시점에서는 아래와 같이 실행됨

      List strList = new ArrayList();
      List integerList = new ArrayList();
      • 이걸 왜 알아야 할까?

        • 실 사용에는 사실 몰라도 사용 가능한데..
          왜 제네릭 객체는 없을까? 왜 와일드카드 객체는 없을까?
          왜 제네릭 형 변환 체크 (instanceof)가 안되는지의 이유!

    Share article

    LifeLog, DevLog - https://github.com/KYJTHEYJ

    RSS·Powered by Inblog