이 영역을 누르면 첫 페이지로 이동
Arc 블로그의 첫 페이지로 이동

Arc

페이지 맨 위로 올라가기

Arc

[Java] 제네릭(Generic)

  • 2024.03.12 16:32
  • Language/Java
글 작성자: SeoArc

제네릭(Generic)?

제네릭은 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입 체크를 해주는 기능이다.

객채 타입을 컴파일 타임에 체크하기 때문에 잘못된 형변환으로 인해 오류가 발생하는 상황을 막아주고, 형변환의 번거로움이 줄어들 수 있다.

 

 

제네릭 클래스

제네릭은 클래스와 메서드에 사용할 수 있는데, 먼저 제네릭 클래스는 클래스 명옆에 <T>와 같이 타입 변수를 붙여 사용한다.

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

여기서 T는 임의로 지정한 변수명으로 T가 아닌 다른 명칭으로 지정할 수 있다.

 

위 코드를 다음과 같이 사용할 수 있다.

Box<Apple> appleBox = new Box<Apple>();
Box<Apple> appleBox = new Box<>(); // JDK 1.7 부터 추정 가능한 경우 타입 생략가능 -> 다이아몬드 연산자

위와 같이 제네릭 타입을 Apple로 지정했다면 Apple 이외의 타입은 지정할 수 없다.

appleBox.setItem(new Object()); // 지정불가
appleBox.setItem(new Apple());

 

호환성을 위해 지네릭 타입을 지정하지 않고 객체를 생성하는 것이 허용되지만, 권장하지 않는다.

 

 

제한점

타입 변수에 대해 다음과 같이 객체 별로 다른 타입을 지정하는 것은 가능하다.

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

 

하지만 타입 변수는 인스턴스 변수로 간주되기 때문에 모든 객체에 대해 동일하게 동작해야하는 static 멤버에 타입 변수를 사용할 수 없다.

class Box<T> {
    private static T item; // 지정할 수 없다.
    public static int compare(T t1, T t2) { } // 지정할 수 없다
}

 

또 제네릭 타입의 배열을 생성하는 것도 허용되지 않는다.

class Box<T> {
    private T[] itemArr; // 선언 가능 -> T타입의 배열을 위한 참조변수
    
    public T[] toArray() {
        T[] tmpArr = new T[itemArr.length]; // 선언 불가 -> 제네릭 타입의 배열 생성 불가
        return tmpArr;
    }
}

이는 new 연산자로 인해 생성될 수 없는 것이다. 왜냐하면 컴파일 시점에 타입 T가 정확히 뭔지 알아야하는데 Box<T> 클래스를 컴파일 하는 시점에서는 T가 어떤 타입이 되는지 알 수 없기 때문이다. 이와 같은 이유로 instanceof 연산자도 T를 피연산자로 사용할 수 없다.

 

꼭 제네릭 배열을 생성해야 한다면, 'Reflection API'의 newInstance()와 같이 동적으로 객체를 생성하는 메서드로 배열을 생성하거나, Object 배열을 생성해서 복사한 다음 'T[]'로 형변환하는 방법 등을 사용할 수 있다.

 

 

제한된 제네릭 클래스

제네릭 타입은 다음과 같이 extends를 통해 특정 타입의 자손들만 대입할 수 있도록 제한할 수 있다.

class FruitBox<T extends Fruit> {
    private List<T> list = new ArrayList<>();
}
class Fruit { }

class Apple extends Fruit { }

class Grape extends Fruit { }

이렇게 위와 같은 상속 구조를 가지고 있다면 다음과 같이 작성할 수 있다.

FruitBox<Fruit> fruitBox = new FruitBox<>();
// FruitBox<Toy> toyBox = new FruitBox<>(); // 에러 -> Toy는 Fruit의 자손이 아니다
fruitBox.add(new Apple());
fruitBox.add(new Grape());

 

만약 클래스가 아닌 인터페이스를 구현해야하는 제약을 추가하고 싶을 때도 extends를 사용한다. 또 여러 개를 추가할 때는 & 기호로 연결한다.

class FruitBox<T extends Fruit & Eatable> { }

 

 

재귀적 타입 한정

자기 자신이 들어간 표현식을 사용하여 타입 매개변수의 허용 범위를 한정할 수 있는데, 이를 재귀적 타입 한정(recursive type bound)라고 한다.

재귀적 타입 한정은 주로 Comparable 인터페이스와 함께 쓰인다.

public static <E extends Comparable<E>> E max(Collection<E> c);

