자바

[자바] 제네릭스

j9972 2022. 7. 22. 11:46
728x90

1. Generics

generics - 다양한 타입의 개게들을 다루는 메서드나 컬렉션 클래스에 "컴파일 시의 타입 체크"를 해주는 기능이다
제네릭 장점
1. 타입 안전성 제공
2. 타입체크와 형변환을 생략할 수 있으므로 코드 간결

 

제네릭 클래스 선언

T = 타입 변수 ( 임의의 참조형 타입 )

class Box<T> { // 제네릭 타입 T 선언
    T item;
    void setItem(T item) { this.item = item; }
    T getItem() { return item; }
}

Box<String> b = new Box<String>(); // 타입 T 대신 실제 타입
b.setItem(new Object()); // 에러 String만 가능
b.setItem("ABC");
// String item = (String) b.getItem(); 형변환 필요 없음
String item = b.getItem();

 

제네릭 용어

Box<T> 제네릭 클래스. 
T  타입 변수 또는 타입 매개변수 ( T는 타입 문자 )
Box  원시 타입
Box<String> b = new Box<String>();
// Box<String> => 제네릭 타입 호출
// < String >의 Stirng => 대입된 타입

 

제네릭 제한

1. static 멤버에 대한 타입 변수 T를 사용할 수 없다 ( T는 인스턴스 변수로 간주 )
2. 제네릭 타입의 배열을 생성하는것도 허용되지 않는다 ( new 연산자 때문 )
-> 이유, 컴파일 시점에 T가 뭔지 정확히 알아야 하는데 T가 어떤 타입이 될지 몰라서 안된다
3. instanceof 연산자도 new연산자같이 사용할 수 없다

 

하지만, 꼭 제네릭 배열이 필요한 경우

1. new대신 newInstance() 메서드 사용
2. Object 배열 생성 후 복사해서 '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(); }
}

 

Box<T>의 객체를 생성할때, 참조변수와 생성자에 대입된 타입(매개변수화된 타입)이 일치해야 한다.

Box<Apple> appleBox = new Box<Apple>(); 
// Box<Apple> appleBox = new Box<>();  가능
Box<Apple> appleBox = new Box<Grape>(); // error

appleBox.add(new Apple());
appleBox.add(new Grape()); // error

 

두 타입의 상속관계

Box<Fruit> appleBox = new FruitBox<Apple>; // error 타입이 다름

FruitBox 가 Box의 자손이라 할때
Box<Apple> appleBox = new FruitBox<Apple>; // ok, 다형성

 

제한된 제네릭 클래스

타입의 종류를 제한 -> 'extends'를 사용하면 특정 타입의 자손들만 대입할 수 있다. 클래스가 아니라 인터페이스를 구현해야하더라도 implements 가 아니라 'extends' 이다
class FruitBox<T extends Fruit> { } // Fruit 자손만 타입으로 지정 가능

 

fruit의 자손이면서 eatable인터페이스도 구현해야하면 '&' 기호 쓰기
class FruitBox<T extends Fruit & Eatable> {  ... }

 

와일드 카드

제네릭 타입이 다르다면, 오버로딩이 성립하지 않는다. 

이럴때, 필요한 것은 와일드 카드 이다.

와일드 카드는 '?' 로 표현하는데, 와일드 카드는 어떠한 타입도 될 수 없다.
? 만으로는 Object타입과 다를게 없으므로, 'extends' 나 'super' 로 상한, 하한을 제한할 수 있다.
< ? extends T > - 와일드 카드의 상한 제한. T와 그 자손들만 가능하다
< ? super T > - 와일드 카드의 하한 제한. T와 그 조상들만 가능하다
< ? > - 제한 없음. <? extends Object> 와 동일하다

 

Comparator에는 항상 < ? super T > 가 습관적으로 붙는다.

Comparator< ? super Apple > : Comparator< Apple > , Comparator< Fruit > , Comparator< Object >  // 조상
Comparator< ? super Grape > : Comparator< Grape > , Comparator< Fruit > , Comparator< Object >  // 조상

 

지네릭 메서드

제네릭 메서드 - 메서드의 선언부에 지네릭 타입이 선언된 메서드 ( 지역변수 선언 == 메서드에 선언된 제네릭 타입 )

예시 코드

static <T> void sort (List<T> list, Comparator<? super T>c)

 

제네릭 메서드는 제네릭 클래스와 다르게 static메서드여도 사용이 가능하다

 

주의

1. 지네릭 클래스에 정의된 타입 매개변수와 지네릭 메서드에 정의된 타입 매개변수는 전혀 별개다
같은 타입 문자 T를 사용해도 다른것이다.

2. 제네릭 메서드를 호출할 때, 대입된 타입을 생략할 수 없는 경우에는 참조변수나 클래스 이름을 생략할 수 없다.
같은 클래스 내에 있는 멤버들끼리는 참조변수나 클래스이름, 즉 'this' , '클래스 이름'을 생략하고 메서드 이름만으로 호출이 가능하지만, 대입된 타입이 있을 때는 반드시 써줘야 한다 
System.out.println(<Fruit> makeJuice(fruitBox)); // error - 클래스 이름 생략불가
System.out.println(this.<Fruit> makeJuice(fruitBox)); // OK
System.out.println(Juicer.<Fruit> makeJuice(fruitBox)); // OK

 

매개변수 타입이 복잡할 때 유용하다 ( code가 간결해진다. )

public static void printAll(ArrayList<? extends Product> list, ArrayList<? extends Product> list2 ) { }

public static <T extednds Product> void printAll(ArrayList<T> list, ArrayList<T> list2 ) { }

 

지네릭 타입의 형변화

제네릭 타입과 넌제네릭 타입간의 형변화는 가능하다.
Box<String> -> Box<? extends Object> 가능하다 ( 다형성이다 )

그러나, 대입된 타입이 다른 제네릭 타입 간에는 형변화가 불가능 하다 ( String -> Object )

 

<?>는 <? extends Object>를 줄여 쓴것으로 <>안에 생략된 타입은 ? 가 아닌 Object다

Optional<Object> -> Optional<String>으로 직접 형변환하는 것은 불가능 하지만,와일드 카드가 포함된지네릭 타입으로 형변화하면 가능하다.

 

Optional<Object> -> Optional<T> // 불가능
Optional<Object> -> Optional<?> -> Optional<T> // 가능

 

지네릭 타입의 제거

1. 제네릭 타입의 경계를 제거한다.
2. 제네릭 타입을 제거한 후에 타입이 일치하지 않으면, 형변환을 추가한다.
// 1
class Box<T extends Fruit> {
    void add(T t) { 
    ....
    }
}

class Box {
    void add(Fruit t) { 
    ....
    }
}

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

Fruit get(int i) {
    return (Fruit)list.get(i);
}