Lee's Grow up

[자바/java] 지네릭스 & 와일드 카드 본문

PROGRAMMING/JAVA

[자바/java] 지네릭스 & 와일드 카드

효기로그 2020. 5. 15. 10:24
반응형

지네릭스(Generics)

지네릭스는 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입체크를 해주는 기능이다. 객체의 타입을 컴파일 시에 체크하기 때문에 객체의 타입 안정성을 높이고 형변환의 번거로움이 줄어든다.
쉽게 말해 타입을 명시함으로써 타입에 대한 안정성과, 번거로운 형변환을 줄여주는 기능이다. 그리고 지네릭은 클래스와, 메소드에 선언이 가능하다.

지네릭 클래스

Java의 지네릭 클래스인 ArrayList 클래스를 참고해보자 내용은 Oracle 홈페이지의 JDK 11의 API의 내용입니다.

public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, Serializable {
    // 기타
}

위에처럼 클래스에 선언된 지네릭을 지네릭 클래스라 하며, 사용법은 클래스이름<T>과 같은 방법으로 사용이 가능하다.
ArrayList는 지네릭 안에 <E>라는 문자가 들어갔고 설명에서는 <T>가 들어갔는데 여기서 E나 T는 임의의 타입을 선언하는 문법으로써 수학의 x, y, z 등으로 선언하는 것과 비슷하다고 생각하면 됩니다.

이제 간단하게 클래스를 하나 선언과 구현을 해보겠습니다.

public class Item<T> {

    private T item;
    public Item(T item) { this.item = item; }
    public T getItem() { return item; }
}

// 실행
public class Main {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Item<String> stringItem = new Item<>("Lee");
        System.out.println(stringItem.getItem());

        Item<Integer> intItem = new Item<>(20);
        System.out.println(intItem.getItem());
    }
}

여기서 Item 클래스는 T 라는 타입문자 말고 구체적인 클래스를 선언해서 해당 클래스만 받도록 강제화할 수 있습니다. 예를들어 class Item<String>로 선언하면 String 타입만 사용할 수 있게 됩니다.

지네릭 클래스의 제한
  • static 멤버에 타입 변수 T를 사용할 수 없다. 그 이유는 타입변수는 인스턴스 변수로 간주되기 때문에 인스턴스 되기 전까지 타입을 알 수 없기 때문이다.
  • new 연산자의 경우도 마찬가지이다 그래서 동적으로 객체를 생성하는 newInstance()등과 같은 메소드로 생성하는 방법이 있다.

자 그렇다면 객체지향의 특징인 상속 관계에서는 어떻게 사용해야 할까? 기존의 Item 클래스를 조금 변경하고, Book, JavaBook를 추가해줍니다.

public class Item<T> {

    private List<T> list = new ArrayList<T>();

    public List<T> getList() {
        return list;
    }
}

public class Book { }

public class JavaBook extends Book { }

이제 메인 메소드에서 Item<Book> book = new Item<Book>(); 과 같이 선언해보면 당연히 컴파일 오류가 발생하지 않는다. 그럼 다음과 같이 상속관계인 클래스를 사용해보자 Item<Book> javaBook = new Item<JavaBook>(); 얼핏 보면 될거 같지만 컴파일 오류가 발생한다. 이유는 타입을 Book 클래스 타입으로 강제화 했기 때문에 new Item<JavaBook>() 부분이 불가능한 것. 그래서 앞에 선언되는 타입과 뒤에 new 연산자 부분의 타입이 일치하니 java 1.7 부터는 new 연산자 부분에 타입이 생략이 가능해졌다.

타입 변수 T의 타입 제한

그렇다면 T로 선언하면 결국 모든 타입을 다 받을 수 있는거 아니야? 라고해서 제한을 주기 위해 extends 라는 키워드를 제공해서 해당 클래스의 상속 관계인 클래스만 타입을 넣을 수 있도록 제공을 해준다.

public class Item<T extends Book> { }

public class Book { }

public class JavaBook extends Book { }

public class Drink { }

위와 같은 클래스가 존재한다고 할 때 Item<Book> book = new Item<>()Item<JavaBook> javaBook = new Item<>()는 사용이 가능하지만, Book 클래스와 관계가 없는 Drink는 사용이 불가능하다 Item<Drink> drink = new Item<>(); 사용 불가,

추가로 인터페이스의 구현을 해야 한다는 제약이 필요하면 마찬가지로 implements가 아닌 extends 키워드를 사용하고 여러개일 경우 &라는 키워드로 연결해주면 된다 . public class Item<T extends Book & List> { }

와일드 카드

기존의 Item을 매개변수로 받는 static 메소드를 가지는 클래스를 하나 생성해보자.

public class Printer {    
    static void printItem(Item<Book> item) {
        System.out.println("Book 타입의 item");
    }
    static void printItem(Item<JavaBook> item) {
        System.out.println("JavaBook 타입의 item");
    }
}

여기서 Item 자체는 extends란 키워드를 사용해서 상속 관계의 제한을 걸어둔 상태이다. 그러면 현재 Item은 Book 타입과 JavaBook 타입 둘다 가질 수 있는 상태인데, 해당 메서드는 Book 타입만 지원을 한다. 그렇다면 메소드 오버로딩을 사용해서 아래와 같이 추가하면 해결이 될까? 안타깝게도 지네릭만 다르다고 오버로딩은 성립되지 않는다. 그래서 등장한게 와일드 카드라는 개념이다.
?라는 기호로 사용하면 해당 지네릭은 어떤 타입도 될 수 있다는 표현이다.

기존의 메소들를 아래와 같이 변경 static void printItem(Item<?> item) { } 와일드 카드도 제한을 줄 수 있는 키워드를 제공 하는데 extendssuper이다 각각 본인과 자손, 본인과 조상들까지 제한을 걸 수 있다.

제너릭 메서드

메소드의 반환 타입앞에 타입 변수 를 선언하는 메서드를 말한다
static <T> void sort(List<T> list, Comparator< ? super T > c ) 여기서 매개변수의 T와 메소드에 선언된 T는 별개의 타입 변수란 점을 기억해야 한다.

반응형
Comments