IT/Java

제네릭(Generic)

딩딩예 2019. 7. 25. 21:50

왜 제네릭(Generic)을 사용해야 하는가?

제네릭(Generic)이란

  • 타입을 파라미터화해서 컴파일시 구체적인 타입이 결정되도록 하는 것
    • 자바5부터 새로 추가된 기능이다.
    • 컬렉션, 람다식(함수적 인터페이스), 스트림,NIO에서 널리 사용된다.

제네릭을 사용하므로서 얻는 이점

  • 컴파일시 강한 타입 체크를 할 수 있다.

    • 실행시 타입 에러가 나는 것보다는 컴파일시에 미리 타입을 강하게 체크해서 에러를 사전에 방지한다.

    • 타입변환을 제거할 수 있다.

      List list = new ArrayList();
      list.add("hello");                    //object으로 자동형변환
      String str = (String) list.get(0);    //String으로 강제타입변환
      
      // 제네릭 이용하기
      List<String> list = new ArrayList<String>();
      list.add("hello");                    //String으로 해야한다.
      String str = list.get(0);

2절 제네릭 타입

제네릭 타입이란

  • 타입을 파라미터로 가지는 클래스와 인터페이스를 말한다.

    • 선언시 클래스 또는 인터페이스 이름 뒤에 "<>" 부호가 붙는다.

    • "<>" 사이에는 타입 파라미터가 위치한다.

      public class 클래스명<T> {...}
      public interface 인터페이스명<T>{...}
  • 타입 파라미터

    • 일반적으로 대문자 알파벳 한문자로 표현한다.
    • 개발 코드에서는 타입 파라미터 자리에 구체적인 타입을 지정해야한다.
  • 제네릭

    public class Box<T> {
    
        private T t;
        public T get(){ return t;}
        public void set(T t){this.t = t};
    
    }
    Box<String> box = new Box<String>();
    box.set("hello");
    String str = box.get();

3절. 멀티 타입 파라미터

두개 이상의 타입 파라미터를 사용해서 선언할 수 있다.

  • class<K,V...> {...}

  • interface<K,V..> {...}

    public class Product<T,M> {
    
        private T kind;
        private M model;
    
        public T getKind() { return kind; }
        public M getModel() { return model; }
    
        public void setKind(T kind) { this.kind = kind; }
        public void setModel(M model) {this.model = model; }
    
    }
    Product<Tv, String> product = new Product<>();    //< > 생략가능

4절. 제네릭 메소드

제네릭 메소드

  • 매개변수 타입과 리턴 타입으로 타입 파라미터를 갖는 메소드

  • 제네릭 메소드 선언 방법

    public <타입파라미터,...> 리턴타입 메소드명(매개변수,...){...}
    public <T> Box<T> boxing(T t) { ... }        //Box<T>객체가 리턴되어서 나옴
  • 제네릭 메소드를 호출하는 방법

    • 리턴타입 변수 = <구체적인타입> 메소드명(매개값); //명시적으로 구체적타입 지정

    • 리턴타입 변수 = 메소드명(매개값); //더 자주 사용한다. 매개값을 보고 구체적 타입 추정

      Box<Integer> box = <Integer>boxing(100);
      Box<Integer> box = boxing(100);
public class Util {
    public static<K,V> boolean compare(Pair<K,V> p1, Pair<K,V> p2) {
        boolean keyCompare = p1.getKey().equals(p2.getKey());
        boolean valueCompare = p1.getValue().equals(p2.getValue());

        return keyCompare&&valueCompare ;
    }
}
package chap13.exam02_generic_method;

public class Pair<K,V> {

    private K key;
    private V value;

    Pair(K key, V value){
        this.key = key;
        this.value = value;   }

    public void setKey(K key) {    this.key = key; }
    public void setValue(V value) {      this.value = value;  }

    public K getKey() { return  key;   }
    public V getValue() { return  value; }

}

13.5 제한된 타입 파라미터

타입 파라미터에 구체적인 타입을 제한할 필요가 있을 경우

  • 상속 및 구현 관계를 이용해서 타입을 제한

    public < T extends 상위타입> 리턴타입 메소드(매개변수,...) {...}
    • 상위 타입은 클래스 뿐만 아니라 인터페이스도 가능하다.

      인터페이스라고해서 extends 대신 implements를 사용하지 않는다.

  • 타입 파라미터를 대체할 구체적인 타입

    • 상위타입이거나 하위 또는 구현 클래스만 지정할 수 있다.
  • 주의할 점

    • 메소드의 중괄호 {} 안에서 타입 파라미터 변수로 사용 가능한 것은 상위 타입의 멤버(필드, 메소드)로 제한된다.

    • 하위 타입에만 있는 필드와 메소드는 사용할 수 없다.

      public <T extends Number> int compare(T t1, T t2) { 
          //Number이거나 하위클래스 
          double v1 = t1.doubleValue();     //Number의 doubleValue() 메소드 사용
          double v2 = t2.doubleValue();    //특정 자식클래스에만있는 메소드사용(x)
          return Double.compare(v1,v2);
      
      }

6절. 와일드카드 타입

와일드카드<?> 타입

  • 제네릭 타입을 매개변수나 리턴타입으로 사용할 때 타입 파라미터를 제한할 목적

    public static void registerCourse(Course<?> course)
    public static void registerCourseStudent(Course<? extends Student> course)
    public static void registerCourse(Course<? super Worker> course)
  • 와일드카드 타입의 세가지 형태
    1. 제네릭 타입<?> : Unbounded Wildcards (제한없음)
    2. 제네릭 타입 <? extends 상위 타입> : Upper Bounded Wildcards (상위 클래스 제한)
    3. 제네릭 타입 <? super 하위 타입> : Lower Bounded Wildcards (하위 클래스 제한)

🔼Course<?> :수강생은 모든 타입(Person,Worker,Student, HighStudent) 이 될 수 있다.

🔼Course<? extends Student> : 수강생은 Student와 HighStudent만 될 수 있다.

🔼Course<? super Worker> : 수강생은 Worker와 Person만 될 수 있다.

7절. 제네릭 타입의 상속과 구현

제네릭 타입을 부모 클래스로 사용할 경우

  • 타입 파라미터는 자식 클래스에도 기술해야한다.

    public class ChildProduct<T,M> extends Prouduct<T,M>{...}
  • 추가적인 타입 파라미터를 가질 수 있다.

    public class ChildProduct<T,M,C> extends Prouduct<T,M>{...}

제네릭 인터페이스를 구현할 경우

  • 타입 파라미터는 구현 클래스에도 기술해야한다.

    public class StorageImpl<T> implements Storage<T> {...}
public class StorageImpl<T> implements Storage<T> {

    private T[] array;

    public StorageImpl(int capacity) {
        this.array = (T[])(new Object[capacity]);
        //타입 파라미터로 배열을 생성하려면 new T[n]형태로 생성할 수 없고 
        //(T[])(new Object[]) 생성해야한다.
    }

    @Override
    public void add(T item,int index){
        array[index] = item;
    }

    @Override
    public T get(int index){
        return array[index];
    }
}
Storage<Tv> storage = new StorageImpl<Tv>(100);
storage.add(new Tv(), 0);
Tv tv = storage.get(0);