Hyunebee

제네릭스(Generics) 본문

Java/Java의 정석

제네릭스(Generics)

Hyunebee 2022. 3. 9. 18:55

Generics 

다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일시 타입체크를 해주는 기능이다.

컴파일 시에 체크하기 때문에 객체의 타입 안전성을 높이고 형변환의 번거로움이 줄어든다. 

 

Generics은 클래스와 메서드에 선언할 수 있다. 

 

ex) 클래스

class init{
    Object item;
    void setItem(Object item){
        this.item = item;
    }
    
    Object getItem() {
        return item;
    }
}
class init<T>{ // T는 타입변수
    T item;
    void setItem(T item){
        this.item = item;
    }

    T getItem() {
        return item;
    }
}

아래 처럼 변경이 가능하다. T는 타입변수라고 하며 T이외에 다른 문자를 사용해도 상관은 없다. ArrayList<E>와 같은 맥락이라고 볼 수 있다.  이 둘은 기호의 종류만 다를 뿐 임의의 참조형 타입을 의미하는것은 모두 같다. 

 

Generics 용어

class Box<T>

Box<T> : 제네릭 클래스

Box : 원시타입

T : 타입변수

 

Generics의 제한

static멤버에 타입 변수 T를 사용할 수 없다. T또한 인스턴스변수로 간주되기 때문이다.

static멤버는 타입 변수에 지정된 타입, 즉 대입된 타입의 종류에 관계없이 동일한 것이여야한다.

Box<smallBox>.instance == Box<bigBox>.instance여야 하는것 이다.

 

또한 Generics 타입으로 배열을 생성할 수 없다. new T[] 등

이유는 new 연산자는 컴파일 시점에 타입 T가 뭔지 정확히 알아야한다. 하지만 컴파일시에 T를 정확하게 알수는 없다. 같은 이유로 instanceof또한 사용할 수 없다. 

꼭 Generics 타입의 배열을 생성해야한다면 newInstance()와 같이 동적으로 개체를 생성하는 메서드로 배열을 생성하면 된다. 

 

Generics 예제

Generics 제한

FruitBox<Toy>   toyBox    = new FruitBox<Toy>();

사용할 타입을 명시해서 지정한 타입만 받을 수 있다. 하지만 모든 종류에 타입을 지정가능 함으로 문맥상 이상하게 보일 수 있을것 이다. 위에도 코드를 보면 과일박스에 장난감이 들어있다.  이때 Generics는 extends를 통해 매개변수의 타입을 제한할 수 있다. 

import java.util.ArrayList;

class Fruit implements Eatable {
    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"  ;}}

interface Eatable {}

class FruitBoxEx2 {
    public static void main(String[] args) {
        FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
        FruitBox<Apple> appleBox = new FruitBox<Apple>();
        FruitBox<Grape> grapeBox = new FruitBox<Grape>();
//    FruitBox<Grape> grapeBox = new FruitBox<Apple>(); // 에러. 타입 불일치
//    FruitBox<Toy>   toyBox    = new FruitBox<Toy>();   // 에러.

        fruitBox.add(new Fruit());
        fruitBox.add(new Apple());
        fruitBox.add(new Grape());
        appleBox.add(new Apple());
//    appleBox.add(new Grape());  // 에러. Grape는 Apple의 자손이 아님
        grapeBox.add(new Grape());

        System.out.println("fruitBox-"+fruitBox);
        System.out.println("appleBox-"+appleBox);
        System.out.println("grapeBox-"+grapeBox);
    }  // main
}

class FruitBox<T extends Fruit & Eatable> extends Box<T> {}// Fruit의 자식이면서 Eatable을 구현해야 한다면 &사용

class Box<T> {
    ArrayList<T> list = new ArrayList<T>();
    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();}
}

위의 코드를 보면 T는 Fruit을 상속받고 Fruit은 Eatable을 구현하고 있다. 그렇기 때문에 FruitBox에 Toy가 매개변수로 사용되지 못하게 된것이다. 

 

와일드카드

와일드카드는 매개변수의 타입을 제한하기 위해서 사용한다. 사용 방법은 다양하다.

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

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

-<?> : 제한 없음. 모든 타입이 가능하다. 

 

ex) 

import java.util.*;

class Fruit    {
    String name;
    int weight;

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

    public String toString() { return name+"("+weight+")";}

}

class Apple extends Fruit {
    Apple(String name, int weight) {
        super(name, weight);
    }
}

class Grape extends Fruit {
    Grape(String name, int weight) {
        super(name, weight);
    }
}

class AppleComp implements Comparator<Apple> {
    public int compare(Apple t1, Apple t2) {
        return t2.weight - t1.weight;
    }
}

class GrapeComp implements Comparator<Grape> {
    public int compare(Grape t1, Grape t2) {
        return t2.weight - t1.weight;
    }
}

class FruitComp implements Comparator<Fruit> {
    public int compare(Fruit t1, Fruit t2) {
        return t1.weight - t2.weight;
    }
}

class FruitBoxEx4 {
    public static void main(String[] args) {
        FruitBox<Apple> appleBox = new FruitBox<Apple>();
        FruitBox<Grape> grapeBox = new FruitBox<Grape>();

        appleBox.add(new Apple("GreenApple", 300));
        appleBox.add(new Apple("GreenApple", 100));
        appleBox.add(new Apple("GreenApple", 200));

        grapeBox.add(new Grape("GreenGrape", 400));
        grapeBox.add(new Grape("GreenGrape", 300));
        grapeBox.add(new Grape("GreenGrape", 200));

        Collections.sort(appleBox.getList(), new AppleComp());
        Collections.sort(grapeBox.getList(), new GrapeComp());
        System.out.println(appleBox);
        System.out.println(grapeBox);
        System.out.println();
        Collections.sort(appleBox.getList(), new FruitComp());
        Collections.sort(grapeBox.getList(), new FruitComp());
        System.out.println(appleBox);
        System.out.println(grapeBox);
    }  // main
}

class FruitBox<T extends Fruit> extends Box<T> {}

class Box<T> {
    ArrayList<T> list = new ArrayList<T>();

    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();
    }
}

위의 코드를 보면 

Collections의 sort를 통해서 정렬하구 있다. 

Collections의 sort 메소드의 Api 문서에 들어가보면

public static <T> void sort(List<T> list, Comparator<? super T> c) {
    list.sort(c);
}

 

 

 위의 코드대로 대입을 해보자

Collections.sort(appleBox.getList(), new AppleComp());

 

public static void sort(List<Apple> list, Comparator<? super Apple> c)와 동일하다.

 

우리는 Comparator로 Apple의 상위인 Apple, Fruit, Object 3가지 타입을 사용할 수 있게 되는것이다. 

사실 Apple과 Grape 둘다 같은 코드임으로 하나로 통합된

Collections.sort(appleBox.getList(), new FruitComp());

FruitComp가 더욱 객체지향적인 코드라 볼 수 있다. 

'Java > Java의 정석' 카테고리의 다른 글

Thread  (0) 2022.03.13
Enum  (0) 2022.03.12
Set, Map  (0) 2022.03.08
Arrays  (0) 2022.03.07
Stack, Queue  (0) 2022.03.07