본문 바로가기

STUDY/이펙티브자바

5-1) 제네릭 클래스의 raw type은 사용하지 마라

제네릭 클래스

 

public class Printer<T> {
    private T source;

    public Printer(T source) {
        this.source = source;
    }
    
    public void setSource(T source) {
        this.source = source;
    }

    public void print() {
        System.out.println(source);
    }
}

// 제네릭 클래스 덕분에 String, Integer 타입의 클래스를 따로 만들지 않고 사용할 수 있다.
Printer<String> strPrinter = new Printer<String>("hello");
strPrinter.print();
Printer<Integer> intPrinter = new Printer<Integer>(100);
intPrinter.print();

제네릭 클래스는 클래스의 멤버 변수나 메소드 파라미터의 타입을 고정하지 않고 사용할수 있는 클래스이다. 제네릭 클래스를 사용하면 동일 기능을 하지만 변수의 타입만 다른 클래스들을 여러개 만들지않아도 된다. 위의 소스를 보면 객체를 받아서 출력하는 Printer 클래스를 제네릭으로 만들고, 각각 String, Integer를 타입매개변수로 받아서 Printer 객체를 만들었다. 

 

 


raw type

 

// String을 타입 매개변수로 Printer 객체 생성
Printer<String> strPrinter = new Printer<String>("hello");
// String이 타입 매개변수이기 때문에 setSource에 int값을 넘기면 컴파일 에러 발생
strPrinter.setSource(100);

 

// 타입 매개변수 없이 raw type으로 Printer 객체 생성 
Printer rawPrinter = new Printer("hello");
// 컴파일 에러 없음
rawPrinter.setSource(1);

제네릭 클래스는 타입 매개변수를 선언하면서 사용된다. 그렇게 되면 해당 제네릭 클래스의 객체에서는 선언된 타입을 기준으로 동작하고 컴파일 에러를 발생시킨다. 하지만 제네릭 클래스를 타입 매개변수 없이 사용할 수 있는데, 이를 raw type이라고 한다. 제네릭 클래스는 유연성을 제공하면서도 타입 매개변수 선언 이후로는 객체에서 사용할 타입을 고정시켜서 안정성과 가독성을 제공한다. 하지만 raw 타입은 제네릭 클래스의 안정성과 가독성을 모두 없애버린다. Printer 클래스를 String 타입 매개변수로 객체 생성하면, String 외의 다른 타입이 사용되었을 때 컴파일 에러를 발생시킨다. 하지만 타입 매개변수 없이 raw type으로 Printer 객체를 생성하면 컴파일 에러가 발생하지 않아서 런타임에 문제가 발생하기 쉽다. 만약 모든 객체를 허용하는게 목적이라면 Printer<Object>로 선언해서 명확하게 표시하는것이 좋다.

 

 


와일드카드 타입

 

public class PrinterUtil {
    public static void printTwice(Printer<?> printer) {
        printer.print();
        printer.print();
    }
}

Printer 클래스의 객체를 파라미터로 받아서 print 동작을 두번 수행하는 printTwice 메소드가 있다. printTwice 메소드에서는 Printer 객체가 어떤 타입 매개변수로 만들어졌는지 알수 없고, 알고싶지도 않다. 이럴때 타입 매개변수를 와일드카드(?)로 전달하면 알 수 없고, 알고싶지도 않다는 의도가 명확해진다.

 

public class PrinterUtil {
    public static void printTwice(Printer printer) {
        printer.setSource(100);
        printer.print();
        printer.print();
    }
}

public class PrinterUtil {
    public static void printTwice(Printer<?> printer) {
        // 컴파일 에러 발생
        printer.setSource(100);
        printer.print();
        printer.print();
    }
}

와일드 카드 타입이 raw type과 다른 점은 의도가 명확하기 때문에 잘못된 코드를 작성하면 컴파일 에러가 발생한다는 점이다. raw type은 어떠한 의도도 나타내지 않았기 때문에 setSouce로 어떤 타입의 객체를 전달해도 컴파일 에러가 발생하지 않는다. 그리고 이는 런타임에 문제가 발생하기 쉽다. 하지만 와일드 카드로 명확하게 알 수 없고, 알고싶지도 않다고 표현하면 명확한 타입이 필요한 동작에서 컴파일 에러가 발생한다. 이는 컴파일 단계에서 위험한 코드를 차단하기 때문에 런타임에서의 문제 발생 가능성을 낮춰준다.

728x90