ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java]STEP2 - 1) 제네릭(Generic)이란?, 제네릭의 등장 배경과 필요성
    개발 공부/Java 2020. 11. 24. 19:01

     

    본 게시글은 도서 "이것이 자바다" 동영상 강의를 듣고 기록하는 TIL(Today I Learned) 입니다.
    모든 저작권은 출판사 한빛미디어와 신용권님께 있음을 알립니다.👍

     

     

     

    안녕하세요. 오랜만에 글을 쓰는 것 같습니다.

    지난 게시물을 끝으로 "이것이 자바다"의 1권을 마무리 했습니다.

    이번 게시물 부터는 Java STEP2로써, "이것이 자바다"의 2권에 대한 내용을 작성하도록 하겠습니다.

     

    이번 게시물에서는 자바의 제네릭(Generic)에 대해 학습해보도록 하겠습니다.

    제네릭은 자바 5부터 등장한 개념인데요. 도대체 제네릭이란 무엇인지! 왜 필요하게 되었는지! 알아보겠습니다.

     

     

     

    ◈ 제네릭(Generic)의 개념과 필요성


    제네릭(Generic)은 클래스와 인터페이스, 그리고 메소드를 정의할 때 타입(type)을 파라미터로 사용할 수 있도록 하는 기능을 말한다.

     

    ArrayList<Integer> num = new ArrayList<Integer>();
    HashSet<Integer> set = new HashSet<Integer>();

    우리가 보통 자바를 공부하면서 많이 접하게 되는 ArrayList나 HashSet을 처음에 선언할 때

    <Integer>와 같은 식의 문법을 많이 보게 되는데, 이 것을 바로 제네릭이라고 한다.

    즉, ArrayList나 HashSet 안에 담기는 자료형이 Integer 라고 알려주는 것이다.

     

    먼저, 제네릭이라는 것이 아직 세상에 나오지 않았다고 가정하고 아래 코드를 살펴보자.

    public class Box {
        private Object object;
        public void set(Object object) { this.object = object; }
        public Object get() { return object; }
    }

    Box라는 클래스를 만들어서 사용하고 싶은데, Box에는 정수, 실수, 문자열 처럼 딱 정해진 타입이 아니라

    내가 넣는 자료형에 따라서 어느 자료형이더라도 받을 수 있도록 만들고 싶다고 해보자.

     

    그렇다면 Box의 필드 값은 String이나 Integer, Character 와 같은 형식이 아니라 모든 클래스의 부모 클래스인 Object 클래스로 선언하면 된다. 그리고 set 메소드를 이용해서 Integer, Character, Double, String 등등 어떠한 타입을 넣어도 정상적으로 삽입이 된다.

    Box box = new Box();
    box.set("ABC");
    box.set(123);
    box.set('A');
    box.set(123.456);

     


     

    그러나 여러 가지 타입을 받기 위해서 Object 클래스를 사용하는 것이 크게 두 가지 문제점을 야기한다.

     

    1) 컴파일 시에는 에러가 검출 되지 않으나, 런타임 시 에러가 발생하여 에러 검출을 힘들게 만든다.

    // Box.java
    
    public class Box {
    	private Object object;
    
    	Box(Object object) {
    		this.object = object;
    	}
    
    	public void set(Object object) {
    		this.object = object;
    	}
    
    	public Object get() {
    		return object;
    	}
    }

    아까 만들었던 Box 클래스에 생성자를 추가하였다. 여기서는 어떠한 타입의 자료형이라도 받기 위해 Object 클래스를 사용하였다. 그리고 다음 이 Box 클래스를 이용해 실행하는 main 메소드를 보자.

    // BoxExample.java
    
    public class BoxExample {
    	public static void main(String[] args) {
    		Box box = new Box("Box");
    		Integer a = (Integer) box.get();
    	}
    }

    box 객체를 만들기 위해 "Box" 라는 문자열을 생성자 매개변수로 넣었다.

    그런데 그 다음 문장에서 box 에 있는 String 값을 Integer로 강제 타입 변환을 하여 a라는 변수에 넣고 있다.

    이를 실제로 이클립스 같은 IDE에서 돌려보면 컴파일 에러가 나타나지 않는다. (코드에 빨간 줄이 생기지 않는다.)

    그러나! 실행을 시키면 ClassCastException이 발생한다. 왜냐면 문자열을 정수로 강제 타입 변환 할 수 없기 때문이다.

     

    이는 "Box"라는 String이 Object 객체로 자동 타입 변환이 되면서, 생기는 일종의 버그다.

    따라서 Object를 이용해서 만들게 되면 이렇게 컴파일 시 에러는 나지 않아도, 런타임이 에러가 나기 때문에 개발자 입장에서는 에러 검출에 더 어려움을 겪게 되는 것이다.

     

     

    2) 불필요한 강제 타입 변환을 발생 시킨다.

    public class BoxExample {
    	public static void main(String[] args) {
    		Box box = new Box("");
    		box.set("김카일");
    		String name = (String) box.get();
    		
    		box.set(new Apple());
    		Apple apple = (Apple) box.get();
    	}
    }

    아까의 Box 클래스는 그대로 재사용한다. 그리고 Apple이라는 클래스도 별도로 만들었다고 가정한다.

    set 메소드를 이용해 box 객체에 들어간 String과 Apple 타입의 값을 다시 꺼내서 사용하기 위해서는

    (String), (Apple)과 같이 강제 타입 변환을 해주어야 한다. 왜냐면 현재 이 값들은 Object 타입이기 때문!!!

     

    따라서 여러 타입을 자유자재로 사용하기 위해서 Object 타입으로 설정을 해놓은 것이었는데

    강제 타입 변환이 너무 많이 발생하기 때문에 오히려 불편하고 코드의 길이도 늘어나게 된다.

     


     

    그래서 자바 5에서 부터 등장한 것이 바로!!!! 제네릭!! Generic 이다.

    제네릭의 등장 배경을 설명하기 위해서 참 머나먼 길을 돌아왔다.

    위에서 살펴보았던 두 가지의 불편함을 머릿 속에 잘 넣어두고, 왜 제네릭이 혁신적인지 지금부터 알아보자.

     

    제네릭은 일단 다음과 같이 선언할 수 있다.

    // 제네릭 타입의 클래스 선언
    public class Box<T> {...}
    
    // 제네릭 타입의 인터페이스 선언
    public interface Box<T> {...}

    우리는 여러 가지의 자료형을 받기 위해 Object 클래스를 사용했었지만, 이제 <T> 하나만 있으면 그럴 필요가 없다.

    <> 안에 들어가는 알파벳은 T, E, K, V 등등 아무거나 상관없지만 "대문자 알파벳 한글자" 라는 것만 지켜주면 된다.

    (물론 지키지 않는다고 에러가 나는 것은 아니고, 어디까지나 개발자들 사이에서의 관례이다.)

     

    이 <T>의 T가 이제부터 Object의 역할을 대신 수행한다.

    T는 String이 들어오면 String으로 바뀌고, Integer가 들어오면 Integer로 바뀌면서 수행을 한다.

    // 제네릭 타입의 Box 클래스
    public class Box<T> {
    	private T t;
    	public T get() { return t; }
    	public void set(T t) { this.t = t; }
    }

    이렇게 제네릭 타입 <T>를 설정해주고, 필드와 메소드 리턴 값 등을 T로 바꿔주면 된다.

    public class BoxExample {
    	public static void main(String[] args) {
    		Box<String> box1 = new Box<String>();
    		box1.set("hello");
    		String str = box1.get();
    
    		Box<Integer> box2 = new Box<Integer>();
    		box2.set(6);
    		int value = box2.get();
    	}
    }

    그런 다음, Box의 객체를 생성할 때 <String>, <Integer>로 어떤 타입의 값을 넣을 것인지 지정해주면 된다.

    이렇게 지정을 하면 get 메소드를 써서 값을 호출 할 때도 강제 타입 변환을 하지 않아도 된다.

     


     

    제네릭에 대해 조금 더 알아보자면, 타입 파라미터는 한 개가 아니라 여러 개도 가능하다.

    public class Product<T, M> {
    	private T kind;
    	private M model;
    	
    	public T getKind() { return this.kind; }
    	public M getModel() { return this.model; }
    	
    	public void setKind(T kind) { this.kind = kind; }
    	public void setModel(M model) { this.model = model; }
    }

    <T, M>으로 타입 T와 M을 전달 받을 수 있도록 선언하였다.

    그러면 Product의 객체를 생성할 때, <String, Integer>와 같은 식으로 한 번에 여러 타입 자료형을 전달 받을 수도 있는 것이다.

     

    그리고 자바 7 부터는 제네릭 타입 파라미터의 중복 기술을 줄이기 위해 다음과 같이 작성할 수 있도록 만들었다.

    Product<Tv, String> product = new Product<>();

    new Product<TV, String>(); 에서 TV, String 부분을 따로 작성하지 않아도 타입 파라미터를 자동으로 유추한다.

    이로써 코드가 간결해지고 중복을 줄일 수 있게 되었다.

     

     

     

    이번 게시물은 여기서 마치겠습니다.

    쓰다 보니 글이 길어져서 제네릭 메소드와 상속 부분에 대해서는 다음 게시물에서 다뤄보도록 하겠습니다.

     

    긴 글 읽어주셔서 감사합니다~~!!

     

     

     

    댓글

Designed by Tistory.