Generics Class

코코·2020년 8월 1일
0

Java

목록 보기
19/25
post-thumbnail

자바의 정석을 참고했습니다.

Generics

컬렉션 클래스에 컴파일 시 타입 체크를 해주는 기능.

장점

  • 타입 안정성을 높여서, 의도하지 않은 타입의 객체가 저장되는 것을 막는다.

지네릭 클래스

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

Box<T>에서 T를 타입 변수라고 한다. 타입변수는 꼭 T가 아니라도 상황에 맞는 다른 알파벳을 지정할 수 있다.

위에서 정의한 제네릭 클래스 Box의 객체를 생성한다고 했을 때, <T>가 무슨 타입인지 지정해야 한다.

Box<String> box = new Box<String>();

new Box<String>(); 이 부분은 당연히 String이 될 것이므로 new Box<>()로 적어도 된다.

용어

class Box<T> { }
  • Box<T>
    제네릭 클래스 T의 Box, T Box
  • <T>
    타입 변수, 타입 매개변수
  • Box
    원시타입raw type

지정한 타입을 매개변수화된 타입parameterized type이라고 한다.

제한

  • static멤버에 타입 변수를 사용할 수 없다.
static T item;	// 에러

타입변수는 인스턴스 멤버로 간주하기 때문이다.
static변수는 매개변수화된 타입에 상관없이 동일한 것이어야 한다.

  • 지네릭 타입 배열 생성 불가
T[] itemArr;	//T타입 배열을 위한 참조변수
...
T[] toArray() {
	T[] tmpArr = new T[itemArr.length];	//Error!
    return tmpArr
}

선언T[] itemArr할 수 있지만 생성new할 수는 없다.
바로 'new'연산자 때문이다. new는 컴파일 시점에 타입 T가 무엇인지 정확히 알아야 한다. 그런데 지네릭 클래스는 컴파일 시점에 T가 어떤 타입이 될 것인지 알 수 없다.

지네릭 클래스 사용

지네릭 클래스 Box<T>를 사용하는 예제 코드다.
자세한 것은 주석에.

package com.javaex.generics;

import java.util.ArrayList;
import java.util.List;

public class FruitsBoxEx1 {
    public static void main(String[] args) {
        //매개변수화된 타입으로, 어떤 타입이든 올 수 있다.
        Box<Fruit> fruitBox = new Box<>();  // == new Box<Fruit>();
        Box<Apple> appleBox = new Box<>();
        Box<Toy> toyBox = new Box<>();
        /*
        *Box<Fruit> box = new Box<Apple>(); 은 매개변수화된 타입이 다르므로 에러가 발생한다.
        *반드시 참조 변수와 생성자의 매개변수화된 타입이 일치해야만 한다. 
        *다형성은
        * Box<Apple> = new FruitBox<Apple>();
        * 이렇게 사용할 수 있다.
        * */

        //void T add(T item) -> void add(Fruit item)
        fruitBox.add(new Fruit());
        fruitBox.add(new Grape());
        fruitBox.add(new Apple());

        appleBox.add(new Apple());
//        appleBox.add(new Graple());   //사과 박스엔 사과만 담을 수 있다.

        toyBox.add(new Toy());

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

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

    void add(T item) {
        list.add(item);
    }

    T getItem(int i) {
        return list.get(i);
    }

    int size() {
        return list.size();
    }

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


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

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";
    }
}

제한된 지네릭 클래스

지네릭은 타입 안정성을 보장한다고 했다. <T>타입으로 무슨 타입이든 받는 것은 과연 안전할까? 좀더 안전하게 <T>에 지정할 수 있는 타입을 제한할 수 있는 방법이 있다.

class Box<T extends Fruit>

간단하다. 지네릭 타입에 'extends'를 사용하면 된다. 구태여 설명할 것도 없겠지만, 매개변수화된 타입으로 Fruuit을 상속하는 클래스만 받겠다는 뜻이다. 참고로 인터페이스를 구현한 클래스를 매개변수화된 타입으로 받겠다고 선언할 때도 'extends'를 사용한다.

package com.javaex.generics;

import java.util.ArrayList;
import java.util.List;

public class FruitsBoxEx2 {
    public static void main(String[] args) {
        FruitBox<Fruit> fruitBox = new FruitBox<>();
        FruitBox<Apple> appleBox = new FruitBox<>();
        FruitBox<Grape> grapeBox = new FruitBox<>();
        ToyBox<Toy> toyBox = new ToyBox<>();

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

        appleBox.add(new Apple());

        grapeBox.add(new Grape());

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

}
//Eatable을 구현하면서, Fruit를 상속하는 타입만 <T>로 받는다.
class FruitBox<T extends Fruit & Eatable> extends Box<T> {}
//Funable을 구현한 타입만 <T>로 받는다.
class ToyBox<T extends Funable> {}

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

    void add(T item) {
        list.add(item);
    }

    T getItem(int i) {
        return list.get(i);
    }

    int size() {
        return list.size();
    }

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

interface Eatable {}
interface Funable {}

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

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

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

간단하게 조금 설명하자면.

class Box<T>

이 클래스는 세상 모든 박스의 원형이다. 말하자면 나로써는 감히 상상만 해볼 수밖에 없는 Box의 이데아! 우리는 이 박스를 이용해서 세상에 존재하는 모든 물건을 담을 수도 있을 것이다. 그러면 아마 잡동사니 박스가 되겠지. 그것을 도저히 견딜 수 없는 인간. 그러니까 너무나 게을러서 이 박스에 뭐가 들었는지 하나하나 찾아보기가 너무나 귀찮은 인간은 분류라는 것을 하기로 했다.

박스를 여러 개로 나눈 다음, 1번 박스에는 먹을 수 있는 과일만 담기로 하고, 2번 박스에는 장난감만 담기로 한다.
코드로 이렇겠지.

class FruitBox<T extends Fruit & Eatable> extends Box<T> {}
class ToyBox<T extends Funable> {}

이렇게 하면 언제든지 1번 박스에는 과일만, 2번 박스에는 장난감만 있을 것이다. 어쩌다 실수로 과일박스에 장난감을 넣을 수도, 장난감박스에 과일을 넣을 수도 없다.

그러니까 제한된 제네릭 클래스 얘기다.

0개의 댓글

관련 채용 정보