티스토리 뷰

Java

제너릭(Generic)이란?

파미페럿 2021. 9. 13. 17:51

자바로 코딩을 하다보면, 하다못해 Map 컬렉션을 쓰다보면 아래와 같이 '<>' 안에 정확한 type을 명시하는 것을 볼 수 있다.

이것은 바로 제너릭(Generic)라는 것으로 자바에서 타입을 좀 더 유연하게 받고 사용하기 위한 기능이다.

Map<String, Object> map = new HashMap<>();

 

 

제너릭(Generic)이란?

Map과 같이 Object 타입이면 또는 어떤 객체를 상속 받은 하위 객체이면 무조건 사용할 수 있을 때 해당 타입을 와일드카드와 같이 다양한 정해진 범위의 다양한 타입으로 유연하게 사용할 수 있도록 정의된 일종의 키워드 같은 것이다.

참고로 제너릭은 자바 5부터 사용 가능하다.

 

예를 들어 아래와 같이 클래스에서 특정 필드애 대해서 호출자가 정의한 타입으로 해당 필드를 사용하고자 할 때 사용할 수 있다.

public class Test<T> {
	T obj;
    
        public Test(T obj) {
            this.obj = obj;
        }

        public T getData() {
            return this.obj;
        }
 }

 

 

제너릭 식별자인 T를 이용해서 Test 클래스를 위와 같이 구현했을 경우 아래 코드들이 다 가능해진다.

이와 같이 특정 데이터에 대해 데이터타입을 호출자가 원하는대로 유연하게 사용해야하는 경우 'T'와 같은 제너릭스 키워드를 사용해서 유연한 타입 설정이 가능해진다.

Test<String> test1 = new Test("test");
Test<Integer> test2 = new Test(1);

 

 

또한 제너릭은 Object를 반환하는 메소드에 대해 Objec가 아닌 제너릭 키워드로 명시해 명확하지 않은 casting(형변환)을 피하게 해준다.

예를 들어 아래와 같은 함수를 사용했을 때 호출자는 알아서 String 타입으로 반환 값을 형변환 해줘야 한다. 하지만, 이 경우는 반환 값을 무조건 String으로 형변환할 수 있다는 확신은 없다.

private Object notGenericObject(Object obj) {
        return obj;
}
String text = (String) notGenericObject("text");

 

 

하지만 제너릭을 사용할 경우 타입을 제너릭 키워드로 컨트롤할 수 있게 되므로 함수 자체에서 자동으로 제너릭 식별자로 인해 해당 타입으로 반환되게 되고 호출자가 불필요하게 확실하지 않은 형변환을 할 필요가 없다.

private <T> T genericObject(T obj){
        return obj;
}
String text = genericObject("text");

 

 

제너릭(Generic) 키워드 종류

제너릭스는 보통 타입을 뜻하는 T를 제일 많이 사용한다. 꼭 무슨 키워드를 사용해야지 어떤 것을 뜻한다, 이런 것은 아니지만 나름대로 제너릭스를 사용하는데

각각의 알파벳에 뜻을 두고 개발자들은 사용한다.

 

- T: 객체 타입을 뜻한다.

- E: element를 뜻한다.

- K: key를 의미한다.

- V: value를 의미하고 K와 많이 쓰인다. (예. Map)

 

위의 알파벳들은 개발자들 사이에서 저렇게 뜻을 두고 사용된다. (코드의 가독성을 높이기 위해서) 그 밖에 다른 알파벳인 S, G 등도 쓰일 수 있으며 위의 의미들을 위반하고 알파벳을 사용했다고 해서 에러가 발생하거나 하지는 않는다.

 

 

제너릭(Generic) 종류

제너릭스은 사용하는 방법도 여러 가지가 있다.

 

 

1. 기본 제너릭

위에 사용했듯이 키워드 T, E, K, V, S, G... 등과 같은 알파벳 키워드를 사용해서 제너릭을 사용하는 것을 의미한다.

타입을 유연하게 받기 위해 사용하는 제너릭이다.

 

 

2. 범위 제너릭

기본 제너릭의 경우는 타입에 대한 제한이 없다. 즉, 어떤 타입이 들어와도 상관이 없다. 하지만 특정 상위 클래스를 상속 받아 구현한 하위 클래스로만 타입을 한정해야 한다면 얘기가 달라진다.

이럴 경우 사용하는 것이 범위 제너릭이다.

 

예를 들어 아래와 같이 숫자 타입의 객체만 다루는 제너릭일 경우 '<T extends Number>'로 제한을 할 수 있다.

private <T extends Number> T numberGeneric(T n) {
        return n;
}

 

 

