Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
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 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

DOing

제네릭을 무엇이고 왜 사용하는 것일까? 본문

Java & Kotlin

제네릭을 무엇이고 왜 사용하는 것일까?

mangdo 2021. 11. 23. 10:10

제네릭

  제네릭은 프로그램의 안정성을 도와주는 도구 중 하나로, 컴파일 단계에서 자료형을 체크해주는 도구이다.

제네릭을 왜 사용해야 하는 지, 어떻게 프로그램의 안정성을 제공해준다는 것 인지 예시를 통해 알아보자.

chicken만을 넣는 chickenBox가 있다.

 

class Chicken{ }

class ChickenBox{
    private Chicken[] box = new Chicken[10];
    int cursor = 0;

    void putData(Chicken chicken){
    	box[cursor++] = chicken;
    }

    Chicken getData(){
      Chicken chicken = box[cursor - 1]
      box[cursor - 1] = null;
      cursor--;
      return chicken
    }

}

 

이와 비슷하게 이번에는 book만 넣는 bookBox가 있다.

class Book{ }

class BookBox{
    private Book[] box = new Book[10];
    int cursor = 0;

    void putData(Book book){
    	box[cursor++] = book;
    }

    Book getData(){
        Book book = box[cursor - 1]
        box[cursor - 1] = null;
        cursor--;
        return book
    }

}

 

 

  ChickenBox는 chicken클래스에 의존적이고, BookBox는 Book 클래스에 의존적이다. ChickenBox와 BookBox는 로직상 크게 차이가 없는 무엇인가를 담는 박스일 뿐이지만, 각각 Box관련 클래스들을 만들어줘야 했다. 앞으로 Burger, Shoes 와 같이 상품이 추가될때 마다 Box 클래스를 따로 만들어줘야한다면 매우 비효율적일 것이다. 그래서 Box를 추상화하고, 일반적으로 사용할 수 있도록 다시 설계해보자.

class Box{
    private Object[] box = new Object[10];
    int cursor = 0;

    void putData(Object boxElement){
    	box[cursor++] = boxElement;
    }

    Object getData(){
        Object boxElement = box[cursor - 1]
        box[cursor - 1] = null;
        cursor--;
        return boxElement
    }
}

// 사용
Box chickenBox = new Box();
Box bookBox = new Box();

chickenBox.puData(new Chicken());
Chicken chicken1 = (Chicken)chickenBox.getData();

// 위험 : chickenBox에 book을 넣을 수 있다.
chickenBox.put(new Book());
// 프로그램 실행 도중 형변환 Exceptioin 발생
Chicken chicken2 = (Chicken)chickenBox.getData();

 

 

  제작의도와는 다르게 ChickenBox에 Chicken뿐만 아니라, book을 넣을 수 있기 때문에 프로그램 실행 도중 형변환 Exception이 발생할 수도 있다. 이러한 문제는 제네릭을 통해 해결할 수 있다. 제네릭을 사용해서 바꾼 코드는 다음과 같다.

 

// 제네릭 클래스
// Formal type parameter : T 선언
class Box <T> {
    private T[] box = new T[10];
    int cursor = 0;

    void putData(T boxElement){
    	box[cursor++] = boxElement;
    }

    T getData() {
        T boxElement = (T)box[cursor - 1]
        box[cursor - 1] = null;
        cursor--;
        return boxElement;
    }
}

// 사용
// Actual type parameter : Chicken
Box<Chicken> chickenBox = new Box<>();
chickenBox.putData(new Chicken());

Chicken chicken1 = chickenBox.getData();

// 이런 명시적 형변환 필요 없음
Chicken chicken1 = (Chicken)chickenBox.getData();

// 컴파일시 incompatible 에러 발생 
chickenBox.put(new Book());

 

  제네릭은 Formal type parameter를 선언하고 사용하여 제네릭 클래스를 만들 수 있다. 제네릭은 모든 것을 전달받을 수 있는 Object클래스와 유사하나, Formal type을 지정함으로써 Formal type과 관련된 것들로 모두 강제적으로 정해지는 제약성을 준다. 즉, T가 chicken으로 정해지면 관련된 T들이 모두 Chicken으로 정해진다. 제네릭이 선언된 클래스는 Actual type parameter를 통해 참조자료형을 선언할 수 있으며, Actual type parameter가 chicken으로 정해지면 T를 chicken 인스턴스로 강제한다. 그러므로 chickenBox에는 chicken만 저장될 수 있기 때문에 명시적인 형변환이 필요없어진다.

 

 

타입 변수

: 제네릭 클래스를 사용할 때는 어떤 타입인지를 지정해줘야한다. 이때 Object 타입 대신 타입변수(E)를 선언해서 사용한다. 변수 이름은 뭘 사용해도 상관없다. E말고도 X,Y도 쓸 수 있다. 기본적으로는 Type을 의미하기 때문에 T를 많이 사용하고 Element의 E를 사용하기도 한다. 보통 대문자 한글자로 사용한다.

다음 예제에서는 배열의 요소기 때문에 E를 사용했다.

[참고] 주로 사용하는 타입 변수명 의미

   E : Element

   K : Key

   N : Number

   T : Type

   V : Value

   R : Return Type