여기서 E extends Comparable<E>형식으로 사용할 수 있는데, 이는 타입 매개변수 E는 Comparable<E>를 구현한 타입이 비교할 수 있는 원소의 타입을 정의한다. 즉, 자기 자신을 서브 타입으로 구현한 Comparable 구현체로 한정한다는 의미이다.

 

따라서 String은 Comparable<String>을 구현하고, Integer는 Comparable<Integer>를 구현하는 식이다. Comparable를 구현한 객체이면서 오로지 같은 E인 Integer 타입만 받는다는 의미이다.

 

 

와일드 카드

다음과 같은 클래스가 있다고 가정해보자.

class Juicer {
    public static Juice makeJuice(FruitBox<Fruit> box) {
        StringBuilder tmp = new StringBuilder();
        for (Fruit f : box.getList()) {
            tmp.append(f).append(" ");
        }
        return new Juice(tmp.toString());
    }
}

위 메서드를 보면 제네릭이 아닌 일반 클래스이고 static 메서드이기 때문에 매개변수에 타입 변수를 적용할 수 없다.

 

때문에 다음 코드는 오류가 발생한다.

FruitBox<Fruit> fruitBox = new FruitBox<>();
FruitBox<Apple> appleBox = new FruitBox<>(); // 에러

 

 

 

그럼 위 코드를 수행시키려면 어떻게 해야될까?

다음과 같이 할 수 있을까?

class Juicer {
    public static Juice makeJuice(FruitBox<Fruit> box) {
        StringBuilder tmp = new StringBuilder();
        for (Fruit f : box.getList()) {
            tmp.append(f).append(" ");
        }
        return new Juice(tmp.toString());
    }
    
    public static Juice makeJuice(FruitBox<Apple> box) {
        StringBuilder tmp = new StringBuilder();
        for (Fruit f : box.getList()) {
            tmp.append(f).append(" ");
        }
        return new Juice(tmp.toString());
    }
}

아쉽게도 제네릭 타입만 다른 것으로 오버로딩을 할 수 없다.

 

우리는 이를 해결하기 위해 제네릭의 와일드 카드 기능을 사용할 것이다.

와일드 카드는 '?' 기호로 표시하며, 어떤 타입도 가능하다.

 

  • <? extends T>: 와일드 카드의 상한 제한. T와 그 자손들만 가능
  • <? super T>: 와일드 카드의 하한 제한. T와 그 조상들만 가능
  • <?> 제한 없음. 모든 타입이 가능 -> <? extends Object>와 동일

 

이를 사용하면 위 상황을 다음과 같이 작성하여 해결할 수 있다.

class Juicer {
    public static Juice makeJuice(FruitBox<? extends Fruit> box) {
        StringBuilder tmp = new StringBuilder();
        for (Fruit f : box.getList()) {
            tmp.append(f).append(" ");
        }
        return new Juice(tmp.toString());
    }
}

 

주의점

위에서 매개변수 타입을 <? extends Object>로 하면 모든 종류의 FruitBox가 가능해지겠지만 위와 달리 Fruit의 자손이라는 보장이 없기 때문에 for문에서 Fruit 타입의 참조변수로 받을 수 없게된다.

class Juicer {
    public static Juice makeJuice(FruitBox<? extends Object> box) {
        StringBuilder tmp = new StringBuilder();
        for (Fruit f : box.getList()) { // 에러
            tmp.append(f).append(" ");
        }
        return new Juice(tmp.toString());
    }
}

 

 

제네릭 메서드

다음과 같이 메서드에 타입 변수를 선언할 수 있는데, 이를 제네릭 메서드라고 한다.

제네릭 메서드에서 제네릭 타입은 반환 타입 바로 앞에 선언한다.

 

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

 

제네릭 클래스 vs 제네릭 메서드

제네릭 클래스에 정의된 타입 변수와 제네릭 메서드에 정의된 타입 변수는 다른 것이다. 즉, 다음 코드에서 FruitBox에 선언된 T와 sort() 에 선언된 T는 다른 것이다.

class FruitBox<T> {
    public static <T> void sort(List<T> list, Comparator<? super T> c) { }
}

또 위 메서드를 살펴보면 static으로 선언된 것을 볼 수 있는데, staticc 멤버에는 타입 매개변수를 사용할 수 없지만 위처럼 제네릭 메서드로 만든 후 사용하는 것은 가능하다.

 

사용

제네릭 메서드는 다음과 같이 호출할 수 있다.

FruitBox<Fruit> fruitBox = new FruitBox<>();
Juicer.<Fruit>makeJuice(fruitBox);

