도서/자바의 신

[도서/자바의 신] #21 실수를 방지하기 위한 제네릭이라는 것도 있어요

yulee_to 2022. 12. 29. 20:44

자바의 신

✔️이 글은 [자바의 신 - 이상민 지음] 도서를 바탕으로 정리한 글입니다. 


실수를 방지할 수 있도록 도와주는 제네릭

실행시 발생하는 예외에 대비하기 위해서 테스트를 잘 해야 한다. 메소드 개발과 함께 JUnit과 같은 테스트 코드를 작성하는 것이 좋다. 
JUnit은 메소드나 클래스 같은 작은 단위를 쉽게 테스트할 수 있도록 도와주는 프레임웍이다. 

형변환시 발생하는 예외를 방지하기 위해 instanceof로 하나씩 점검해야하는 단점을 보완하기 위해 Java 5부터 제네릭이라는 것이 추가되었다.

제네릭이 뭐지?

제네릭(Generic)은 타입 형 변환에서 발생할 수 있는 문제점을 컴파일시에 점검하기 위해서 만들어진 것으로 데이터타입을 일반화해준다.

클래스를 제네릭으로 선언하려면 클래스 이름 뒤에 꺾쇠 기호(<>)와 그 안에 가상의 타입 이름을 하나 지정해주고, 클래스 안에서 그 이름을 하나의 타입처럼 사용하면 된다. 

제네릭을 대신해 넣어줄 수 있는 것은 참조 자료형만 가능하고, 다형성을 사용할 수 있어 부모 타입의 객체에 자식 타입의 객체를 넣어줄 수 있다. 

꺾쇠 안에는 현재 존재하는 클래스도, 존재하지 않는 것도 들어갈 수 있지만 되도록 클래스 명명 규칙과 동일하게 지정하는 것이 좋다.

public class 클래스이름<가상타입이름> { }

제네릭 타입의 이름 정하기

꺾쇠 안에 어떤 단어가 들어가도 상관은 없지만 자바에서 지정한 기본 규칙은 있다.

  • E : 요소 (Element, 자바 컬렉션에서 주로 사용됨)
  • K : 키
  • N : 숫자
  • T : 타입
  • V : 값
  • S, U, V : 두번째, 세번째, 네번째에 선언된 타입

제네릭에 ?가 있는 것은 뭐야?

꺾쇠 안에 ?가 들어가는 경우는 매개변수로 넘어오는 객체를 제네릭한 객체로 선언해주기 위해서이다. 

?로 명시한 타입(wildcard)는 어떤 타입이 제네릭 타입이 되더라도 상관없다. 메소드 내부에서는 해당 타입을 정확히 모르기 때문에 모든 클래스의 부모 클래스인 Object로 처리해줘야 한다. 

 

어떤 객체를 wildcard로 선언해서 값을 get해오는 것은 가능하다.

하지만, 어떤 객체를 wildcard로 선언하고 특정 값으로 set하는 것은 그 객체의 타입을 정확히 알수 없기 때문에 컴파일 에러가 난다. 

 

예시

public void wildcarStringMethod(WildcardGeneric<?> c) {
    Object value = c.getWildcard();
    System.out.println(value);
}

제네릭 선언에 사용하는 타입의 범위도 지정할 수 있다

Unbounded Type

제네릭 타입 선언 이후 아무 키워드도 붙이지 않으면 unbounded type이다.

 

Upper Bounded Type

<T extends A>, <? extends A> 처럼 extends 키워드로 자기 자신(A) 혹은 A에 상속된 클래스 타입 또는 구현한 인터페이스만 사용하게 해줄 수 있다. 

Bounded Wildcards로 선언한 타입에는 값을 할당할 수 없어 조회용 매개변수로만 사용해야 한다.

upper bounded type에 한해서 &연산자로 N개의 bound 적용이 가능하다. 클래스 타입은 bound를 1개 또는 0개만 가능하고, 인터페이스는 개수 제한이 없다. 클래스 타입과 인터페이스 타입을 같이 쓰면 클래스 타입이 젤 앞에 선언되어야 한다. 

 

Lower Bounded Type

<T super A>, < ? super A> 처럼 super 키워드로 자기 자신(A) 혹은 상속받은 상위 클래스의 타입만 사용하게 해줄 수 있다. 

메소드를 제네릭하게 선언하기

wildcard로 메소드를 선언하면 매개변수로 사용된 객체에 값을 추가할 수 없다는 큰 단점이 있다. 

이걸 해결하기 위해 메소드의 리턴 타입 앞에 <>로 제네릭 타입을 선언해주고, 그 타입을 매개변수에서 사용하면 값을 할당해줄 수 있다. 

메소드에서 제네릭을 사용하면 메소드의 매개변수 타입이나 리턴 타입을 호출시에 지정해줄 수 있다는 의미가 된다. 

 

예시

public <S,T extends Car> void genericMethod(WildcardGeneric<T> c, T addValue, S another) {
    c.setWildcard(addValue);
    T value = c.getWildcard();
    System.out.println(value);
    c.setWildcard(another);
    S value2 = c.getWildcard();
    System.out.println(value2);
}

wildcard를 사용하는 것보다 이렇게 명시적으로 메소드 선언시 타입을 지정해주면 더 견고한 코드를 작성할 수 있다.

리턴 타입 앞에 오는 제네릭에 extends를 붙여 upper bounded type으로 만들어줄 수도 있다.

한 개 이상의 제네릭 타입을 사용할 경우 콤마(,)를 이용해 구분해주면 된다. 


정리해 봅시다

1. 제네릭이 자바에 추가된 이유는?

객체를 형변환할 때 생기는 문제점을 컴파일시에 점검하기 위해서이다.

2. 제네릭 타입의 이름은 T나 E처럼 하나의 캐릭터로 선언되어야 하나요?

NO

3. 메소드에서 제네릭 타입을 명시적으로 지정하기 애매할 경우에는 <> 안에 어떤 기호를 넣어주어야 하나요?

?(wildcard)

4. 메소드에서 제네릭 타입을 명시적으로 지정하기에는 애매하지만, 어떤 클래스의 상속을 받은 특정 타입만 가능하다는 것을 나타내려면 <> 안에 어떤 기호를 넣어주어야 하나요?

? extends 타입

5. 제네릭 선언 시 wildcard라는 것을 선언했을 때 어떤 제약 사항이 있나요?

wildcard 타입을 Object 타입으로만 사용해야 한다. 

6. 메소드를 제네릭하게 선언하려면 리턴 타입 앞에 어떤 것을 추가해 주면 되나요?

제네릭 타입을 선언해주면 된다. 

 

728x90