Java Generic - Object 다형성을 제네릭으로 개선하기

앞에서 작성한 코드를 살펴보자.

ObjBox.class

public class ObjBox {
    private Object value;

    public Object get() {
        return value;
    }

    public void set(Object value) {
        this.value = value;
    }
}

Main.class

public class Main {
    public static void main(String[] args) {
        ObjBox objBox = new ObjBox();
        objBox.set(100);
        Object integer = objBox.get();
        Integer ant = (Integer) integer;
        System.out.println("integer = " + ant);

        ObjBox stringBox = new ObjBox();
        stringBox.set("Hello Java");
        Object str = (String) stringBox.get();
        System.out.println("str = " + str);
    }
}

위 코드는 Object 클래스를 사용하여 코드 중복 없이 여러 타입을 받게하고 처리한 모습을 볼 수 있다. 이로 인해 코드 재사용을 줄였으나 타입 안전성이 떨어져 개발자 실수를 유발될 수 있다.


제네릭 적용하기

상단의 코드를 Generic 사용하여 타입 안전성을 개선해보도록 한다.

GenericBox.class

public class GenericBox<T> {
    private T value;

    public void set(T value) {
        this.value = value;
    }

    public T get() {
        return value;
    }
}
  • 다이아몬드 <>를 사용한 클래스는 제네릭 클래스이다.
  • 제네릭 클래스는 Integer, String 타입이 결정되지 않는다.
  • 다이아몬드 <T> 와 같이 선언하여 제네릭 클래스로 된다. T는 타입의 파라미터이고 이 파라미터는 Integer, String 으로 컴파일에서 변할 수 있다.
  • 타입 사용에는 다이아몬드로 선언한 파라미터 T를 붙이도록한다.

Main.class

public class Main {
    public static void main(String[] args) {
        GenericBox<Integer> integerVal = new GenericBox<Integer>();
        integerVal.set(100);
        System.out.println(integerVal.get());

        GenericBox<String> strVal = new GenericBox<>();
        strVal.set("Hello");
        System.out.println(strVal.get());
    }
}

100
Hello

오타로 개발자 실수가 발생한 경우

Integer만 받도록 하였는데 set 메소드로 실수로 문자열을 기입하면 다음과 같이 오류가 발생되어 실행할 수 없도록한다.

이러한 특성으로 타입 안전성이 증가한 코드라고 볼 수 있다.

제네릭 클래스 타입 선언

class GenericBox<T>

다음과 같이 <> 다이아몬드 안에 타입 매개변수를 정의한다.

제네릭 클래스 생성

new GenericBox<Integer>()

제네릭 new 키워드 생성 단계에서 다이아몬드<> 타입을 지정하거나 변수에 이미 타입을 선언하였다면 생략할 수 있다.

제네릭 클래스로 원하는 타입이 사용 가능

new GenericBox<Integer>()
new GenericBox<Boolean>()
new GenericBox<String>()
new GenericBox<NewClass>()

제네릭 생성 시점에는 타입을 지정할 수 있다.
제네릭을 도입하더라도 <String>, <Integer> 선언해서 코드가 임의의 장소에서 실제로 만들어지지 않는다. 자바 컴파일러가 입력한 타입 정보를 기반해서 이런 코드를 가정하고 컴파일 과정 타입 정보를 반영한 것이다. 타입이 맞지 않는다면 앞서 본것처럼 오류를 발생시킨다.

타입추론

GenericBox<Integer> int1 = new GenericBox<Integer>() // 명시적으로 타입 지정
GenericBox<Integer> int2 = new GenericBox<>() // 타입추론으로 생략

자바 컴파일러가 타입이 뭐가 들어가는지 변수에서 타입을 알아내고 제네릭으로 생성한다. 그래서 new 생성 코드에서 다이아몬드가 생략이 가능하다. 이렇게 자바가 스스로 타입 정보를 추론하는 것을 타입 추론이라 한다.
타입 추론은 자바 컴파일러가 타입을 추론할 수 있는 상황에서만 가능하다. 읽을 수 있는 타입 정보의 힌트를 주어줘야 한다.


정리

타입 추론을 사용해서 코드 재사용과 타입 안전성을 잡을 수 있어 고품질 코드를 생성할 수 있게 되었다.