본문 바로가기
나만의 정리

자바 제네릭 완전정복

by 코리늬 2019. 3. 21.

다양한 제네릭 표현

소스코드를 보다가 <? extends > 라는 코드를 봤다.

?????

찾아봤다..


제네릭 표현 중 하나라고 한다.


제네릭
  • 클래스 내부에서 사용할 데이터 타입을 나중에 인스턴스를 생성할 때 확정하는 것

  • 객체의 타입을 컴파일 시 체크하기 때문에 객체의 타입 안정성을 높이고 형변환의 번거로움이 줄어든다.

class Person<T> {
   public T info;
}

public class Generic{
   public static void main(String[] args){
       Person<String> p1 = new Person<String>();
       Person<StringBuilder> p2 = new Person<StringBuilder>();
  }
}

개인적으로 이 예제가 가장 제네릭을 이해하기 쉬웠다.


Person 클래스의 info 필드의 데이터 타입은 T로 되어있다.

하지만 T라는 데이터 타입은 존재하지 않는다. 이 값은 인스턴스가 생성될 때 정해진다.

따라서 main에서 p1, p2가 생성되었을 때

p1.info 라고 선언을 해서 사용하면 T는 String이 되고

p2.info 라고 선언을 해서 사용하면 T는 StringBuilder가 된다.




인스턴스를 생성할 때 사용한 데이터 타입에 따라서 결정된다.


그렇다면 쓰면 뭐가 좋냐 ??


장점
  1. 타입의 안정성을 제공한다.

    • 의도하지 않은 타입의 객체를 저장하는 것을 막고, 저장된 객체를 꺼내올 때 원래의 타입과 다른 타입으로 형변환되어 발생할 수 있는 오류를 줄여준다는 뜻이다.

  2. 타입 체크와 형변환을 생략할 수 있으므로 코드가 간결해 진다.

    • 다룰 객체의 타입을 미리 명시함으로써 형변환을 하지 않아도 된다.

컬렉션 클래스 이름 바로 뒤에 저장할 객체의 타입을 적어주면, 컬렉션에는 지정한 타입의 객체만 들어갈 수 있다.


컬렉션 클래스<지정 할 객체의 타입> 변수명 = new 컬렉션 클래스<지정 할 객체의타입>();

ArrayList<Tv> tvList = new ArrayList<Tv>();
//tv객체만 저장할 수 있는 ArrayList 생성
tvList.add(new Tv());
tvList.add(new Radio()); //컴파일 에러

다형성을 사용해야 하는 경우 부모타입을 지정함으로써 여러 종류의 객체를 저장할 수 있다.
class Product{}
class Tv extends Product{}
class Audio extends product {}

//product 클래스의 자손객체들을 저장할 수 있는 ArrayList 생성
ArrayList<Product> list = new ArrayList<Product>();
list.add(new Product());
list.add(new Tv());
list.add(new Audio());

Product p = list.get(0); //형 변환 필요없음
Tv t = (Tv)list.get(i);  //형 변환 필요

ArrayList가 Product 타입의 객체를 저장하도록 지정하면, 이들의 자손인 Tv와 Audio 타입의 객체도 저장할 수 있다.

다만, 꺼낼 때 원래의 타입으로 형변환 해야 한다.



Product 클래스가 Tv클래스의 조상이라 해도 아래와 같이는 할 수 없다.
ArrayList<Product> list = new ArrayList<Tv>(); //허용 x 

List<Tv> tvList = new ArrayList<Tv>(); //허용



와일드 카드

제네릭에서는 보통 하나의 타입을 지정한다.

하지만 와일드 카드 ' ? ' 를 사용하면 하나 이상의 타입을 지정할 수 있게 해준다.

예를들어 와일드 카드가 있고, 그 타입이 Product의 자손이라고 선언하면, Tv객체를 저장하는 ArrayList<Tv> 또는 Audio 객체를 저장하는 ArrayList<Audio> 를 매개변수로 넘겨 줄 수 있다.

둘다 Product의 자손이기 때문이다.


문법

<?>타입 변수에 모든 타입을 사용할 수 있음
<? extends T>T타입과 T타입을 상속받는 자손 클래스 타입만 사용할 수 있음
<? super T>T타입과 T타입이 상속받는 조상 클래스 타입만 사용할 수 있음

//Product 또는 그 자손들이 담긴 ArrayList를 매개변수로 받는 메소드
public static void printAll(ArrayList<? extends Product> list){
   for(Unit u : list){
       System.out.println(u);
  }
}



복수의 제네릭

클래스 내에 여러개의 제네릭을 필요로 하는 경우가 있다.

제네릭을 나타내는 Type의 규칙은 아래와 같다.


