Java/자바의 신 VOL.2
03. 실수를 방지하기 위한 제네릭이라는 것도 있어요.
h2boom
2024. 6. 27. 17:38
제네릭 (Generic)
- 타입 형 변환에서 발생할 수 있는 문제점을 "사전"에 없애기 위해서 만들어진 것
명시적으로 타입을 지정할 때 사용하는 것
Object object;
object = new String();
String temp = (String)object;
- 이 경우 Object는 모든 클래스의 부모 클래스기 때문에 어떤 타입의 객체가 와도 상관이 없다.
- object 객체는 String 객체지만 클래스 타입이 Object이기 때문에 같은 객체 타입에 값을 저장하려고 하더라도 형 변환이 필요하다.
- 형 변환을 해주지 않으면 에러가 발생한다.
- 어떤 타입으로 변환을 해줘야하는지 햇갈린다면 instanceof를 사용할 수 있다.
- 이런 경우 상당히 번거로운 확인 절차를 거쳐야하는데 Generic은 이런 단점을 보완한다.
- object 객체는 String 객체지만 클래스 타입이 Object이기 때문에 같은 객체 타입에 값을 저장하려고 하더라도 형 변환이 필요하다.
public class CastingGeneric<T> {
public static void main(String [] args) {
CastingGeneric<String> obj = new CastingGeneric<String>(); // 제네릭 객체 생성
obj.setObject(new String());
String obj2 = obj.getObject();
}
private T object;
public void setObject(T obj){
// 내용 생략
}
public T getObject(){
// 내용 생략
}
}
- 제네릭을 사용하려면 클래스 선언문에서 클래스 명<>으로 작성하며 <>안에는 가상의 타입이름을 작성한다.
- <> 안에 타입에는 존재하는 클래스를 사용해도 되고 존재하지 않는 것을 사용해도 된다.
- 타입 이름은 어떤 단어가 들어가도 문제가 되지 않지만 클래스 명명 규칙과 동일하게 지정하는 것이 좋다.
- 제네릭 타입 이름에 대해 정의한 기본 규칙이 있다.
- E : 요소 (Element, Collection에서 주로 사용)
- K : 키
- N : 숫자
- T : 타입
- V : 값
- S, U, V : 두 번째, 세 번째, 네 번째에 선언된 타입
- 꼭 지켜야하는 것은 아니지만 어떤 사람이 봐도 쉽게 이해하려면 규칙을 따르는 것이 좋다.
- 제네릭 타입의 객체를 생성할 때 <>안에 실제 타입을 명시해준다.
- 이렇게 생성된 객체는 실제 타입이 <>안에 명시한 타입이 되므로 객체 치환 시 형 변환이 필요가 없다.
- 잘못된 타입으로 치환 시 컴파일 자체가 되지 않는다.
- 실행 시 잘못된 형 변환으로 예외가 발생하는 일이 없다.
- <> 안에 타입에는 존재하는 클래스를 사용해도 되고 존재하지 않는 것을 사용해도 된다.
제네릭 타입 <?> (Wildcard)
- 제네릭에서 ?로 명시한 타입은 wildcard 타입이라고 한다.
- 만약 메소드 매개 변수로 어떤 제네릭 타입이 넘어오는지 모르는 경우 어떻게 해야할까?
- 제네릭 타입을 확실히 하기 힘든 경우, <?>로 타입을 지정함으로 어떤 타입이 와도 문제가 생기지 않으며 처리할 수 있다.
// 제네릭 타입이 매개 변수로 넘어오는 경우 String만 받을 수 있다.
public void wildcardStringMethod(WildcardGeneric<String> c) {
String value = c.getWildcard();
System.out.println(value);
}
// 제네릭 타입이 매개 변수로 넘어오는 경우 어떤 타입도 다 받을 수 있다.
public void wildcardStringMethod2(WildcardGeneric<?> c) {
Object value = c.getWildcard();
if (value instanceof String) {
System.out.println(value);
}
}
// wildcard로 선언한 객체를 특정 타입으로 값을 지정하는 것은 불가능하다.
WildcardGeneric<?> wildcard = new WildcardGeneric<String>(); // 컴파일 에러 발생
- wildcard 타입으로 매개 변수를 받는 경우에는 메소드 내부에서는 어떤 타입이 들어올지 전혀 알 수 없다.
- Object 타입에 변수에 매개 변수를 저장하고 넘어오는 타입의 경우의 수가 적은 경우 instanceof를 사용해 해당 타입을 확인 후 처리하면 된다.
- wildcard 타입은 메소드의 매개 변수로만 사용하는 것이 좋다.
- 어떤 객체를 wildcard로 선언하고 그 객체의 값은 가져올 수 있다.
- wildcard로 객체를 선언했을 때 특정 타입으로 값을 지정하는 것은 "불가능"하다.
- wildcard로 제네릭 선언에 사용하는 타입의 범위도 지정할 수 있다.
- <?> 대신 <? extends 사용 가능한 타입>으로 사용하는 타입을 제한할 수 있다.
- <? extends 사용 가능한 타입> : Bounded Wildcards 라고 불리며 매개 변수로 넘어오는 제네릭 타입의 경계를 지정하는 데 사용한다는 의미
- extends 뒤 타입을 상속받은 모든 클래스를 제네릭 타입으로 사용할 수 있다는 의미.
- ex) <? extends Car> 인 경우 Car 클래스와 관련되어 있는 상속한 클래스만 매개 변수 타입으로 올 수 있다.
- 적합한 타입이 아니면 컴파일 에러 발생
- Bounded Wildcards로 선언한 타입에도 값을 할당할 수 없다.
- wildcard 타입은 조회용 매개 변수로 사용해야만 한다.
제네릭 메소드 선언
- wildcard를 매개 변수로 받는 메소드를 선언 시 매개 변수로 받은 객체에 값을 추가할 수 없다는 단점이 있었다.
- 매개 변수 객체에 값을 추가하는 방법 = 제네릭 메소드로 선언하면 된다.
- 메소드 선언부 시 리턴 타입 앞에 <>로 제네릭 타입을 선언하고 그 타입을 매개 변수에서 사용하면 컴파일 시 문제도 발생하지 않으며 값도 할당할 수 있다.
- ?를 사용하는 wildcard처럼 타입을 애매모호하게 하는 것보다 이 방식처럼 명시적으로 메소드 선언 시 타입을 지정해주면 보다 더 견고한 코드를 작성할 수 있다.
- Bounded Wildcard처럼 사용 하려면 리턴 타입 앞 제네릭 타입 선언부에 <? extends 타입>
대신에 <제네릭 타입 extends 사용가능한 타입>으로 선언해주면 된다.- public <T extends Car> void genericMethod(WildcardGeneric<T> c, T addValue){}
- 제네릭 타입을 여러 개 사용하려면 콤마로 구분해 나열해준다.
- public <S, T extends Car> void genericMethod2(WildcardGeneric<T> c, T addValue, S another){}
public <T extends Comparable<T>> T getMax(T... a) {
T maxT = a[0];
for (T tempT : a) {
if (tempT.compareTo(maxT) > 0) {
maxT = tempT;
}
}
return maxT;
}
- 여기서 메소드 제네릭 타입으로 <T>가 아닌 <T extends Comparable<T>>를 사용한 이유는?
- compareTo() 메소드를 사용하기 위해서, 만약 <T>로 한다면 instanceof로 타입을 확인후 사용하는 코드로 작성하지 않으면 compareTo() 메소드를 사용할 수 없다.
간단 내용 정리
1. 제네릭이 자바에 추가된 이유는 무엇인가요?
타입 형 병환에서 발생할 수 있는 문제점을 "사전"에 없애기 위해서 만들어졌다
2. 제네릭 타입의 이름은 T나 E 처럼 하나의 캐릭터로만 선언되어야 하나요?
X, 예약어만 아니면 어떤 단어도 사용할 수 있다.
3. 메소드에서 제네릭 타입을 명시적으로 지정하기 애매할 경우에는 < > 안에 어떤 기호를 넣어 주어야 하나요?
<?>
4. 메소드에서 제네릭 타입을 명시적으로 지정하기에는 애매하지만, 어떤 클래스의 상속을 받은 특정 타입만 가능하다는 것은 나타내려면 < > 안에 어떤 기호를 넣어 주어야 하나요?
<? extends 타입>
5. 제네릭 선언시 wildcard라는 것을 선언했을 때 어떤 제약사항이 있나요?
매개 변수 값을 할당 사용하려면 Object 타입에 할당해야 한다.
wildcard로 객체를 선언했을 때 특정 타입으로 값을 지정하는 것은 "불가능"하다.
6. 메소드를 제네릭하게 선언하려면 리턴타입 앞에 어떤 것을 추가해 주면 되나요?
<제네릭 타입>