티스토리 뷰

JAVA

[자바 스터디] 12주차 : 제네릭

짜비 2022. 3. 20. 07:33

제네릭

  • 다양한 타입의 객체를 다루는 메서드나 클래스에 컴파일 시의 타입체크를 해주는 기능
  • 컴파일러가 타입체크 해주기 때문에 type-safe한 프로그래밍 가능
    • e.g. List<String> list = new ArrayList(); 로 선언하면 list에 integer가 들어가는 코드를 방지할 수 있음.
  • 객체의 타입을 미리 명시해줌으로써 번거로운 형변환을 생략할 수 있음
    • 제네릭 이전에는 다양한 종류의 타입을 다루는 메소드의 매개변수, 리턴타입으로 Object 타입의 참조변수를 사용한 뒤 형변환하는 방식을 활용.

//제네릭 이전
class Box {
    Object item;

    void setItem(Object item) {
        this.item = item;
    }
    public Object getItem() {
        return item;
    }
}

//제네릭 사용
class Box<T> {
    T item;

    void setItem(T item) {
        this.item = item;
    }
    public T getItem() {
        return item;
    }
}

Box<String> b = new Box<String>();
String item = b.getItem(); //형변환이 필요 없음

제네릭 용어

class Box<T> {}

  • Box : 제네릭 클래스
  • T : 타입변수 or 타입매개변수
  • Box : 원시타입 (raw type)
  • T 에 String 을 대입한 경우, String : 매개변수화된 타입(parameterized type)
class Apple extends Fruit {
    @Override
    public String toString() {
        return "Apple{}";
    }
}

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

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

public class FruitBotEx1 {

    public static void main(String[] args) {
        Box<Fruit> fruitBox = new Box<>();
        Box<Apple> appleBox = new Box<>();
        Box<Toy> toyBox = new Box<>();

        fruitBox.add(new Fruit());
        fruitBox.add(new Apple()); // OK 다형성
        fruitBox.add(new Grape());

        appleBox.add(new Apple());

        //appleBox.add(new Toy()); 컴파일 에러

        System.out.println("fruitBox = " + fruitBox);
        System.out.println("appleBox = " + appleBox);
        System.out.println("toyBox = " + toyBox);
    }
}

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

    ArrayList<T> getList() {
        return list;
    }

    @Override
    public String toString() {
        return list.toString();
    }
}

제네릭 주의사항

  • static 멤버에 타입변수를 사용할 수 없음
    class Box<T> {
      static T item;    //에러
      static int compare(T t1, T t2) {} //에러
    }
    
- static 멤버는 타입변수에 지정된 타입의 종류와 관계없이 늘 동일해야 하기 때문. JVM 메모리에 static 멤버 정보는 오직 하나만 올라간다. 따라서 Box<Apple>.item , Box<Grape>.item 과 같이 서로 다른 정보는 static 영역에 올릴 수 없음.
- 달리 설명하면, 제네릭 클래스의 타입변수는 객체를 생성할 때 결정되기 때문에 static 멤버가 만들어지는 시점에는 타입변수에 어떤 클래스가 올지 알 방법이 없음. 그래서 static 멤버에는 타입매개변수를 사용할 수 없음


## 제네릭 주요 개념 (바운디드 타입, 와일드 카드)

