열심히 끝까지

[10분 테코톡] 두강의 Generics 본문

디바이스 융합 자바(Java)기반 풀스택 개발자 양성과정(과제)

[10분 테코톡] 두강의 Generics

노유림 2022. 7. 10. 00:30

두강의 Generics
https://www.youtube.com/watch?v=n28M8iryFPw

[Generics - 타입문지기]

- Generic?
   : 다양한 타입의 객체들을 다루는 메서드나 클래스에 
     컴파일 시의 타입 체크를 해주는 기능

   ex ) List<String> stringList = new ArrayList<>();
        stringList.add(1);   < 에러
        stringList.add(3.14);  < 에러
        stringList.add("우테코");  < ok
        >> 이처럼 의도치 않는(1, 3.14) 타입이 들어오는 것을 막는
             작업 == "타입 체크"

  제네릭 사용 효과
  : 객체의 타입 안정성을 높이고 형변환의 번거로움이 줄어듦

  제네릭 형태
public class Box<T> { }
>> 클래스명 뒤에 꺽세에 타입 매개변수가 추가 되는 것
     >> T : 타입 매개변수
     >> Box<T> : 제네릭 클래스

예시 ----
public class Box<T> {
     List<T> items = new ArrayList<>();

     public void add(T item) {
          item.add(item);
     }
}
>> T가 전달이 되면 아래의 T들에게도 공유가 됨
   : String으로 변경되면 String으로 공유
     int로 변경되면 int로 공유
   : 형태가 공유되면 그 형태의 값만 설정 가능
    > String이면 String값만 추가 가능
    > int면 int값만 추가 가능 

- 제네릭을 사용하면 좋은 점
  1. 강력한 타입 체크를 해줌
    >> int형(Integer)만 들어가게 설정하게 되면
         정수는 잘 들어가다가 문자형이 들어오면 밑줄이 그어지며
         타입 체크를 함 > 문자형을 정수로 바꾸면 밑줄 사라짐
  2. 형변환을 하지 않아도 됨
     >> 제네릭이 없을 때, 인덱스에 해당하는 값을 보여줌
     >> 모든 타입이 들어오기 때문에 어떤 값을 넣더라도 잘 들어감
         > 하지만 나중에 꺼내서 쓸 때는 형변환을 명시적으로 하여야 하는데
            제네릭이 존재하면 형변환을 하지 않아도 된다는 장점 존재

- 제네릭 사용법
  1. 참조변수와 생성자에 대입된 타입이 일치해야 함
    ex ) 
          Box<Apple> appleBox = new Box<Apple>();
          Box<Grape> appleBox = new Box<Grape>();
          Box<Fruit> fruitBox = new Box<Apple>();
          >> Apple이 Fruit를 상속해도 불가능!

  2. 제네릭 클래스가 상속 관계인 것은 괜찮음
     ex ) 
          Box<Fruit> fruitBox = new FruitBox<Fruit>();
          List<Fruit> fruitBox = new ArrayList<Fruit>();
          Box<Fruit> fruitBox = new FruitBox<Apple>();
          >> 단, 여전히 대입되는 타입은 같아야 함

- 제네릭의 문제점
  Box에 과일 종류만 넣고 싶은데, 아무거나 다 들어감
  ex ) 
   과일만을 넣고 싶어서 FruitBox 생성
     Box<Toy> fruitBox = new FruitBox<Toy>();
     > 근데 Toy가 제네릭 안에 들어가면 Toy도 들어가는 문제점 발생
   >> 그럴 때 쓸 수 있는 것이 제네릭

      public class Box<T extends Fruit>{
           List<T> items = new ArrayList<>();

           void add(T item){
                item.add(item);
            }
      }
     >> 전달된 타입 매개변수 뒤에 Fruit이라는 제한된 범위를 지정해주면
           Fruit을 상속하는 타입만 들어올 수 있음
         >> 제한해주면 Toy객체는 더이상 들어가지 않음
             : Box 타입 매개변수로 Fruit을 상속하지 않는 타입은 올 수 없음

