infinity : 무한한 성장가능성
자바 제네릭 완전정복 (1) 본문
자바 제네릭을 다루게 된 이유?
스프링 배치를 개발하던 중 MyBatisItemWriterBuilder을 사용해서 MyBatisBatchItemWriter 객체를 만들 때 파라미터의 값을 기본 Item 값에서 추가해야 하는 경우가 있었다.
MyBatisWriterBuilder의 itemToParameterConverter 을 통해 파라미터를 수정할 수 있었는데, 해당 메서드의 내부를 살펴보니
public MyBatisBatchItemWriterBuilder<T> itemToParameterConverter(Converter<T, ?> itemToParameterConverter) {
this.itemToParameterConverter = itemToParameterConverter;
return this;
}
제네릭이 보이는 것이다..!
위의 코드가 하나도 이해되지 않아, 제네릭을 공부하고 위의 메서드를 분석해보고자 한다.
(자바를 예전에 공부할때 제네릭을 분명 공부했었는데, 까먹어 버렸다.. 코드를 살펴볼 때 자주 등장하는 제네릭인만큼 이번에 완벽히 이해하고 넘어가고자 한다.)
자바 제네릭 이란?
제네릭: 클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법 즉 인스턴스를 생성할때 타입을 결정한다.
public class Animal<T> {
public T info;
}
예를들어, 이렇게 Ainimal 클래스를 만들 때 클래스 내부에서 사용되는 info의 타입을 Ainimal 객체 생성 시 결정하는 것이다.
public class Main{
public static void main(String[] args){
Animal<String> animal = new Animal<>();
}
}
new Animal<String>(); 으로 객체 생성 시 info의 타입은 String으로 결정된다.
위에서 new Animal<String> 에서 String 이 생략된 이유는 좌측의 Animal <String> animal에서 제네릭 타입을 추론할 수 있기 때문에 생략할 수 있다.
제네릭이 등장한 이유?
제네릭이 없는 경우를 먼저 생각해보자
public class Animal {
private Object info;
public Animal(Object info) {
this.info = info;
}
public Object getInfo() {
return info;
}
}
info에 여러 동물의 객체 타입이 올 수 있다면, Object 타입을 쓰는 방법밖에 없다.
public class Main{
public static void main(String[] args){
Animal animal = new Animal(new DogInfo("솜", "3"));
DogInfo dogInfo = (DogInfo) animal.getInfo();
dogInfo.print();
}
}
public class DogInfo {
private String name;
private String age;
public DogInfo(String name, String age) {
this.name = name;
this.age = age;
}
public void print() {
System.out.println("name: " + name + "age: " + age);
}
}
Animal 클래스에서 info 에 들어간 동물 클래스 객체를 가져와 해당 동물 클래스의 메서드를 호출하고 싶다면?
👉 animal 클래스에서 info 필드는 Object 타입이기 때문에 info에 할당된 객체를 가져와 메서드를 호출하려면 할당된 객체타입으로 형변환을 하는 방법밖에 없다.
위의 코드에서는 두 가지 문제점이 존재한다.
1) TypeSafe 하지 않음
public static void main(String[] args){
Animal animal = new Animal(new DogInfo("솜", "3"));
CatInfo dogInfo = (CatInfo) animal.getInfo();
dogInfo.print();
}
animal 인스턴스에 들어간 info type 은 DogInfo인데 개발자가 실수로 CatInfo 타입으로 형변환을 한다면?
런타임에 애러가 발생한다.
컴파일 시에 해당 오류를 발견하지 못한다.
제일 좋은 오류는 컴파일 오류다...!
2) 불필요한 형변환
위의 main 메서드에서 dogInfo.print() 메서드를 호출하기 전 형변환을 통해 info 객체 값을 가져오는 것을 볼 수 있다.
info 타입에 할당된 인스턴스에 메서드를 호출하려면 형변환을 해줘야 한다는 것이다.
제네릭을 사용한다면?
public class Animal<T> {
private T info;
public Animal(T info) {
this.info = info;
}
public T getInfo() {
return info;
}
}
public class Main{
public static void main(String[] args){
Animal<DogInfo> animal = new Animal<>(new DogInfo("솜", "3"));
DogInfo dogInfo = animal.getInfo();
dogInfo.print();
}
}
위의 두 가지 문제가 해결된 것을 볼 수 있다.
Animal 객체 생성시점에 info의 타입을 결정하기 때문에 불필요한 형변환을 할 필요가 없고, Type Safe 하다.
제네릭의 타입과 개수
위의 코드에서 들어간 제네릭 타입을 보면 T 만 들어간 것을 볼 수 있는데,
<> 안에 들어가는 알파벳은 T, E, K 등 아무거나 상관없지만 대문자 알파벳 한글자라는 것을 지켜주면 된다.
위 규칙을 지키지 않는다고 에러가 발생하는 것은 아니지만 개발자들 사이에서의 관례로 지켜주는 것이 좋다.
public class Animal<T,K> {
private T info;
private K ancestor;
public Animal(T info, K ancestor) {
this.info = info;
this.ancestor = ancestor;
}
}
위 코드처럼 제네릭 타입 파라미터는 여러 개가 가능하다.
지금까지 다룬 지식만으로 맨 위 코드를 분석할 수 있을까?
아니다.. ㅎ 제네릭 메서드와 와일드카드(?)가 남았다..!!!
다음 포스팅에서 제네릭 메서드와 와일드카드의 개념에 대해 학습하고, 맨 위의 코드를 분석하고자 한다.
참고
'Develop > 💜Java' 카테고리의 다른 글
MapStruct 에 대해 알아보자 (0) | 2025.01.19 |
---|---|
ModelMapper 에 대해 알아보자 (0) | 2025.01.05 |
자바 제네릭 완전정복 (2) (0) | 2024.02.18 |