### 바운디드 타입
- 타입 매개변수 T에 지정할 수 있는 타입의 종류를 제한
```java
// Fruit의 자손만 매개변수타입으로 지정 가능  
class FruitBox<T extends Fruit> {
    ArrayList<T> list = new ArrayList<T>();
    //...
}

FruitBox<Apple> appleBox = new FruitBox<Apple>();    //OK
FruitBox<Toy> toyBox = new FruitBox<Toy>();    // 에러
  • 만약 매개변수타입을 특정 인터페이스를 구현한 클래스로 제한하고 싶다면 이때도 ‘extends’ 를 사용한다. ‘implements’를 사용하지 않는다는 것을 주의.
interface Eatable {}
class FruitBox<T extends Eatable> { … }

//Fruit의 자손이면서 Eatable 인터페이스를 구현해야 하는 경우
class FruitBox<T extends Fruit & Eatable> { … }

와일드 카드

  • <?>
    • 와일드카드
    • 어떤 타입이든 될 수 있음. Object 타입과 다를 게 없음.
  • <? extends T>
    • T와 그 자손들만 가능
  • <? super T>
    • T와 그 조상들만 가능
    • e.g. static <T> void sort(List<T> list, Comparator<? super T> c)

class Juice {
    String name;

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

    @Override
    public String toString() {
        return name;
    }
}

class Juicer {
    static Juice makeJuice(FruitBox<? extends Fruit> box) {
        String tmp = "";

        for (Fruit fruit : box.getList()) {
            tmp += fruit + " ";
        }
        return new Juice(tmp);
    }
}

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

        fruitBox.add(new Apple());
        fruitBox.add(new Grape());
        appleBox.add(new Apple());
        appleBox.add(new Apple());

        System.out.println(Juicer.makeJuice(fruitBox));
        System.out.println(Juicer.makeJuice(appleBox));
    }
}

제네릭 메소드 만들기

  • 메서드 선언부에 제네릭 타입이 선언된 메서드

  • 제네릭 클래스에 정의된 타입매개변수와 제네릭 메서드에 정의된 타입매개변수는 별개의 것. 같은 타입문자 T를 사용하더라도 같은 것이 아님

    • 메서드에 선언한 제네릭 타입은 지역 변수를 선언한 것과 같다고 생각하자
  • e.g.

  • 제네릭 메소드 사용 전

    • static Juice makeJuice(FruitBox<? extends Fruit> box)
  • 제네릭 메소드 사용 후

    • static <T extends Fruit> Juice makeJuice(FruitBox<T> box)
  • 제네릭 메소드 호출할 때는 타입변수에 타입을 대입해야 한다.

  • e.g.

    • System.out.println(Juicer.<Apple>makeJuice(appleBox));
  • 그러나 대부분의 경우 컴파일러가 타입을 추정할 수 있기 때문에 타입을 생략.

    • System.out.println(Juicer.makeJuice(appleBox));
  • (주의) 제네릭 메소드에 대입된 타입이 있을 경우, 같은 클래스 내에 있는 멤버에서 제네릭 메소드를 호출할 때 this., 클래스이름. 을 생략할 수 없음

    • System.out.println(<Fruit>makeJuice(appleBox));: 컴파일에러
    • System.out.println(this.<Fruit>makeJuice(appleBox));: OK
    • System.out.println(Juicer.<Fruit>makeJuice(appleBox));: OK

public static void printAll(ArrayList<? extends Fruit> list, ArrayList<? extends Fruit> list2) {
    for (Fruit fruit : list) {
        System.out.println("fruit = " + fruit);
    }
}

//제네릭 메소드를 이용해서 매개변수 타입 간소화
public static <T extends  Fruit> void printAll(ArrayList<T> list, ArrayList<T> list2) {
    for (Fruit fruit : list) {
        System.out.println("fruit = " + fruit);
    }
}

Erasure(소거) : 제네릭 타입의 제거

  • 컴파일러는 제네릭 타입을 이용해서 소스파일을 체크한 뒤 필요한 곳에 형변환을 넣어준다. 그리고 제네릭 타입을 제거한다. 따라서 클래스 파일에는 제네릭 타입에 대한 정보가 없다.
  • 이유 : 제네릭을 지원하지 않는 자바 하위 버전과의 호환성을 위해.

기선님 리뷰 영상

  • GenericDao : spring JPA 에서 implements Repository<K,V> 을 붙이는 이유
    • 직접 GenericDao 만들어보면 좋을 듯.
  • 브릿지 메소드
  • 런타임 시 대입된 제네릭 클래스 타입을 알 수 있다! (Erasure 되지만 메타데이터는 남는다.)
    • Class<E> parameterizedType = (Class<E>)((ParameterizedType)this.getClass().getGenericSuperClass()).getActualTypeArguments()[0];

와일드카드 심화

  • wildCard를 사용하는 케이스
    • 타입매개변수에 어떤 클래스가 오든지 상관 없어. 관심 없어.
    • e.g. List<?> 를 정의하는 것은, list에 정의된 size() 와 같이 element 타입과는 독립적인 메서드만 사용할 것임을 의미. 이때는 add() 와 같이 element 타입에 종속적인 메서드는 사용할 수 없다.
    • Collections 의 메서드를 보면 wildCard를 쓰는 경우, 타입매개변수를 쓰는 경우가 혼재되어 있음.
public class Generics2 {
    public static void main(String[] args) {
        List<Integer> integerList = new ArrayList<>();
        List<Number> numberList;
        numberList = integerList;    //컴파일 에러

        List<? extends Number> wildCardNumberList;
        wildCardNumberList = integerList;    //OK

        ArrayList<Integer> integerArrayList = new ArrayList<>();
        List<Integer> integerList1;
        integerList1 = integerArrayList;
    }
}

References

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함