위와 같이 아예 Number로 숫자 타입만 받고 반환하는 것으로 특정을 지어버리면 아래와 같이 문자를 넣었을 때 String 객체는 Number 객체에 하위 클래스가 아니라고 컴파일 에러가 발생한다.

 

 

많이 범위를 여러 개 주고 싶다면 &로 여러 개의 상위 범위를 묶어서 표현하면 된다. 참고로 아래 코드는 Number와 String이 서로 같은 계열의 객체도 아니거니와 서로 자동으로 호환이 되지 않아서 컴파일 에러가 발생한다.

private <T extends Number & String> T numberGeneric(T n) {
	...
}

 

 

3. 와일드카드 제너릭

와일드카드 제너릭은 잘 사용하지는 않지만 위에 T와 같은 식별자가 필요 없다면 사용할만한 방법이다.

예를 들어 아래와 같이 T라는 식별자를 통해서 제너릭을 사용했다면,

private void printData(CustomData<T> data) {

 

 

같은 동작을 모든 객체를 다 아우르는 키워드인 '?'를 사용해서 똑같이 할 수 있다.

private void printData(CustomData<?> data) {

 

 

단, 와일드카드 제너릭의 경우는 위와 같이 컬렉션과 같이 해당 객체에 대한 제너릭 표시에서만 사용할 수 있다.

별도 메소드 반환 값 등으로는 사용할 수 없으며 T와 같은 식별자가 아니므로 그저 모든 타입에 대해 받겠다는 표시만 되는 것이지 '?'라는 식별자를 통해 별다른 동작을 할 수 없다.

만일 아래와 같이 제너릭 키워드를 사용해야할 경우에는 ?가 아니라 T, G와 같은 특정 식별자로 타입을 받아야한다.

private <T> void listGeneric(List<T> list) {
        for (T e: list) {
            System.out.println("==> e: " + e);
        }
}

 

 

제너릭(Generic) 사용 예

1. 제너릭 클래스

맨 처음에 예시로 나타냈던 제너릭 예이다.

public class Test<T> {
	T obj;
    
        public Test(T obj) {
            this.obj = obj;
        }

        public T getData() {
            return this.obj;
        }
 }

 

클래스 필드 또는 클래스 메소드 전체적으로 제너릭 식별자를 사용해야할 때 사용하는 방법이다.

특히 메소드의 경우는 별도 메소드만 제너릭 식별자를 사용할 수 있지만(아래 제너릭 메소드 참고), 필드의 경우는 별도로 필드만 제너릭 식별자를 사용할 수 없으므로 클래스 자체에도 꼭 제너릭 식별자를 붙여줘야 한다.

 

 

2.  제너릭 메소드

특정 메소드의 반환 값 및 매개변수의 타입을 유연하게 받고 싶을 경우 사용하는 방법이다. 아래와 같이 메소드의 반환 값 또는 파라미터에 T라는 제너릭스 키워드를 사용해서 메소드를 좀 더 유연하게 

클래스 내에 있는 메소드이더라도 메소드 자체에 '<>'를 통해 제너릭 식별자를 명시하면 메소드 자체에만 제너릭 식별자를 사용할 수 있다.

 

 

메소드에는 때 클래스 이름 뒤에 '<>'를 통해 제너릭 키워드를 붙였듯이 메소드를 명시할 때 반환 값 앞에 '<>'를 붙여서 제너릭을 사용할 수 있다.

public <T> T getData(T data) {
	...
    	return data;
}

 

 

만일 다른 메소드에서 똑같은 제너릭 식별자를 사용할 경우에도 서로 다른 영역의 제너릭 식별자가 되므로 서로 별개로 동작하게 된다.

public <T> T getData2(T data) {
	...
        return data;
}

 

 

제너릭(Generic) 사용 주의사항

제너릭은 여러 객체들을 대신해주는 일종의 키워드, 식별자이다.

따라서 위와 같이 Object, String을 대신하곤 했다. 하지만 만일 재너릭 키워드에 기본 자료형을 넣을 경우? 컴파일 에러가 발생한다.

기본 자료형(int, double, byte 등)과 같은 경우는 제너릭 매개유형으로 사용할 수 없으므로 그것들을 객체형(Integer, Double, Byte 등)으로 바꿔서 사용해야한다.

List<int> list = new ArrayList<>(); // 컴파일 에러가 발생한다.
List<Integer> list = new ArrayList<>(); // list의 요소로 int 값을 정상적으로 사용할 수 있다.

 

 

 

 

✋ The Basics of Java Generics

https://www.baeldung.com/java-generics

 

 

 

 

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함