[JAVA] 지네릭스

maxxyoung·2023년 2월 19일

지네릭스

지네릭스란?

  • 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입체크를 해주는 기능
  • 장점
    • 타입의 안정성을 제공
    • 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해짐

지네릭 클래스의 선언

  • 클래스와 메서드에 선언 가능

    • 클래스 선언
    class Box<T>{
    	T item;
    
    	void setItem(T item) {this.item = item; }
        T getItem() {return item; }
     }
    • T는 타입 변수(type variable)라고 하며, Type의 첫글자에서 따옴
    • ArrayList<E>는 Element, Map<K, V>는 Key, Value에서 따옴
    • 무조건 T를 사용하기 보다는 가능하면 상황에 맞게 의미있는 문자 사용할 것
    • 기호의 종류만 다를 뿐 임의의 참조형 타입을 의미한다는 것은 모두 같음

지네릭스 용어

class Box<T> {}
  • Box<T> 지네릭 클래스. 'T의 Box'또는 'T Box'라고 읽음
  • T 타입 변수 또는 타입 매개변수(T는 타입 문자)
  • Box 원시 타입

지네릭스 제한

  • 모든 객체에 대해 동일하게 동작해야하는 static멤버에 타입변수 T를 사용할 수 없음(특정 타입으로 제한한 경우 사용가능)
    • static 멤버는 인스턴스 멤버를 참조할 수 없기 때문에 인스턴스 멤버인 T를 참조할 수 없음
  • 지네릭 타입의 배열 생성도 안됨(new T[10] 안됨)
    • new 연산자는 컴파일 시점 타입 T가 뭔지 알아야하는데 T가 뭔지 모르므로
    • 꼭 생성해야하면 'Reflection API'의 newInstance()와 같이 동적으로 객체 생성하거나 Object배열을 생성해서 복사한 다음 T[]로 형변환 하는 방법도 있음

지네릭 클래스의 객체 생성과 사용

Box<Apple> appleBox = new FruitBox<Apple>();
  • 두 제네릭 클래스의 타입이 상속관계에 있고, 대입된 타입이 같은 경우도 사용가능
Box<Apple> appleBox = new Box<>();
  • 자바 1.7부터 추정이 가능한 경우 타입을 생략할 수 있음

제한된 지네릭 클래스

class FruitBox<T extends Fruit> {
	ArrayList<T> list = new ArrayList<T>();
}
  • extends를 사용하면 특정 타입의 자손들만 대입할 수 있게 제한할 수 있임
interface Eatable{}
class FruitBox<T extends Eatable> {}
}
  • 클래스가 아닌 인터페이스를 구현해야하는 제약이 필요할 때도 extends사용
class FruitBox<T extends Fruit & Eatable> {...}
  • 클래스 Fruit자손이면서 Eatable인터페이스도 구현해야한다면 '&'기호 사용

와일드 카드

  • '?'기호 사용. 어떤 타입도 될 수 있음
  • extends와 super로 상한과 하한을 제한 할 수 있음
- <? extends T> 와일드 카드의 상한 제한. T와 그 자손들만 가능
- <? super T> 와일드 카드의 하한 제한. T와 그 조상들만 가능
- <?> 제한 없음. 모든 타입이 가능. <? extends Object>와 동일

지네릭 메서드

  • 메서드 선언부에 지네릭 타입이 선언된 메서드를 지네릭 메서드라 함
static <T> void sort(List<T> list, Comparator<? super T> c)
  • 지네릭 타입의 선언 위치는 반환타입 바로 앞
  • static 메서드는 지네릭 타입을 선언하고 사용하는 것은 가능
static <T extends Fruit> Juice makeJuice(FruitBox<T> box) {
	String tmp = "";
    for(Fruit f : box.getList()) {
    	tmp += f + " ";
    }
    return new Juice(tmp);
}

//호출
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
Juicer.<Fruit>makeJuice(fruitBox);
Juicer.makeJuice(fruitBox);
  • fruitBox 선언부를 통해 대입된 타입을 컴파일러가 추정할 수 있어 생략 가능
  • 대입된 타입이 있을 때, 같은 클래스 내에 있는 멤버들끼리 this, 클래스 이름을 반드시 써야함

지네릭 타입의 형변환

Box box = null;
Box<Object> objBox = null;

box = (Box)objBox;
objBox = (Box<Object>) box;
  • 지네릭과 원시타입간의 형변환은 경고는 뜨지만 가능함
Box<String> strBox = null;
Box<Object> objBox = null;

objBox = (Box<Object>)strBox;
strBox = (Box<str>) objBox;
  • 지네릭 타입간의 형변환은 불가능
static Juice makeJuice(FruitBox<? extends Fruit> box){...}

FruitBox<? extends Fruit> box = new FruitBox<Apple>();
  • 지네릭 상속관계 간에는 다형성 적용할 수 있음, 즉 형변환 가능!

지네릭 타입의 제거

  • 컴파일러는 지네릭 타입을 이용해서 소스파일을 체크하고, 필요한 곳에 형변환을 넣어줌, 그 후 지네릭 제거
profile
오직 나만을 위한 글. 틀린 부분 말씀해 주시면 감사드립니다.

0개의 댓글