- 다양한 타입 매개변수
T - Type(일반적인 타입)
E - Element(자바 컬렉션 프레임워크에서 주로 사용 - 요소)
K - Key   (페어나 맵 같은 곳에서 사용) 
V - Value(페어나 맵 같은 곳에서 사용)
N - Number
S, U, V .. - 2nd, 3rd, 4th type, .. (타입 뒤를 잇는 두번째 세번째 타입을 표시)

> 타입 매개변수를 이렇게 표기하는 것이 관례
>> 하지만 원한다면 자신이 원하는 대로 표기하여 사용해도 문제가 되지 않음
    ex ) public class Box<woowa123>{   }
     // Map
      public Interface Map<K,V>{
           Set<Map.Entry<K,V>> entrySet();
      }
     // List
      public Interface List<K,V>{
           Interface<E> iterator();
      }
     // Type, 2nd, Return type
      public Interface BiFunction<T,U,R>{
           R apply(T t, U u);
      }
     // 
      public Interface BiConsumer<T,U>{
           void accept(T t, U u);
      }

>> 제네릭 메서드
  [ public <T> T foo(List<T> list){ } ]
   <T> : 타입 매개변수
    T : 리턴 타입
    foo : 메소드 명
    List<T> list : 매개변수

  >> 일반적인 메서드와의 차이 리턴타입 앞에 
       타입 매개변수 작성하는 곳이 존재한다는 것

  [ public <T> T foo(List<T> list){ } ]
   > 위의 메서드에 전달되는 타입 매개변수의 범위를 제한하고 싶다면?
 >> public <T extends Fruit> T foo(List<T> list) { } 로 하면 됨
     >> public <T> foo(List<T extends Fruit> list) { }가 아니다!!!
 >> 뒤가 아닌 앞에 넣을 것

ex ) 
public static <T> T getFirstItem(List<T> list){
    return list.get(0);
}
>> 안에 있는 여러가지 T중에 앞에 있는 T가 호출
    T 같은 경우 제네릭 메서드에서 T도 리턴이 가능하다는 것을 보여주기 위해서 사영
    >> 다 사용 가능

- 제네릭 사용하면서 헷갈리는 것
1. 제네릭 메서드의 타입과 제네릭 클래스의 타입은 서로 다른 것!!!
public class Box<T> {
   public <T> void printParamClass(T t){
       System.out.println(t.getClass());
   }
}
>> 이때 Box의 T와 아래 public T는 같아보이지만 
      다른 타입일 수 있음
   >> PrintParamClass의 T는 전달된 타입의 클래스를 출력하는 메서드
ex ) 
Box<Fruit> box = new Box();

box.printParamClass("String");
box.printParamClass(1);
box.printParamClass(3.14);

>> box에 전달이 되면 만약에 
     제네릭 클래스에서 사용되는 T와
     메서드에서 사용되는 T가
     같다면?
   >> String이나 Double이 전달될 수 없음
     >> 근데 String도 Double도 전달 가능

2. 제네릭 클래스가 아닌 일반 클래스에서도 사용 가능

- 와일드 카드
  : 기호 '?'로 표현
  : 어떠한 타입도 될 수 있다는 것을 의미
  
   사용법
  : 아무 타입의 list를 출력하는 메서드
  <MyClass>
   public static void printList(List<Object> list) {
      for(Object elem : list){
          System.out.println(elem + " ");
      }
   }
>> static 메서드는 아무 타입이나 출력하는 메서드
    > list Object를 넣고 출력
  main문에...
  List<Fruit> fruits = new ArrayList<>();
  MyClass.printList(fruits); 
  저 List 타입에 <Fruit>를 넣어서 전달하려고 하면 컴파일 에러가 남
 >> list Object가 아니기 때문에 못받음
 >> 정확히 Object 형의 요소를 갖는 List만 들어올 수 있기 때문!

>> 이 것을 해결하기 위해서 와일드 카드를 사용
  : 모든 타입이 들어올 수 있도록 수정
   public static void printList(List<? extends Object> list) {
      for(Object elem : list){
          System.out.println(elem + " ");
      }
   }
 > 아무 타입이나 다 받을 수 있게 됨
>> 결론
  제네릭은 형변환의 번거로움을 줄임
  의도하지 않은 타입이 들어오는 것을 막기 위한 것(강력한 타입체크)