3 분 소요

지네릭스란

클래스나 메서드에서 다룰 타입을 미리 명시해주어 번거로운 형 변환을 줄여주는 기능입니다. 지네릭스를 사용하지 않고 Object를 사용한다면 타입 안정성이 낮아지고 객체를 꺼낼 때 마다 타입을 체크해줘야 합니다. 지네릭스를 사용한다면 명시한 타입만 저장할 수 있기 때문에 타입 안정성이 높아지고 별도의 타입 체크를 하지 않아도 됩니다.

// 지네릭스 사용x
public class Box {

    private Object[] items;

    public Box(Object[] items) {
        this.items = items;
    }

    public Object[] getItems() {
        return items;
    }

    public static void main(String[] args) {
        Apple apple1 = new Apple("apple1", 10);
        Apple apple2 = new Apple("apple1", 10);

        Toy toy1 = new Toy();
        Toy toy2 = new Toy();

        Object[] items = {apple1, apple2, toy1, toy2}; // Fruit를 상속한 것만 Box에 넣어줘야 하는데 Toy도 넣음.
        Box box = new Box(items);

        for (Object item : box.getItems()) {
            if (item instanceof Fruit) { // 위와 같은 실수를 방지하기 위해 값을 넣을 때와 뺄 때 타입 체크를 해줘야 함.
                System.out.println(item.toString());
            }
        }
    }
}
// 지네릭스 사용o
public class Box<T> {

    private T[] items;

    public Box(T[] items) {
        this.items = items;
    }

    public T[] getItems() {
        return items;
    }

    public void setItems(T[] items) {
        this.items = items;
    }

    public static void main(String[] args) {
        Apple apple1 = new Apple("apple1", 10);
        Apple apple2 = new Apple("apple1", 10);

        Toy toy1 = new Toy();
        Toy toy2 = new Toy();

        // Object[]로 타입을 설정한다면 컴파일 시점에서 에러를 잡을 수 있다.(안정성이 높아짐)
        Apple[] items = {apple1, apple2};
        Box<Apple> box = new Box<>(items);

        for (Apple item : box.getItems()) {
            System.out.println(item);
        }
    }
}

제한된 지네릭 클래스

지네릭 타입에 extends를 사용하면, 특정 타입의 자손들만 대입할 수 있게 제한할 수 있습니다.

public interface Eatable {
}
public class FruitBox<T extends Fruit & Eatable> extends Box<T> {

}
public class Fruit implements Eatable{

    private String name;
    private int weight;

    public Fruit(String name, int weight) {
        this.name = name;
        this.weight = weight;
    }

    @Override
    public String toString() {
        return "Fruit";
    }
}

public class Apple extends Fruit {

    public Apple(String name, int weight) {
        super(name, weight);
    }

    @Override
    public String toString() {
        return "Apple";
    }
}
public class FruitBoxEx2 {
    public static void main(String[] args) {
        FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
        FruitBox<Apple> appleBox = new FruitBox<Apple>();
//        FruitBox<Toy> toyBox = new FruitBox<Toy>(); // Error. Type parameter 'javaPractice.Toy' is not within its bound; should extend 'javaPractice.Fruit'
    }
}

와일드 카드

와일드 카드는 기호 ‘?’로 표현하는데, 어떠한 타입도 될 수 있습니다.

<?> : 제한이 없어 <? extends Object> 같습니다.
<? extends T> : T와 T의 자손들만 가능합니다.
<? super T> : T와 T의 조상들만 가능합니다.
public class Juicer {
    static Juice makeJuice(FruitBox<? extends Fruit> box) {
        String tmp = "";
        for (Fruit fruit : box.getList()) {
            tmp += fruit + " ";
        }

        return new Juice(tmp);
    }
}
public class Juice {

    private String name;

    public Juice(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return this.name;
    }
}
public class FruitBoxEx3 {

    public static void main(String[] args) {
        FruitBox<Fruit> fruitBox = new FruitBox<>();
        FruitBox<Apple> appleFruitBox = new FruitBox<>();

        fruitBox.add(new Apple("apple1", 10));

        appleFruitBox.add(new Apple("apple2", 20));
        appleFruitBox.add(new Apple("apple3", 30));

        System.out.println(Juicer.makeJuice(fruitBox)); // apple1
        System.out.println(Juicer.makeJuice(appleFruitBox)); // apple1, apple2
    }
}

지네릭 메서드

지네릭 메서드는 메서드의 선언부에 지네릭 타입이 선언된 메서드입니다. 주의할 점은 지네릭 클래스에 정의된 타입 매개변수와 지네릭 메서드에 정의된 타입 매개변수는 전혀 다른 별개입니다. 따라서 같은 T를 사용하더라도 같은 것이 아닙니다.

public class Juicer {
    static <T extends Fruit> Juice makeJuice(FruitBox<T> box) {
        String tmp = "";
        for (Fruit fruit : box.getList()) {
            tmp += fruit + " ";
        }

        return new Juice(tmp);
    }
}
public class FruitBoxEx4 {

    public static void main(String[] args) {
        FruitBox<Fruit> fruitBox = new FruitBox<>();
        FruitBox<Apple> appleFruitBox = new FruitBox<>();

        fruitBox.add(new Apple("apple1", 10));

        appleFruitBox.add(new Apple("apple2", 20));
        appleFruitBox.add(new Apple("apple3", 30));

        System.out.println(Juicer.<Fruit>makeJuice(fruitBox)); // <Fruit> 생략 가능 (컴파일러가 추정)
        System.out.println(Juicer.<Apple>makeJuice(appleFruitBox)); // <Apple> 생략 가능 (컴파일러가 추정)
    }
}

마치며

지네릭과 지네릭 메서드에 대해서 간략하게 알아봤습니다. 프로젝트를 진행하면서 지네릭스를 사용해 본 적이 없고 다른 선배님이 작성한 코드만 봤습니다. 코드를 직접 쳐보니 되게 낯설었지만 칠수록 할만하다고 느껴졌습니다.

다음은 상속과 다형성에 대해서 알아보도록 하겠습니다.

카테고리:

업데이트: