개발은 아름다워

[ Java ] 제네릭은 무엇이며 왜 쓰는걸까? 본문

자바

[ Java ] 제네릭은 무엇이며 왜 쓰는걸까?

do_it_zero 2024. 10. 12. 11:28

제네릭의 핵심은 사용할 타입을 미리 결정하지 않는다는 점이다. 실제 사용하는 생성 시점에 타입을 결정하는 것이다.

제네릭은 대체 무엇이고 왜 쓰는걸까? 책으로 공부해도 사실 와닿지 않았다. 기다리고 기다리던 김영한님의 자바 중급-2 강의가 나와서, 강의를 들으며 공부한 내용을 정리할 예정이다. 자바 중급2의 첫번째 테마인 제네릭에 대해서 차근 차근 공부하며 내 것으로 만들어 가야겠다.

제네릭이 필요한 이유

일단 코드로 !

일단 코드로 살펴보자. 숫자와 문자열을 보관하고 꺼낼 수 있는 단순한 기능을 제공하는 Box를 만들어보자.

public class IntegerBox {

    private Integer value;

    public void setValue(Integer value) {
        this.value = value;
    }

    public Integer getValue() {
        return value;
    }
}


public class StringBox {

    private String value;

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }
}
public class BoxMain1 {
public static void main(String[] args) { 
		IntegerBox integerBox = new IntegerBox(); integerBox.set(10); //오토 박싱
		Integer integer = integerBox.get(); System.out.println("integer = " + integer);
        StringBox stringBox = new StringBox();
        stringBox.set("hello");
        String str = stringBox.get();
        System.out.println("str = " + str);
}

실행 결과

integer = 10
str = hello

위의 클래스를 보면 타입만 다르고 나머지는 똑같다. 다른 타입 박스를 추가로 만들 경우, 타입만 바꿔서 클래스를 만들면 된다. 그렇다면 만약 다양한 타입을 담는 박스를 만들어야한다면?? 각각의 타입별로 수십개의 클래스를 만들어야한다. 타입만 다를뿐 나머지 코드는 중복되는데 어떻게 해결하면 좋을까?

다형성을 통한 중복 해결 시도

타입만 다르게 하면 된다? -> 모든 타입을 받을 수 있는 Object를 쓰면 되겠다!
다형적 참조를 사용해서 중복 해결을 시도해보자!

public class ObjectBox {

   private Object value;

   public Object getValue() {
       return value;
   }

   public void setValue(Object value) {
       this.value = value;
   }
}
public class BoxMain2 {
    public static void main(String[] args) {
        ObjectBox integerBox = new ObjectBox();
        integerBox.setValue(10);
        Integer integer = (Integer) integerBox.getValue();
        System.out.println("integer : " + integer);

        ObjectBox stringBox = new ObjectBox();
        stringBox.setValue("hello");
        String str = (String) stringBox.getValue();
        System.out.println("str : "+ str);
    }
}

실행결과

integert : 100
str : hello

중복되는 코드는 그대로 두고 타입을 모든 타입의 부모인 Object를 사용함으로써 수십개의 클래스를 만들뻔한 문제를 해결하였다.
자 이제 상상을 해보자. 이 코드는 나만 쓰는게 아니라 다른 사람과 쓰는거라면? 코드 작성 후 먼훗날 다시 코드 작성을 할때 인수를 잘못 전달했다면?

interBox.set("문자열100");
// 이 때 "문자열100"을 넣어도 상관없다 왜냐하면 setValue시 인수로 Object value를 받기 때문이다.

수 -- 십 --- 줄

interBox니깐 이거는 당연히 Integer로 형변환 해야겠다 싶어서
Integer result = (Integer) integerBox.get(); 만들면 캐스팅 예외가 발생하는 것이다.

new Object();로 만들어진 객체를 매번 다운 캐스팅해야 하는 것도 추후에 문제가 발생할 수 있다. 왜냐하면 캐스팅한 타입이 맞지 않을 수 도 있기 때문이다.

즉, 타입 안정성에 대한 문제가 생겼다.

정리

다형성을 활용한 덕분에 코드의 중복을 제거하고, 기존 코드를 재사용할 수 있게 되었다. 하지만 입력할 때 실수로 원하지 않는 타입이 들어갈 수 있는 타입 안전성 문제가 발생한다.

코드의 중복을 제거하고 타입의 안정성을 갖추는 방법이 있지 않을까?

제네릭 적용

제네릭을 사용하면 코드 재사용과 타입 안정성이라는 두 마리 토끼를 한 번에 잡을 수 있다.

 public class GenericBox<T> {
     private T value;
     public void set(T value) {
         this.value = value;
	}
     public T get() {
         return value;
	}
}
  • <>를 사용한 클래스를 제네릭 클래스라 한다. <> 기호를 보통 다이아몬드라고 한다.
  • 제네릭 클래스를 사용할 때는 타입을 미리 결정하지 않는다.
  • 대신에 클래스명 오른쪽에 와 같이 선언하면 제네릭 클래스가 된다. 여기서 T를 타입 매개변수라 한다. 이 타입 매개변수는 이후에 String이나 참조 타입으로 변할 수 있다.
  • 제네릭 클래스 내부에 타입이 필요할 경우 T value와 같이 타입 매개변수를 적어두면 된다.
  • 제네릭은 생성하는 시점에 <>사이에 타입을 지정한다.
public class BoxMain3 {
    public static void main(String[] args) {
        // 타입 추론 : 생성하는 제네릭 타입은 생략 가능하다.
        GenericBox<Integer> integerBox = new GenericBox<>();
        integerBox.set(100);
        Integer integer = integerBox.get();
        System.out.println("integert : " + integer);

        GenericBox<String> stringBox = new GenericBox<>();
        stringBox.set("hello");
        String str = stringBox.get();
        System.out.println("str : " + str );
    }
}

타입 추론

GenericBox integerBox = new GenericBox() // 타입 직접 입력
GenericBox integerBox2 = new GenericBox<>() // 타입 추론
첫번째 줄의 코드를 보면 변수를 선언할 때와 객체를 생성할 때 가 두 번 나온다. 자바는 왼쪽에 있는 변수를 선언할 때의 를 보고 오른쪽에 잇는 객체를 생성할 때 필요한 타입의 정보를 얻는다. 따라서 두 번째 줄의 오른쪽 코드 new GenericBox<>()와 같이 타입 정보를 생략할 수 있다. 이렇게 자바가 스스로 타입 정보를 추론해서 개발자가 타입 정보를 생략할 수 있는 것을 타입 추론이라고 한다.
자바 컴파일러가 타입을 추론할 수 있는 상황에만 가능하다. 쉽게 이야기해서 읽을 수 있는 타입 정보가 주변에 있어여 추론할 수 있다.

정리

제네릭은 코드의 재사용성과 타입 안정성을 높이기 위해 쓰는 것이다.