우선은 기본적으로 '제네릭(Generic)' 타입이 무엇인지를 알고 넘어가야 합니다. 제네릭은 컬렉션부터 람다식, 스트림, NIO 등 자바의 intermediate 이상의 레벨에서 학습하는 내용에 빠지지 않고 등장하기 때문이죠. API 문서에서도 자주 사용하기 때문에 알아두지 못하면 JAVA를 공부하거나 개발 과정에서 꽤나 곤란한 상황을 겪게 됩니다.
기본적으로 제네릭(Generic)은, "타입"을 컨트롤하는 개념입니다. 그래서 제네릭을 사용하게 되면 다음 두 가지 장점을 갖게 됩니다. 첫 번째로 컴파일 과정에서 타입 체크를 강력하게 할 수 있다는 점입니다. 실행되고 에러가 발생하느니, 컴파일 레벨에서 미리 문제를 해결하는 게 낫다는 것이죠. 두 번째로는 불필요한 타입 변환(casting)을 제거할 수 있다는 점입니다.
두 번째 요소에 대한 예시를 살펴볼까요? List 타입 인스턴스에 String을 저장했을 경우, String 변수에 List 타입에서 get() 함수를 사용해 값을 불러와 저장하려면 get()을 사용할 때 캐스팅을 진행해야 합니다. 아래 예제 코드를 보겠습니다.
List list = new ArrayList();
list.add("Hello World");
String str = (String) list.get(0);
위의 경우, 우리가 암묵적으로 list에는 'String만 넣겠다'라고 약속을 하더라도 String 이외의 데이터가 들어갈 수 있습니다. 그래서 반드시 캐스팅을 진행해야 String 타입 변수에 데이터를 get 할 수 있습니다. 하지만 아래의 제네릭을 사용한 경우를 보겠습니다.
List<String> list = new ArrayList<String>();
list.add("Hello World");
String str = list.get(0);
위의 케이스의 경우, list에 저장되는 요소에 대해서 제네릭의 기능을 통해 String 타입으로 애초에 강제가 되었습니다. 그러므로 list에서 get 메서드를 사용하게 될 때 굳이 String 타입으로 캐스팅을 시키지 않아도 되는 것이지요.
○ 제네릭 타입( class<T>, interface<T> )
일단 '제네릭 타입'은, "타입을 파라미터로 가지는 클래스와 인터페이스"를 의미합니다. 이 제네릭 타입의 사용 방식은 아래의 예시와 같이, 클래스 or 인터페이스 이름 뒤에 <T>와 같은 형태로 타입 파라미터를 부여하는 형태입니다. 일반적으로 타입 파라미터는 'T'와 같이 대문자 한 글자로 표현하는 것이 관례입니다.
public class ClassName<T> { ... }
public interface InterfaceName<T> { ... }
실제 자바 코드를 작성할 때는 이 <T>에 데이터 타입을 지정해 작성하게 됩니다. 그런데 일단, 현재까지의 설명만으로는 어떻게 사용하고, 왜 이 제네릭 타입을 사용하는지 잘 와닿지 않습니다. 그럼 이제부터 하나씩 살펴보도록 하겠습니다.
일단 아래와 같이 Box라는 클래스가 있다고 가정해 보겠습니다. 필드에는 Object 타입 변수 object가 선언되고, 각각 setter와 getter가 선언됩니다.
public class Box {
private Object object;
public void set(Object object) {
this.object = object;
}
public Object get() {
return object;
}
}
위의 Box 클래스에서는 Object 타입의 필드를 이용합니다. setter를 통해서는 Object 타입 객체를 받아 대입을 진행하고, getter를 이용해 해당 필드를 리턴하는 형태로 메서드가 선언되어 있습니다.
우리가 잘 알다시피, Object는 자바에서 최상위 클래스입니다. 그래서 어떠한 타입의 객체도 대입이 가능합니다. 만일 Object 타입의 필드 object에 어떠한 객체를 대입하게 된다면, Object는 모든 타입의 최상위 부모 객체이므로 자연스럽게 자동 형변환이 일어나게 되면서 Object 타입의 데이터로 저장됩니다.
이제 이런 상태에서 get() 메서드를 이용해 object 필드를 리턴하게 되면, 자연히 Object 타입으로 데이터가 리턴됩니다. 하지만, 만일 get()으로 리턴한 값을 해당 원래의 데이터 타입의 변수에 저장하게 된다면, 결국 캐스팅이 진행되어야 합니다.
아래와 같이, Apple이라는 임의의 클래스도 있다는 가정하에 실행 클래스까지 작성해 실행해 보겠습니다.
public class Apple {
}
public class ExampleMain {
public static void main(String[] args) {
Box box = new Box();
box.set("테스트");
String name = (String) box.get();
System.out.println(name);
box.set(new Apple());
Apple apple = (Apple) box.get();
}
}
/* 출력
테스트
*/
위에서 보시다시피 get()을 사용할 때는 원래의 데이터 타입으로 캐스팅을 진행하였습니다. 이제 이를 다음 아티클에서 제네릭을 사용하여 표현해 보겠습니다.
'Programming > JAVA' 카테고리의 다른 글
제네릭(2) - 멀티타입 파라미터 (0) | 2023.04.26 |
---|---|
제네릭(1) - 제네릭 타입 2 (0) | 2023.04.25 |
중첩 클래스&인터페이스(5) - 익명 객체 7 (0) | 2023.04.23 |
중첩 클래스&인터페이스(5) - 익명 객체 6 (0) | 2023.04.23 |
중첩 클래스&인터페이스(5) - 익명 객체 5 (0) | 2023.04.20 |