제네릭스(generics)란?

beomjun·2022년 4월 1일
0

Java

목록 보기
1/1
post-thumbnail

제네릭스(Generics)

제네릭스란?

  • 제네릭스는 JDK1.5 에서 처음 도입된 것으로 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입체크를 해주는 기능이다.
    객체의 타입을 컴파일 시에 체크하기 때문에 객체의 타입 안정성을 높인다는 것은 의도하지 않은 타입의 객체가 저장되는 것을 막고
    저장된 객체를 꺼내올 때 원래의 타입과 다른 타입으로 잘못 형변환되어 발생할 수 있는 오류를 줄여준 다는 뜻이다.

제네릭의 선언

  • 제네릭 타입은 클래스와 메서드에 선언 가능한데, 클래스에서 선언하는 제네릭 타입으로
class Box {
    Object item;
    
    void setItem(Object item) { this.item = item; }
    Object getItem() { return item; }
}

이 클래스를 제네릭 클래스로 변경하면 클래스옆에 <T>를 붙이면 된다. 그리고 Object를 모두 T로 바꾼다.

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

Box 에서 T를 타입변수 라고하며, 타입 변수는 T가 아닌 다른 것을 사용해도 된다. 아래표와 같은 타입변수가 주로 사용된다.

image
제네릭은 컴파일시 타입을 체크해 준다고 했다.

Box<String> b = new Box<String>();    //타입 T 대신, 실제 타입 String을 지정
b.setItem(new Object());              // 에러발생 제네릭이 String이므로 String타입만 가능
b.setItem("ABC");                     // String 타입이므로 가능
String item = b.getIgem();   // 제네릭의 다른 장점으로 형변환 생략가능

제네릭스의 제한

  • 제네릭 클래스 Box의 객체를 생성할 때, 객체별로 다른 타입을 지정하는 것은 적절하다. 제네릭스는 인스턴스별로 다르게 동작하도록
    하려고 만든 기능이다
Box<Apple> appleBox = new Box<Apple>(); Apple객체만 저장이 가능
Box<Grape> appleBox = new Box<Grape>(); Grape객체만 저장이 가능

하지만 모든 객체에 대해 동일하게 동작하는 static멤버에는 타입 변수 T를 사용할 수 없다. T는 인스턴스 변수로 간주.(static멤버에 인스턴스 변수는 사용불가)

class Box<T> {
    static T item;                     //에러발생
    static int compare(T t1,T t2){...} //에러발생
}
class Box<T> {
    T[] itemArr;
    T[] toArray() {
        T[] tmpArr = new T[imemArr.length]; //에러. 제네릭 배열 생성불가
    }
}

여기서 배열을 생성할 수 없는 것은 new연산자 때문이다. 이 연산자는 컴파일 시점에 타입 T가 뭔지 알아야 하지만 컴파일 시점에는 어떤
타입이 될지 알 수 없다.

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

  • 제네릭 클래스를 생성할때 Box<Apple> applebox = new Box<Grape>() 같이 타입이 일치하지 않으면 에러가 발생한다.
    두 타입(Apple,Grape)이 상속관계에 있어도 마찬가지로 오류가 발생한다.
    단, 두 제네릭 클래스이 타입이 상속관계이고 타입이 같은 것은 괜찮다. Box class가 FruitBox class의 부모 클래스일때

    Box<Apple> appleBox = new FruitBox<Apple>(); 은 다형성이 적용되서 오류가 발생하지 않는다.
  • JDK1.7부터는 추정이 가능한 경우 생성자에 타입을 생략할 수 있게 되었다. 참조변수의 타입으로 부터 Box가 Apple타입의 객체만
    저장한다는 것을 알 수 있기 때문에 가능하다 그래서 아래 문장처럼 선언가능하다.

    Box<Apple> appleBox = new Box<>();

제네릭의 범위 설정

  • 제네릭을 단순히 이런 식으로 설정할 수도 있지만 범위를 주고 싶을 수도 있다. 예를 들어 Interger, Byte, Double 등과 같은 숫자를 다루는
    래퍼 클래스들만 범위를 주고 싶을 때 extends라는 키워드를 사용한다. 로 설정 시에는 숫자 관련된 래퍼 클래스와 그 자손들만
    담을 수 있다

와일드 카드

제네릭 타입이 다른 것만으로는 오버로딩이 성립하지 않기 때문에 와일드 카드가 고안됐다. 와일드 카드?
로 표현하는데, 와일드 카드는 어떠한 타입도 될 수 있다. <?>만으로는 Object타입과 다를 게 없으므로, extendssuper
상한과 하흔을 제한할 수있다.

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

< ? Super T> 와일드 카드이 하한 제한. T와 그 조상들만 가능

< ? > 제한 없음. 모든 타입이 가능 < ? extends Object >와 동일

< K extends T>, <? extends T>는 차이가 있다 K는 특정 타입으로 지정이 되지만, ?는 타입이 지정되지 않는다는 의미가 있다.

제네릭 메소드(Generic method)

  • 메서드에도 제네릭을 적용할 수 있다. 제니릭 메소드의 제네릭 타입변수의 위치는 반환형 앞에 위치하며 제네릭 클래스에
    정의된 타입 매개변수와는 별개의 것이다. 같은 타입의 문자를 사용했더라도 같은 것이 아니니 주의해야한다.예제로

    static Juice makeJuice(FruitBox<? extends Fruit> box) {} 를 지네릭 메서드로 바꾸면
    static <T extends Fruit> Juice makeJuice(FruitBox<T> box) {} 로 바꿀수 있다

    또 다른 예로는

    public static <T extends Comparable<? super T>> void sort(List<T> list) 메서드를 보자면 List의 요소가 Comparable을
    구현한 것이여야하며 구현한 T또는 T의조상이 올수 있다는것이다. TOrange이고 조상인 Fruit가 있다면 T에는 Orange Fruit Object 가 올수있다.

참고문헌 : 자바의 정석 3rd

profile
백엔드 개발자 지망생

0개의 댓글