Java - 제네릭(Generic)

Java

Posted by kwon on 2020-05-08

Java - 제네릭(Generic)

  • 제네릭(Generic)은 클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법을 말한다.
1
2
3
4
5
class Person<T>{
public T info;
}
Person<String> p1 = new Person<String>();
Person<StringBuilder> p2 = new Person<StringBuilder>();
  • 위와 같이 사용한다면 외부에서 생성한 객체의 타입을 <>에 정의함으로써 Person클래스의 info라는 필드의 타입이 정의되는 것이다.
    • p1으로 객체를 생성한다면 info의 데이터 타입은 String이 되는 것이고,
    • p2로 객체를 생성한다면 info의 데이터 타입은 StringBuilder이 되는 것이다.

  • <T>의 위치에는 레퍼런스 형만 올 수 있고, 기본 데이터 타입((primitive type) => int, char 등..)은 제네릭으로 사용할 수 없다.(기본 데이터 타입은 객체가 아님)
  • 따라서, 기본 데이터 타입을 객체인 것처럼 만들 수 있는 객체를 제공하는 wrapper 클래스를 사용한다.
    • int -> Integer, double -> Double …와 같이 사용
    • Integer id = new Integer(1); 와 같이 생성하여 생성자의 매개변수로 넘길 수 있다.
    • p1.id.intValue()와 같이 wrapper 클래스(Integer)의 메서드를 사용하여 wrapper 클래스가 담고 있는 원시 데이터 타입으로 반환 받을 수 있다.

제네릭의 생략

  • 다음과 같은 경우 제네릭은 생략 가능하다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Person<T, S>{
    public T info;
    public S id;
    Person(T info, S id){
    this.info = info;
    this.id = id;
    }
    }
    public class GenericDemo {
    public static void main(String[] args) {
    EmployeeInfo ei = new EmployeeInfo(1);
    Integer id = 10;
    Person p1 = new Person(ei, id);
    }
    }
  • 이 경우, 이미 ei와 id의 타입을 자바가 알고 있기 때문에 제네릭을 생략할 수 있다.

제네릭의 제한

  • <T extends Info>와 같은 형태로 사용
    • Info클래스 또는 인터페이스를 상속받는 클래스의 데이터 타입인 경우로 제한한다.
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      abstract class Info{
      public abstract int getLevel();
      }

      class EmployeeInfo extends Info{
      public int rank;
      EmployeeInfo(int rank){
      this.rank = rank;
      }
      public int getLevel(){
      return this.rank;
      }
      }

      class Person<T extends Info>{
      public T info;
      Person(T info){
      this.info = info;
      }
      }
      public class GenericDemo {
      public static void main(String[] args) {
      Person<EmployeeInfo> p1 = new Person<EmployeeInfo>(new EmployeeInfo(1));
      Person<String> p2 = new Person<String>("부장"); // 이 경우에는 에러

      }
      }
  • Person<EmployeeInfo> p1 = new Person<EmployeeInfo>(new EmployeeInfo(1));와 같이 Info를 상속받는 EmployeeInfo타입인 경우에는 정상적으로 사용이 가능하지만
  • Person<String> p2 = new Person<String>("부장");와 같이 String타입인 경우 에러가 발생하게 된다.
  • 즉, class Person<T extends Info>{에서 T에는 Info 클래스 혹은 그 자식만이 올 수 있다.
  • abstract class Info{ 같은 경우 추상 클래스가 아니라 일반 클래스 혹인 인터페이스인 경우에도 동일하다.
    • Info가 인터페이스라고 해서 T extends Info>의 부분이 implements로 변한다거나 하지는 않는다.

제네릭의 장점

  • 타입 안정성을 제공한다.(의도하지 않은 타입의 객체를 저장하는 것을 막고, 저장된 객체를 꺼내올 때 원래의 타입과 다른 타입으로 형변환하여 발생할 수 있는 오류를 줄인다.)
  • 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해진다.
    • 다룰 객체의 타입을 미리 명시함으로써 형변환을 하지 않아도 되도록 하는 것이다.
    • 기존에는 다양한 종류의 타입을 다루는 메서드의 매개변수나 리턴 타입으로 Object타입의 참조 변수를 많이 사용했고, 그로 인해 형변환이 불가피했지만, 이젠 Object타입 대신 원하는 타입을 지정해주기만 하면 되는 것이다.

참조
https://devbox.tistory.com/entry/Java-%EC%A0%9C%EB%84%A4%EB%A6%AD
https://www.youtube.com/watch?v=YUinFIexEQ4