본문 바로가기

BE/JAVA

[JAVA] 자바 제네릭(Generic) 기본 및 활용

목차 
1. 제네릭이란?
2. 제네릭 타입이란?   

3. 제네릭을 사용하는 이유 
4. 제네릭 타입 예제 

 

📌 1. 제네릭이란?

제네릭(Generic)이란 "타입을 일반화"하는 것을 의미하며

클래스 내부에서 정하는 것이 아닌 사용자 호출에 의해 타입이 지정되는 것을 의미합니다.  

즉, 특정 타입의 변수형에 지정되는 것이 아닌 필요에 의해 여러 가지를 타입(Integer, String 등)을 사용하고 싶을 경우 사용합니다. 

제네릭 타입 클래스는 자바5부터 추가된 개념으로 자바 API 문서에도 간간히 볼 수 있는데요.

주로 우리가 쓰는 컬렉션 프레임워크의 List 인터페이스도 제네릭 타입입니다.

 

 

📌2. 제네릭 타입이란? 

제네릭 타입은 타입(Type)을 파라미터로 가지는 클래스와 인터페이스를 말합니다.

즉 제네릭 타입은 파라미터가 아닌 클래스 및 인터페이스인데요. 

제네릭 타입은 클래스 및 인터페이스 이름 뒤에 "< >" 를 쓰고 타입 파라미터(T)를 명시해야 합니다. 

타입 파라미터를 별도로 명시하지 않기 때문에 특정 타입 파라미터에 종속되지 않을 수 있다는 장점이 있습니다.  

추가로, 제네릭을 사용하지 않은 타입은 원시 타입(Raw Type) 이라고 하며 일반적으로 많이 사용하는 Integer, String 등을 파라미터로 사용한 클래스입니다. 

public class 클래스명<T> {...}
public interface 인터페이스명<T> {...}

 

타입 파라미터(인자) 

타입 파라미터는 제네릭 클래스/인터페이스에 사용되며  "타입이 정해지지 않은" 파라미터인데요.

대부분 참조 타입의 경우 T, 요소의 경우 E(Element) , 키를 나타낼 경우 K(Key), 값을 나타낼 경우 V(Value) 등을 사용합니다.

타입 파라미터를 나타낼 때는 일반적으로 대문자 알파벳(A~Z)을 사용하며, 꼭 T일 필요는 없습니다.

 

타입 분류  타입 인자 
참조 타입 <T>
요소 <E>
키  <K>
값  <V>

 

 

📌 3. 제네릭 타입을 사용하는 이유 

제네릭 타입을 사용하면 특정 타입으로 종속받지 않아 편리한데요. 구체적으로 왜 사용해야 할까요? 

1) 재사용성 증가 

제네릭 타입은 여러 타입의 파라미터를 삽입해 객체를 생성할 수 있기 때문에 코드를 간결하게 하고 재사용성을 높입니다.  

동일한 기능을 하는 메서드에서 파라미터 타입만 다르게 사용할 경우, 제네릭 타입이 유용하게 쓰일 수 있습니다. 

2) 컴파일 시 타입 에러 발견 가능

제네릭 타입의 경우 컴파일시 잘못 사용되는 타입 문제점을 제거하기 위해 강하게 타입 체크를 수행합니다. 

이덕분에 컴파일 이후 런타임 단계에서 타입 문제가 발생될 가능성을 방지해줍니다. 

3) 컴파일러가 타입 변환 수행 

컴파일 단계에서 컴파일러가 타입 캐스팅을 수행해주기 때문에 불필요하게 코드에서 타입 캐스팅을 해줄 필요가 없습니다. 

 

 

📌 4. 제네릭 타입 예제 

아래에서 제네릭 클래스 및 인터페이스의 구체적인 사용 방법에 대해 알아보겠습니다.

제네릭 클래스와 인터페이스의 경우, 타입 파라미터를 명시하고 사용이 가능하며 타입 파라미터 상속이 가능합니다. 

 

1) 제네릭 클래스 

먼저 제네릭 클래스 사용방법에 대해 알아보겠습니다.

아래와 같이 Coffee 클래스 내 타입 파라미터를 T로 명시하여 클래스를 선언해주었습니다.

또한 타입 파라미터는 클래스 내 멤버 변수 타입으로도 사용이 가능하여 T taste; 와 같이 변수를 선언했습니다. 

타입 파라미터 T의 경우, Coffee 객체를 생성헀을 때 실제 구체적인 타입으로 변경됩니다. 

· 클래스명 : Coffee <T> 

· 메소드명 : protected T taste : 커피의 맛 변수 

class Coffee<T>{
	protected T taste; 
}

 

2) 제네릭 인터페이스

이제 제네릭 인터페이스에 대해 알아보겠습니다. 인터페이스의 경우 추상 메서드만 선언이 가능한데요.

아래와 같이 Food 인터페이스의 타입 파라미터 W 를 반환하는 getWeight() 함수를 선언하겠습니다.

· 인터페이스명 : Food<W>

· 메소드명 : public W getWeight() : 음식 무게 반환 메서드  

interface Food<W>{
	public W getWeight();
}

 

3) 제네릭 타입 상속

제네릭이 아닌 기본 클래스/인터페이스가 상속이 가능한 것처럼 제네릭 타입도 부모-자식간 상속이 가능한데요.

그러나 주의해야 하는 사항이 있습니다. 상속관계에서 자식 제네릭 타입은 부모의 제네릭 타입 파라미터를 반드시 선언해야 합니다. 

 

□  부모 클래스를 상속 받는경우 

public class 클래스명<부모 제네릭 타입, 자식 제네릭 타입> extends 부모 클래스{...}

 

부모 인터페이스를 상속 받는경우 

public class 클래스명<부모 제네릭 타입, 자식 제네릭 타입> implements 부모 인터페이스{...}

 

□ 부모 클래스, 인터페이스를 동시에 상속 받는경우 

아래와 같이 커피(Coffee) 클래스와 푸드(Food) 인터페이스를 상속받는 아이스아메리카노 제네릭 클래스를 선언해주었습니다. 

· 부모 제네릭 클래스 : Coffee <T> 

· 부모 제네릭 인터페이스 : Food <W>

· 자식 제네릭 클래스 : IceAmericano<T,W,C> 

자식 제네릭 클래스는 부모 제네릭 타입인 T, W 는 필수로 선언해주어야 하며, 자식 타입 파라미터인 C를 추가해주었습니다.

[예시] 

public class IceAmericano<T, W, C> extends Coffee<T> implements Food<W> {
  public W weight;
  
  @Override
  public W getFoodWeight() {
    return weight;
  }
  
  public T getCoffeeTaste(T taste) {
    return taste;
  }
  
  public C isCold(C cold) {
    return cold;
  }
}