E - Element (used extensively by the Java Collections Framework) K - Key N - Number T - Type V - Value S, U, V etc. - 2nd, 3rd, 4th types

class EmployeeInfo{
  public int rank;
  EmployeeInfo(int rank){
      this.rank = rank;
  }
   
   
  //복수의 제네릭을 사용할 시 ,로 구분한다.
  class Person<T, S> {
      public T info;
      public S id;
      Person(T info, S id){
          this.info = info;
          this.id = id;
      }
  }
   
  public class MultiGeneric{
      public static void main(String[] args){
          Person<EmployeeInfo, int> p1 = new Person<EmployeeInfo, int> (new EmployeeInfo(1), 1);
      }
  }
}

위의 코드는 예외가 발생되지만 형식만 보도록 하자.

예외의 해결은 아래 예제를 보도록 하자.



기본 데이터 타입과 제네릭

제네릭은 참조 데이터 타입에 대해서 사용할 수 있다. 기본 데이터 타입에서는 사용할 수 없다.

wrapper 클래스로 사용할 수 있다. -> 기본 타입을 객체타입으로 만드는 것

그래서 위 코드를 변경해보자.

class EmployeeInfo{
   public int rank;
   EmployeeInfo(int rank){
       this.rank = rank;
  }
   
    class Person<T, S> {
       public T info;
       public S id;
       Person(T info, S id){
           this.info = info;
           this.id = id;
      }
  }
   
   public class MultiGeneric{
       public static void main(String[] args){
       EmployeeInfo e = new EmployeeInfo(1);
       Integer i = new Integer(10);
           Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(e,i);
           
           System.out.println(p1.id.intValue());
      }
  }
}

new Integer는 기본 데이터 타입인 int를 참조 데이터 타입으로 변환해주는 역할을 한다.

이러한 클래스를 wrapper 클래스라고 한다.



제네릭의 생략

제네릭은 생략 가능하다. 아래 두 개의 코드가 있는데 e와 i의 데이터 타입을 알기 때문에 가능하다.

EmployeeInfo e = new EmployeeInfo(1);
Integer i = new Integer(10);
Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(e,i);
Person p2 = new Person(e,i); //이렇게 제네릭을 생략해도 가능



메소드에 적용

제네릭은 메소드에도 적용할 수 있다.

class EmployeeInfo{

   public int rank;

   EmployeeInfo(int rank){
   this.rank = rank;
  }

}

class Person<T, S>{

   public T info;

   public S id;

   Person(T info, S id){

       this.info = info;

       this.id = id;

  }


// U 데이터 타입은 info라는 매개변수의 데이터타입(EmployeeInfo)이 된다.
   public <U> void printInfo(U info){

       System.out.println(info);

  }

}

public class GenericDemo {

   public static void main(String[] args) {

       EmployeeInfo e = new EmployeeInfo(1);

       Integer i = new Integer(10);

       Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(e, i);

       p1.<EmployeeInfo>printInfo(e);

       p1.printInfo(e);// 제네릭 생략이 가능함

  }

}



제네릭의 제한
  1. extends

제네릭으로 올 수 있는 데이터 타입을 특정 클래스의 자식으로 제한할 수 있다.

class EmployeeInfo extends Info{

  public int rank;

  EmployeeInfo(int rank){ this.rank = rank; }

  public int getLevel(){

      return this.rank;

  }

}


// Info 클래스나 info의 자식 클래스만이 제네릭으로 올 수 있는 데이터 타입이 된다.
class Person<T extends Info> {
   
}

  1. implements

class EmployeeInfo implements Info{

  public int rank;

  EmployeeInfo(int rank){ this.rank = rank; }

  public int getLevel(){

      return this.rank;

  }

}

class Person<T extends Info>{// extends는 상속이 무엇인가가 아니라, 부모가 누군가를 확인하는 코드이다.(implement가 아니다.)

  public T info;

  Person(T info){ this.info = info; }

}



클래스와 인터페이스를 동시에 상속받고 구현해야 한다면 엠퍼센트(&) 기호를 사용하면 된다.

class AnimalList<T extends LandAnimal & WarmBlood> {...}



참고

https://devbox.tistory.com/entry/Java-%EC%A0%9C%EB%84%A4%EB%A6%AD

'나만의 정리' 카테고리의 다른 글

컴파일과 런타임  (0) 2019.07.14
[웹] White Domain, RBL, SPF, DKIM  (0) 2019.07.14
빌더 패턴(Builder Pattern)  (0) 2019.03.21
대용량 트래픽 처리(입문)  (1) 2019.03.04
인터넷 용어 정리  (0) 2019.03.02

댓글