그런데 대부분 컴파일러가 선언부를 통해 대입된 타입을 추정할 수 있기 때문에 다음과 같이 생략할 수 있다.

Juicer.makeJuice(fruitBox);

 

단, 주의할 점은 만약 대입된 타입을 생략할 수 없다면, 참조변수나 클래스 이름을 생략할 수 없다.

<Fruit>makeJuice(fruitBox); // 에러
this.<Fruit>makeJuice(fruitBox);
Juicer.<Fruit>makeJuice(fruitBox);

 

 

형변환

먼저 제네릭 타입과 Non제네릭 타입 간의 형변환은 경고가 발생하지만 가능하다.

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

box = (Box) objBox;
objBox = (Box<Object>) box;

 

하지만, 다른 제네릭 타입 간의 형변환은 불가능하다.

Box<String> strBox = null;
Box<Object> objBox = null;

strBox = (Box<String>) objBox; // 에러
objBox = (Box<Object>) strBox; // 에러

 

그럼 다른 타입으로 어떻게 형변환 할 수 있을까?? 다음 코드를 한 번 작성해보자.

Box<? extends Object> wBox = new Box<String>();

형변환이 된다는 것을 확인했는가? 이렇게 형변환이 가능하기 때문에 다음과 같이 다형성이 적용될 수 있다.

FruitBox<? extends Fruit> box = new FruitBox<Fruit>();
FruitBox<? extends Fruit> box = new FruitBox<Apple>();
FruitBox<? extends Fruit> box = new FruitBox<Grape>();

여기서 반대로 형변환 하는 것도 가능하지만, 경고가 발생한다.

저작자표시

'Language > Java' 카테고리의 다른 글

[Java] 위도/경도 값에 BigDecimal or double?  (2) 2024.04.30
[Java] utility class는 무엇으로 구현하는 것이 좋을까?  (1) 2024.04.15
[Java] Mockito  (0) 2023.07.31
[Java] hashCode()  (1) 2023.07.02
[Java] Java 버전 별 특징  (2) 2023.04.14

댓글

이 글 공유하기

  • 구독하기

    구독하기

  • 카카오톡

    카카오톡

  • 라인

    라인

  • 트위터

    트위터

  • Facebook

    Facebook

  • 카카오스토리

    카카오스토리

  • 밴드

    밴드

  • 네이버 블로그

    네이버 블로그

  • Pocket

    Pocket

  • Evernote

    Evernote

다른 글

  • [Java] 위도/경도 값에 BigDecimal or double?

    [Java] 위도/경도 값에 BigDecimal or double?

    2024.04.30
  • [Java] utility class는 무엇으로 구현하는 것이 좋을까?

    [Java] utility class는 무엇으로 구현하는 것이 좋을까?

    2024.04.15
  • [Java] Mockito

    [Java] Mockito

    2023.07.31
  • [Java] hashCode()

    [Java] hashCode()

    2023.07.02
다른 글 더 둘러보기

정보

Arc 블로그의 첫 페이지로 이동

Arc

  • Arc의 첫 페이지로 이동

검색

메뉴

  • 홈
  • 태그
  • 방명록

카테고리

  • 분류 전체보기 (106)
    • Language (28)
      • C++ (0)
      • C# (0)
      • Java (28)
    • Algorithm (47)
      • Algorithm (15)
      • Data Structure (6)
      • PS (26)
    • Computer Science (22)
      • Design Pattern (1)
      • Network (14)
      • OS (7)
    • Game (0)
      • Unity (0)
    • Backend (3)
      • Spring (1)
      • JPA (2)
    • DB (0)
      • SQL (0)
    • DevOps (2)
      • AWS (0)
      • Docker (2)
      • Jenkins (0)
      • Nginx (0)
    • Software Engineering (4)
      • OOP (4)
    • AI (0)
      • Machine Learning (0)
    • Others (0)

최근 글

인기 글

댓글

공지사항

아카이브

태그

  • algorithm
  • 알고리즘
  • 자바
  • 네트워크
  • 그래프
  • java
  • graph
  • network

나의 외부 링크

정보

SeoArc의 Arc

Arc

SeoArc

블로그 구독하기

  • 구독하기
  • RSS 피드

방문자

  • 전체 방문자
  • 오늘
  • 어제

티스토리

  • 티스토리 홈
  • 이 블로그 관리하기
  • 글쓰기
Powered by Tistory / Kakao. © SeoArc. Designed by Fraccino.

티스토리툴바