ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java] Generic 개념 및 정리
    Language/Java 2020. 1. 22. 17:21
    728x90
    반응형

    Generic 정의

    • 일반적인 코드를 작성하고, 이 코드를 다양한 타입의 객체에 대하여 재사용하는 프로그래밍 기법이다.
    • 타입을 파라미터화해서 컴파일시 구체적인 타입이 결정되도록 하는 것이다.
    • 클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법이다.
    • Generic은 컬렉션, 람다식, 스트림등에서 널리 사용된다.

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class Person<T> {
        public T info;
     
        public static void main(String[] args) {
            Person<String> p1 = new Person<>();
            Person<StringBuilder> p2 = new Person<>();
        }
    }
     
     
     

     

    Generic 타입이란?

    • 타입을 파라미터로 가지는 클래스와 인터페이스를 말한다.
    • 선언시 클래스 또는 인터페이스 이름 뒤에 <> 부호가 붙는다.
    • <>사이에는 타입 파라미터가 위치한다.
      • public class 클래스명 <T> { ... }
      • public interface 인터페이스명 <T> { ... }
    • 타입 파라미터
      • 일반적으로 대문자 알파벳 한문자를 사용한다.
      • Main메소드에서 타입 파라미터 자리에 구체적인 타입을 지정해야한다.

     

     

    Generic 장점

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

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import java.util.ArrayList;
    import java.util.List;
     
    public class Test{
        public static void main(String[] args) {
            List list = new ArrayList<>();
            list.add("hello");
            String str = (String)list.get(0);
            
        }
    }
     
     

     

    이렇게 Generic을 사용하지 않으면 list 객체에 어떤 타입의 객체도 받아 올 수 있지만 get으로 해당 객체를 꺼내올 때 빈번하게 타입변환이 발생하기 때문에 비효율적이다. 

     

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import java.util.ArrayList;
    import java.util.List;
     
    public class Test{
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            list.add("hello");
            String str = list.get(0);
     
        }
    }
     
     

     

    하지만 위와 같이 Generic 타입을 이용해서 <String>을 지정해준다면 형변환을 하지 않아도 get 메소드를 사용해서 객체를 꺼내올 수 있다.

     

    구체적인 예제를 보면서 이해해보자.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class Box {
        private Object object;
        
        public void set(Object object) {
            this.object = object;
        }
        
        public Object get() {
            return object;
        }
     
        public static void main(String[] args) {
            Box box = new Box();
            box.set("hello");
            String str = (String)box.get(); //빈번한 형변환 발생
        }
    }
     
     
     

     

    위처럼 Box라는 클래스에서 자료형을 Object로 해놓으면 set메소드를 이용해서 "hello"를 저장할 때 String형인 "hello"를 Object로 형변환을 해야하고 다시 get메소드로 꺼내올 때도 Object형에서 String형으로 형변환을 해야하기 때문에 빈번한 형변환이 발생한다. 여기까지는 위와 비슷한 내용인데 이제 왜 Generic을 사용하면 좋은지 내부 과정이 어떻게 이뤄지는지에 대해서 알아보겠다.

     

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class Box<T> {
        private T t;
     
        public void set(T t) {
            this.t = t;
        }
     
        public T get() {
            return t;
        }
     
        public static void main(String[] args) {
            Box<String> box = new Box();
            box.set("hello");
            String str = box.get();
        }
    }
     
     
     

     

    위와 같이 Generic을 사용하면 아까 위의 List예제 처럼 형변환을 하지 않고도 쓸 수 있다. 

     

     

    그런데 왜 형 변환 없이도 사용할 수 있는 것일까? 

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class Box<String> {
        private String t;
     
        public void set(String t) {
            this.t = t;
        }
     
        public String get() {
            return t;
        }
    }
     
     
     

     

    Box 클래스의 <String>으로 인스턴스화 할 때 메모리가 할당이 되는데 이 때 컴파일러가 T 부분을 String으로 바꿔준다고 생각하자. 그렇기 때문에 box 객체는 형변환을 하지 않고도 사용할 수 있는 것이다.

     

     

    그러면 < > 안에 쓰는 대문자는 어떤 의미가 있을까?

     
     
     
     
     
     
     
     
    E : Element
     
    K : Key
     
    N : Number
     
    T : Type
     
    V : Value
     

     

    일반화 유형에 사용되는 자료형으로 다음과 같이 자료형에 해당하는 하나의 대문자를 사용한다. 

    나는 이게 궁금했다. 문법적으로 특정상황에 맞는 대문자를 쓰지 않으면 에러가 발생하는지? 궁금했다. 하지만 어떤걸 써도 상관없다. 단순히 코드의 가독성을 높히기 위해서 위처럼 약속해놓은 대문자를 해당 상황에 맞게 쓰는 것이다.

     

     

    예를들어서 ArrayList를 보자

    1
    2
    3
    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable
     
     
     

     

    ArrayList는 배열의 형태로 구성되어 있기 때문에 E가 적합하다. 그 이유는 E의 의미는 Element이기 때문에 배열과 비슷한 형태를 이루고 있는 것에 사용하는 것 같다.

     

     

     

    그리고 HashMap을 보자

    1
    2
    public class HashMap<K,V> extends AbstractMap<K,V>
        implements Map<K,V>, Cloneable, Serializable 
     

     

    HashMap은 Key, Value를 이용한 Collection이기 때문에 K(Key)와 V(Value)를 Generic으로 사용하는 것을 알 수 있다.

     

     

     

    그리고 인터페이스 중에 하나인 Iterable의 예를 들어보겠다.

    1
    2
    3
    4
    5
    6
    7
    8
    public interface Iterable<T> {
        /**
         * Returns an iterator over elements of type {@code T}.
         *
         * @return an Iterator.
         */
        Iterator<T> iterator();
     
     
     

     

    이렇게 배열, 집합, 연결리스트와 같이 원소가 들어가는 개념이 아닌 일반적인 상황에서는 T를 사용하는 것 같다. 

    T의 의미는 Type인지라 자료형을 유연하게 하기 위할 때 사용한다.

     

     

    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
    32
    33
     
    import java.util.ArrayList;
     
    public class MyContainer<E> {
        private ArrayList<E> list;
     
        public MyContainer() {
            list = new ArrayList<>();
        }
     
        public E get(int index) {
            return list.get(index);
        }
     
        public void add(E element) {
            list.add(element);
        }
     
        public static void main(String[] args) {
            MyContainer<String> p1 = new MyContainer<>();
            p1.add("algol"); p1.add("C");
            MyContainer<Integer> p2 = new MyContainer<>();
            p2.add(1); p2.add(2); p2.add(3);
     
            System.out.println(p1.get(0));  // algol
            System.out.println(p1.get(1));  // C
            System.out.println(p2.get(0));  // 1
            System.out.println(p2.get(1));  // 2
            System.out.println(p2.get(2));  // 3
     
        }
    }
     
     
     

     

    이렇게 MyContainer클래스를 <E>로만 지정해놓으면 객체만들 때마다 자료형을 지정해주기 때문에 상당히 유연하고 코드의 중복성도 막고 유지보수도 편하다는 장점이 있다.

     

     

    대표이미지 출처

    https://offbyone.tistory.com/327

     

    Java Generic사용법과 Generic에서의 와일드카드

    Java의 Generic은 C, C++의 템플릿과 같은 기능 입니다. 하나의 코드를 다양한 데이터 타입에 대해 사용할 수 있도록 하는 편리한 기능 입니다. 자바에서는 1.5 버전에서 부터 사용할 수 있습니다. 자바에서도 자..

    offbyone.tistory.com

     

     

    .

     

     

    반응형

    댓글

Designed by Tistory.