12. Java - 제네릭(Generic)
안녕하세요 오늘은 제네릭에 대해서 공부를 해보려고 합니다. 코드를 짜다보면 항상 저는 코드가 지저분하고 길게 써서 그런가 가독성이 떨어지더라고요.. 그래서 이번에 제네릭을 이용해서 가독성도 높이고 코드의 재사용성도 높이는 그런 공부를 해보려고합니다..!

(그럼 스타트!)
✅ 00. 제네릭(Generic)
제네릭이란?
제네릭이란 결정되지 않은 타입을 파라미터로 처리하고 실제 사용할 때 파라미터를 구체적인 타입으로 대체시키는 기능입니다.

<T>는 T가 타입 파라미터임을 뜻하는 기호로, 타입이 필요한 자리에 T를 사용할 수 있습니다.
✅ 01. 제네릭 타입
제네릭타입이란?
제네릭 타입은 결정되지 않은 타입을 파라미터로 가지는 클래스와 인터페이스를 말합니다. 제네릭 타입은 선언부에 '< >' 부호가 붙고 그 사이에 타입 파라미터들이 위치합니다.
public class 클래스명<A, B, ...>(...)
public interface 인터페이스명<A, B, ...>(...)
타입 파라미터는 변수명과 동일한 규칙에 따라 작성할 수 있지만 일반적으로 대문자 알파벳 한글로 표현합니다.

다음예제는 인터페이스에서의 제네릭 타입입니다. 다양한 대상을 렌트하기 위해 rent() 메소드의 리턴 타입을 타입 파라미터로 선언했습니다.

그 다음으로, 렌트 대상인 Home과 Car 클래스를 작성했습니다.

이제 HomeAnecy와 CarAgency는 Home과 Car 클래스를 렌트해주는 대리점 클래스로, Rentable의 타입 파라미를 대체해서 구현했습니다.

이처럼 결과를 보면 HomeAnecy와 CarAgency에서 대여한 Home과 Car를 불러올 수 있습니다. 따라서 제네릭을 사용하면 다음과 같은 장점이있습니다.
제네릭을 사용한 이유 요약
· 타입 안전성: Rentable<Home>과 Rentable<Car>는 각각 Home과 Car 타입을 정확히 반환하게 하여 타입 오류를 방지합니다.
· 코드 재사용성: Rentable<T> 인터페이스를 통해 HomeAgency와 CarAgency가 동일한 구조의 코드를 공유할 수 있습니다.
· 유연성: 하나의 인터페이스를 통해 여러 가지 타입을 다룰 수 있으며, 새로운 타입을 추가할 때 기존 코드를 크게 변경하지 않고도 쉽게 확장할 수 있습니다.
✅ 02. 제네릭 메소드
제네릭메소드란?
제네릭 메소드는 타입 파라미터를 가지고 있는 메소드를 말합니다. 타입 파라미터가 메소드 선언부에 정의된다는 점에서 제네릭 타입과 차이가 있습니다.

타입 파라미터 T는 매개값이 어떤 타입이냐에 따라 컴파일 과정에서 구체적이 타입으로 대체됩니다.

다음 boxing() 메소드는 타입 파라미터로 정의하고 매개변수 타입과 리턴 타입에서 T를 사용합니다.
결과값을 보면 다음과 같이 출력됩니다.
· 100의 클래스 타입이 Integer이므로 타입 파라미터 T는 Integer로 대체되어 리턴됩니다.
· "홍길동"의 클래스 타입이 String이므로 타입 파라미터 T는 String로 대체되어 리턴됩니다.

✅ 03. 제한된 타입 파라미터
제한된 타입인 파라미터
경우에 따라서는 타입 파라미터를 대체하는 구체적인 타입을 제한할 필요합니다. 예를 들어 숫자를 연산하는 제네릭 메소드는 대체 타입으로 Number 또는 자식 클래스(Byte, Short, Integer, Long , Double)로 제한할 필요가 있습니다.
public <T extends 상위타입> 리턴타입 메소드(매게변수, ...) {...}
다음은 Number 타입과 자식 클래스 (Byte, Short, Integer, Long , Double)에만 대체 가능한 타입 파라미터를 정의한 것입니다.
public <T extends Number> boolean compare(T t1, T t2) {
double v1 = t1.doubleValue();
double v2 = t2.doubleValue();
return(v1 == v2);
}

package GenericMethod;
public class GenericExample {
//제한된 타입 파라미터를 갖는 제네릭 메소드
public static <T extends Number> boolean compare(T t1, T t2) {
//T의 타입을 출력
System.out.println("compare(" + t1.getClass().getSimpleName() + ", " +
t2.getClass().getSimpleName() + ")");
//Number의 메소드 사용
double v1 = t1.doubleValue();
double v2 = t2.doubleValue();
return (v1 == v2);
}
public static void main(String[] args) {
//제네릭 메소드 호출
boolean result1 = compare(10, 20);
System.out.println(result1);
System.out.println();
//제네릭 메소드 호출
boolean result2 = compare(4.5, 4.5);
System.out.println(result2);
}
}
이 예제는 Number를 상속하는 타입(Integer, Double, Float 등)만 사용할 수 있는 제한된 제네릭 메소드를 정의하여 두 숫자의 값을 비교하는 방법을 보여줍니다. 제네릭 메소드 compare는 타입 파라미터 T를 받아 두 값을 입력받고, 이를 double 타입으로 변환하여 비교합니다. 또한, 메소드 호출 시 입력된 인수의 타입을 출력합니다. 메인 메소드에서는 Integer와 Double 타입의 값을 전달하여 제네릭 메소드를 호출하고, 각각의 비교 결과를 출력합니다. 이 예제를 통해 제네릭 메소드의 유연성과 타입 안전성을 이해할 수 있습니다.
✅ 04. 와일드카드 파라미터
와일드카드 파라미터란?
제네릭 타입을 매개값이나 리턴 타입으로 사용할 때 타입 파라미터로 ?(와일드카드)를 사용할 수 있습니다. ?는 범위에 있는 모든 타입으로 대체할 수 있다는 표시입니다.

타입 파라미터의 대체 타입으로 Student와 자식 클래스인 HighStudent와 MiddleStudent만 가능하도록 매개변수를 다음과 같이 선언할 수 있습니다.
리턴타입 메소드명(제네릭타입<? extends Student> 변수) {...}
반대로 Worker와 부모 클래스인 Person만 가능하도록 매개변수를 다음과 같이 선언할 수도 있습니다.
리턴타입 메소드명(제네릭타입<? super Worker> 변수) {...}
어떤 타입이든 가능하도록 매개변수를 선언할 수도 있습니다.
리턴타입 메소드명(제네릭타입<?> 변수) {...}
