ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Generics
    Computer Science/JAVA 2021. 8. 20. 22:38

    1️⃣ 지네릭스란?

    지네릭스는 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입 체크를 해주는 기능이다. 객체의 타입을 컴파일 시에 체크하기 때문에 객체의 타입 안정성을 높이고 형변환의 번거로움이 줄어든다.

    타입 안정성을 높인다는 건 의도하지 않은 타입의 객체가 저장되는 것을 막고, 저장된 객체를 꺼내올 때 원래의 타입과 다른 타입으로 잘못 형변환되어 발생할 수 있는 오류를 줄여준다는 뜻이다

    2️⃣ 지네릭 클래스의 선언

    class Box<T> {
        T item;
    
        void setItem(T item) { this.item = item; }
        T getItem() { return item; }
    }

    Box에서 T를 타입 변수라고 한다. 타입 변수는 T가 아닌 다른 것을 사용해도 된다. 타입 변수가 여러 개인 경우에는 Map<K, V>와 같이 콤마를 구분자로 나열하면 된다.

    이들은 기호의 종류만 다를 뿐 임의의 참조형 타입을 의미한다는 것은 모두 같다.

    지네릭스 용어

    class Box<T> {}
    • Box : 지네릭 클래스, T의 Box 또는 T Box라고 읽는다

    • T : 타입 변수 또는 타입 매개변수

    • Box : 원시 타입(raw type)

    지네릭스의 제한

    모든 객체에 대해 동일하게 동작해야 하는 static 멤버에 타입 변수 T를 사용할 수는 없다. T는 인스턴스 변수로 간주되기 때문. static 멤버는 타입 변수에 지정된 타입, 즉 대입된 타입의 종류에 관계없이 동일한 것이어야 한다. 또한 지네릭 배열 타입의 참조 변수를 선언하는 것은 가능하지만, new T[10]과 같이 배열을 생성하는 것은 안된다.

    3️⃣ 지네릭 클래스 객체 생성과 사용

    class Fruit                 { public String toString() { return "Fruit" ;}}
    class Apple extends Fruit   { public String toString() { return "Apple" ;}}
    class Grape extends Fruit   { public String toString() { return "Grape" ;}}
    class Toy                   { public String toString() { return "Toy"   ;}}
    
    public class FruitBoxEx1 {
        public static void main(String[] args) {
            Box<Fruit> fruitBox = new Box<Fruit>();
            Box<Apple> appleBox = new Box<Apple>();
            Box<Toy> toyBox = new Box<Toy>();
            // Box<Fruit> grapeBox = new Box<Grape>(); // Fruit와 Grape이 상속 관계일지라도 타입 에러
    
            fruitBox.add(new Fruit());
            fruitBox.add(new Apple()); // void add(Fruit item)이기 때문에 Apple 가능
        }
    }
    
    class Box<T> {
        ArrayList<T> list = new ArrayList<>();
        void add(T item) { list.add(item);}
        T get(int i) {return list.get(i);}
        int size() { return list.size(); }
        public String toString() { return list.toString(); }
    }

    4️⃣ 제한된 지네릭 클래스

    타입 문자로 사용할 타입을 명시하면 한 종류의 타입만 저장할 수 있도록 제한할 수 있지만, 그래도 여전히 모든 종류의 타입을 지정할 수 있다는 것에는 변함이 없음.

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

    class Fruit implements Eatable {
        public String toString() { return "Fruit"; }
    }
    
    interface Eatable { }
    
    class FruitBox<T extends Fruit & Eatable> extends Box<T> {}
    // Fruit를 상속 받고 Eatable 인터페이스를 구현한 클래스만 타입 변수로 올 수 있다.

    5️⃣ 와일드 카드

    지네릭 타입이 다른 것만으로는 오버로딩이 성립하지 않기 때문에 메서드 중복 정의가 되어버린다. 이럴 때 사용하기 위해 고안된 것이 바로 와일드 카드.

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

    • <? extends T> : 와일드 카드의 상한 제한. T와 그 자손들만 가능

    • <? super T> : 와일드 카드의 하한 제한. T와 그 조상들만 가능

    • <?> : 제한 없음. 모든 타입이 가능. <? extends Object>와 동일함

    class Fruit                 { public String toString() { return "Fruit" ;}}
    class Apple extends Fruit   { public String toString() { return "Apple" ;}}
    class Grape extends Fruit   { public String toString() { return "Grape" ;}}
    
    class Juice {
        String name;
    
        Juice(String name) {
            this.name = name + "Juice";
        }
    
        public String toString() {
            return name;
        }
    }
    
    class Juicer {
        static Juice makeJuice(Box<? extends Fruit> box) { // Fruit의 자손 클래스만 받겠다
            String tmp = "";
            for(Fruit f : box.getList()) {
                tmp += f + " ";
            }
            return new Juice(tmp);
        }
    }
    
    public class FruitBoxEx1 {
        public static void main(String[] args) {
            Box<Fruit> fruitBox = new FruitBox<Fruit>();
            Box<Apple> appleBox = new FruitBox<Apple>();
    
            fruitBox.add(new Apple());
            fruitBox.add(new Grape());
            fruitBox.add(new Fruit());
    
            appleBox.add(new Apple());
            appleBox.add(new Apple());
    
            System.out.println(Juicer.makeJuice(fruitBox));
            System.out.println(Juicer.makeJuice(appleBox));
        }
    }
    
    class Box<T> {
        ArrayList<T> list = new ArrayList<>();
        void add(T item) { list.add(item);}
        T get(int i) {return list.get(i);}
        ArrayList<T> getList() { return list; }
        int size() { return list.size(); }
        public String toString() { return list.toString(); }
    }
    
    class FruitBox<T extends Fruit> extends Box<T> {};

    6️⃣ 지네릭 메서드

    메서드의 선언부에 지네릭 타입이 선언된 메서드를 지네릭 메서드라고 함. 지네릭 타입의 선언 위치는 반환 타입 바로 앞이다.

    class FruitBox<T> {
        static <T> void sort(List<T> list, Comparator<? super T> c) {}
    }

    위의 코드에서 지네릭 클래스 FruitBox에 선언된 타입 매개변수 T와 지네릭 메서드 sort()에 선언된 타입 매개변수 T는 타입 문자만 같을 뿐 서로 다른 것이다.

    또한 static 멤버에는 타입 매개변수를 사용할 수 없지만, 이처럼 메서드에 지네릭 타입을 선언하고 사용하는 것은 가능하다. 이 타입 매개변수는 메서드 내에서만 지역적으로 사용될 것이므로 메서드가 static이건 아니건 상관이 없다.

    Uploaded by Notion2Tistory v1.1.0

    'Computer Science > JAVA' 카테고리의 다른 글

    애너테이션  (0) 2021.08.20
    열거형(enums)  (0) 2021.08.20
    String, StringBuilder, StringBuffer  (0) 2021.08.20
    Object 클래스  (0) 2021.08.20
    예외 처리  (0) 2021.08.12

    댓글

Designed by Tistory.