Posts [10분 테코톡] 시드의 제네릭
Post
Cancel

[10분 테코톡] 시드의 제네릭

제네릭의 정의

  • 클래스나 메소드에서 사용할 내부 데이터 타입을 외부에서 지정하는 기법
1
2
3
4
5
6
7
8
9
10
class FruitBox<T> {
    List<T> fruits = new ArrayList<>();

    public void add(T fruit) {
        fruits.add(fruit);
    }
}

// main
FruitBox<Apple> appleBox = new FruitBox<>();

제네릭 클래스

  • 클래스 선언에 타입 매개변수가 쓰이면, 이를 제네릭 클래스라 한다.
1
2
3
class FruitBox<T> {

}

제네릭을 사용하는 이유

  • 제네릭을 사용하는 이유는 두 가지가 있다.
    • 1) 컴파일 타임에 자료형의 오류에 대한 검증을 수행하여 런타임시 자료형에 안전한 코드를 실행한다.
    • 2) 반환값에 대한 타입 변환 및 타입 검사에 들어가는 노력을 줄일 수 있고, 형변환이 없어지므로 가독성이 좋아진다.
  • 1) 타입 안정성(타입에 대한 검증을 컴파일시 수행하여 타입 안정성을 가진다)
    스크린샷 2021-05-07 오후 12 22 34

  • 위와 같이 제네릭을 쓰지 않으면 문법적으론 문제가 없지만 자료형의 오류에 대한 검증이 컴파일타임에 이루어지지 않음으로써 런타임 도중 발생하게 된다.
    스크린샷 2021-05-07 오후 12 24 32
  • 자바 컴파일러는 제네릭 코드에 강한 타입 체크를 한다. 타입 안정성에 위배가 된다면 컴파일에러를 발생시킨다.

  • 2)캐스팅 삭제(컴파일하는 타입에 미리 타입이 정해지므로 타입캐스팅을 해줄 필요가 없다.) 스크린샷 2021-05-07 오후 12 26 54

  • Object클래스는 사용하는 타입으로 형변환을 해줘야하는데 제네릭을 사용하면 컴파일시 미리 타입이 정해지므로 할 필요가 없다.

제네릭 메소드

제네릭 메소드의 형태

  • <>안의 타입으로 매개변수의 데이터 타입을 지정
  • 타입 파라미터의 범위는 메소드 블록 이내로 한정
1
2
3
public <T> void add(T t) {
    // ...
}

제네릭 클래스가 아니더라도 정의할 수 있다.

1
2
3
4
5
class Name {
    public <T> void printClassName(T t) {
        System.out.println(t.getClass().getName());
    }
}

스크린샷 2021-05-07 오후 12 34 26

제네릭 클래스와 제네릭 메소드의 타입 매개변수가 같다면 제네릭 메소드의 타입 매개변수를 우선시 한다.

스크린샷 2021-05-07 오후 12 35 35

타입 매개변수의 제한

스크린샷 2021-05-07 오후 12 36 51
스크린샷 2021-05-07 오후 12 37 33

  • 만약 T에 Vegetable클래스가 들어가면 잘못된 타입이 들어갈 수 있기에 컴파일에러가 발생한다.
  • 그러므로 Fruit에 하위클래스만 들어올 수 있도록 경계를 설정해주어야 한다.

상한 경계

  • T extends Fruit
    • 타입 매개변수의 클래스는 Fruit클래스이거나 하위클래스이어야 한다.
      스크린샷 2021-05-07 오후 12 39 43
      스크린샷 2021-05-07 오후 12 41 17
  • 다음과 같이 Fruit혹은 Fruit의 하위클래스만이 올 것을 보장하면 컴파일에러는 발생하지 않을 것이다.

하한 경계

  • T super Fruit
    • 타입 매개변수의 클래스는 Fruit클래스거나 Fruit의 상위클래스여야한다.
      스크린샷 2021-05-07 오후 12 40 46

와일드카드

비경계 와일드카드(Unbounded Wildcards)

  • ?의 형태로 사용. 예를 들어, List<?>
  • 모든 타입이 인자가 될 수 잇다.
    스크린샷 2021-05-07 오후 12 43 41

  • 비경계 와일드카드의 사용 예시
    스크린샷 2021-05-07 오후 12 47 32

  • List가 어떤 타입이든 출력하는 코드이다.
  • 하지만 String은 Object의 인스턴스가 아니기에 컴파일에러가 발생할 것이다.
  • 임의의 타입 A의 리스트 List<A>는 List<Object>의 서브 타입이 아니다.
    스크린샷 2021-05-07 오후 12 49 09

  • 어떤 타입의 List든 출력하려면 와일드카드(?)를 사용하면 된다.
    스크린샷 2021-05-07 오후 12 49 46

  • 임의의 타입 A의 리스트 List는 List<?>의 서브 타입이다.
    스크린샷 2021-05-07 오후 12 51 20

비경계 와일드카드의 특징

  • 1)List<?>에서 Get한 원소는 Object 타입이다.
    • 비경계 와일드카드의 원소는 어떤 타입도 될 수 있다.
    • 어떤 타입이 와도 읽을 수 있도록, 모든 타입의 공통 조상인 Object로 받는다.
    1
    2
    3
    4
    
      public static void get(List<?> list) {
          Object object = list.get(0);
          Integer object = list.get(0); // Compile Error
      }
    
  • 2)List<?>에는 null만 삽입할 수 있다
    • 비경계 와일드카드의 원소가 어떤 타입인지 알 수 없다. 그러므로 타입안정성을 지키기 위해 null만 삽입할 수 있다.
    1
    2
    3
    4
    5
    6
    7
    8
    
      public static void main(String[] args) {
          List<Integer> ints = new ArrayList<>();
          addDouble(ints);
      }
    
      private static void addDouble(List<?> ints) {
          ints.add(3.14); // 만약 값을 추가할 수 있다면, List<Integer>에 Double을 추가하는 모순 발생
      }
    

상한 경계 와일드카드(Upper Bounded Wildcards)

  • ? extends T(T 상한 와일드 카드)의 형태로 사용. 에를 들어, List<? extends T>(T 상한 와일드 카드의 리스트)
  • T혹은 T의 하위 클래스만 인자로 올 수 있다
    스크린샷 2021-05-07 오후 12 59 02

상한 경계 와일드카드의 특징

  • 1)List<? extends T>에서 Get한 원소는 T이다
    • 상한 경계 와일드카드의 원소는 T혹은 T의 하위클래스이다.
    • 원소들의 최고 공통 조상인 T로 읽으면, 어떤 타입이 오든 T로 읽을 수 잇다
      1
      2
      3
      4
      5
      6
      
        public static void printList(List<? extends Fruit> fruits) {
        for(Fruit fruit:fruits) {
            System.out.println(fruit + " ");
        }
        System.out.println();
        }
      
    • 스크린샷 2021-05-07 오후 1 02 24
    • 다음과 같이 Fruit의 하위클래스인 Apple이오면 컴파일에러가 발생할 것이다.
      1
      2
      3
      4
      5
      6
      
        public static void printList(List<? extends Fruit> fruits) {
        for(Apple fruit:fruits) {
            System.out.println(fruit + " ");
        }
        System.out.println();
        }
      
  • 2)List<? extends T>에는 null만 삽입할 수 있다.
    • 상한 경계 와일드카드의 원소가 어떤 타입인지 알 수 없다
      1
      2
      3
      
        List<Apple> apples = new ArrayList<>();
        List<? extends Fruit> fruits = apples;
        fruits.add(new Banana()); // Compile Error, List<Apple>에 바나나가 들어갈 수 없다
      
    • 스크린샷 2021-05-07 오후 1 06 32

하한 경계 와일드 카드(Lower Bounded Wildcards)

  • ? super T(T 하한 와일드카드)의 형태로 사용. 예를 들어, List<? super T>(T 하한 와일드카드의 리스트)
  • T 혹은 T의 상위 클래스만 인자로 올 수 있다.
    스크린샷 2021-05-07 오후 1 08 40

하한 경계 와일드 카드의 특징

  • 1)List<? super T>에서 Get한 원소는 Object이다.
    • T 하한 경계 와일드카드의 원소는 T의 상위 클래스 중 어떤 타입도 될 수 있다.
    • 어떤 타입이 와도 읽을 수 있도록, T들의 공통 조상인 Object로 받는다.
    1
    2
    3
    4
    5
    6
    
      public static void printList(List<? super Fruit> fruits) {
          for(Object fruit:fruits) {
              System.out.println(fruit + " ");
          }
          System.out.println();
      }
    
    • List<Fruit>, List<Food>, List<Object>가 매개변수로 오면 Object 타입으로 모두 읽을 수 있다.
      스크린샷 2021-05-07 오후 1 10 58
  • 2)List<? super T>에는 T 혹은 T의 하위 클래스만 삽입할 수 있다
    • 하한 경계 와일드카드의 원소는 T 혹은 T의 상위클래스이다.
      1
      2
      3
      4
      
        List<? super Fruit> fruits = new ArrayList<>();
        fruits.add(new Apple());
        fruits.add(new Fruit());
        fruits.add(new Food()); // Compile Error
      
    • 스크린샷 2021-05-07 오후 1 13 34
    • 컴파일 에러의 원인
      • 컴파일러는 fruits가 List<Fruit>일 경우 Food는 Fruit의 상위 클래스이므로 원소를 추가할 수 없기 때문이다.
    • T 혹은 T의 하위클래스만 삽입할 수 있는 이유
      • List<Fruit>, List<Food>, List<Object> 중 어떤 리스트가 올지 fruits는 모른다. 하지만 그 중 어떤 리스트여도, Fruit혹은 Fruit의 하위클래스를 원소로 추가할 수 있다.

참고자료

스크린샷 2021-05-07 오후 1 17 09

This post is licensed under CC BY 4.0 by the author.

[10분 테코톡] 던의 JVM의 Garbage Collector

[10분 테코톡] 유안의 Spring IoC/DI