본문 바로가기
💊 Java & Kotlin & Spring/- Java & kotlin

[Java 심화] Generic(제네릭)을 핵심만 이해해보자.

by Wonit 2020. 8. 27.

이 시리즈는 Spring에서 Generic을 이용하여 CRUD 인터페이스를 구성해보자.와 이어지는 글 입니다.

  1. Generic(제너릭)을 핵심만 이해해보자.
  2. Spring에서 Generic을 이용하여 CRUD 인터페이스를 구성해보자.

이 글은 이론에 관련한 글이고 실전에서 어떻게 쓰이는지 궁금하시다면 2번 링크를 클릭해서 확인해주시기 바랍니다.


오늘 알아볼 것은 바로 Generic(제너릭)으로 불리는 것이다.

 

제너릭을 사용하는 이유와 방법에 대해서는 알겠는데 실전에서 떤 클래스를 제너릭으로 지정해야 할지 모르겠거나 궁금하다면 이번 시리즈가 큰 도움이 되지 않을까 생각한다.

 

시작해보자.

Generic

제너릭은 Java 1.5 버전부터 추가되었으며 뭐 이런 이야기는 잠시 접어 두고 제너릭이 무엇이며 왜 제너릭을 사용해야 하는지를 알아야한다.



제너릭이란?

쉽게 타입을 파라미터로 가지는 클래스와 인터페이스이며 이름 뒤에 <> 가 붙는다. 라고 해두자.


의미론적으로는 더 다양하게 설명할 수 있지만 백문이 불여일견이라 한 번 보는 것이 백번의 설명보다 낫다.

 

제너릭의 장점

  • 컴파일 시에 강한 타입 체크를 할 수 있다.
  • Casting 제거한다.

컴파일 시에 강한 타입 체크를 할 수 있다.

자바 컴파일러는 우리가 코드를 작성하는 시점에 발생할 수 있는 문제점을 제기해준다.


예를 들어서

 

LinkedList<String> list = new LinkedList<>()라는 구문이 있으면 우리는 당연히

list라는 변수에는 String 타입만 들어갈 수 있다는 것을 알 수 있다.

 

왜냐? 바로 타입 파라미터가 String으로 지정되어 있기 때문이다.

 

하지만 제너릭 타입 파라미터에 벗어난 타입을 제공한다면 컴파일 시점에 다음과 같은 오류를 내뿜는다.

이런 식으로 컴파일 시점에 타입 체크를 강제화 할 수 있고 이 말은 자연스럽게 코드의 정확성이 높아진다는 장점이 된다.

Casting이 제거된다.

캐스팅은 우리가 형 변환을 위한 방법중 하나로 알고 있다.

 

예를 들어서 Box라는 클래스에 String을 set 해주고 get 메서드를 통해서 들어간 String을 출력하는 로직을 구현한다고 했을 때 우리는

class Box {

    private String data;

    void setData(String data) {
        this.data = data;
    }

    String getData() {
        return data;
    }
}

이렇게 구현할 수 있다.

 

하지만 Box라는 클래스에 Integer형 타입의 변수가 들어갈 수도 있고, Double 타입의 변수가 들어갈 수도 있다면 우리는 어떤 타입이 올지 모르니 Object 타입으로 받아서 Object 타입으로 반환시키면 사용할 때에 형변환 과정이 필수적으로 이루어져야 한다.

하지만 여기서 제너릭을 사용한다면

보는 것과 같이 형변환의 불필요한 오버헤드를 줄일 수 있어 전체 성능을 조금이나마 상승시킬 수 있게 된다.

 

이렇게 제너릭을 사용한다면 조금 더 우아한 코드를 생성할 수 있게 된다.

 

그럼 기본에 대해서는 알았고 이제 몇 가지의 더욱 우아한 방법의 제너릭을 알아보자.

두 개의 제네릭 타입을 사용하기

제네릭에서는 두 개 이상의 타입에 대해서 멀티 타입 파라미터를 사용할 수 있다.


이것도 말이 어렵지 실제로 여러분들이 사용하고 있던 것 일 수도 있다.

 

HashMap<String, Integer> map = new HashMap<>();

 

해시맵을 이용해서 Key: value 의 객체를 만들기 위해서 사용하는 바로 이 구조가 멀티 타입 파라미터의 대표적인 예 이다.

 

여기서 알아두면 언젠간 쓸모가 있는 지식이 있다면 자바 6버전 이전부터는 멀티 타입에 일일이 구체적 타입을 지정해줬었다.

 

바로 이렇게

 

HashMap<String, Integer> map = new HashMap<String, Integer>();

 

하지만 우리가 너무나도 싫어하는 코드의 중복이 발생하고 코드가 더려워질 수 있다는 이유로 자바 7부터는 다이아몬드 연산자를 이용해서 타입을 자동으로 유추할 수 있기 때문에 우리는 현재의 이 HashMap<String, Integer> map = new HashMap<>(); 우아한 코드를 사용할 수 있는 것이다.

제너릭 메서드

제너릭 메서드는 매개 타입과 리턴 타입으로 타입 파라미터를 갖는 메서드이다.

 

보통 클래스를 선언하지 않고 쓸 수 있는 static 메서드에 자주 쓰이곤 하는데 그도 그럴 것이,


클래스를 선언하지 않으면 제네릭을 사용할 해당 클래스가 어떤 클래스를 받을 수 있는지 직접 지정할 수 없기 때문에 메서드 단위에서 컴파일 체크를 해줄 수 있게 하기 위해 사용된다.

public class Pair<K, V> {
    private K key;
    private V value;

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public void setKey(K key) { this.key = key; }
    public void setValue(V value) { this.value = value; }
    public K getKey()   { return key; }
    public V getValue() { return value; }
}

public class Util {
    public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
        return p1.getKey().equals(p2.getKey()) &&
                p1.getValue().equals(p2.getValue());
    }
}
해당 소스코드는 빠른 설명을 위해 백중원 개발자님의 medium 블로그에서 가져왔습니다. 출처

백중원 개발자님이 작성하신 소스코드를 보자면 Util 클래스에서 static으로 compare이라는 메서드를 사용하기 위해서 반환 타입으로 <K, V>의 제네릭만 사용할 수 있도록 하였다.

 

보통 이런 경우의 사용 방법은

<제네릭 타입 파라미터> 반환 타입 메서드 이름 (매개변수) {...}

의 형태로 사용하곤 한다.


이렇게 오늘은 기본적인 제네릭을 사용하는 방법에 대해서 알아보았다.

다음시간에는 조금 더 깊게 들어가서

? 형태의 와일드 카드에 대해서도 알아보고 조금 이론적인 심화 내용을 한 번 다뤄보자.

오늘은 충분하다.

댓글1