Java - Generic(제네릭) 1편
1. 서두 개발 혹은 설계를 하면서 자주 사용하지만 한번도 제대로 정리한 적이 없어 Generic 관련 내용을 정리하고자 한다. 2. 제네릭이란? 데이터 형식에 의존하지 않고 여러 데이터 타입을 사용할
yarisong.tistory.com
지난 제네릭 1편에서 기본적인 제네릭의 개념을 확인하였다. 이번 포스팅에서는 제네릭 심화 개념인 제네릭 제약에 대해 알아보자 한다.
1. 제네릭 제약의 필요성
일반적으로 제네릭 타입을 T로 하고 외부클래스에서 호출 시 파라미터를 각각 Integer, String 으로 보내게 되면 T는 보낸 타입에 따라 Integer, String이 된다. 또한, Person이란 클래스를 만들고 T 파라미터를 Person으로 보니면 T는 Person이 된다.
1편에서 이야기한 것과 같이 제네릭은 참조 타입 모두 될 수 있다.
만약, Integer 타입 전용으로 만든 클래스에 문자열 파라미터가 들어가면 어떻게 될까? 컴파일 과정에서 오류가 발생하지 않고 서비스 호출 시점에 오류가 발생하게 된다.
위의 발생하는 오류를 피하고자 제네릭에 제한을 걸어줄 수 있다. 이러한 제약의 기준은 클래스의 상하관계로서 상한선, 하한선을 걸어주게 되는 것이다.
2. 제네릭 제약 방법과 와일드카드
제네릭의 제약 방법은 extends, super, ?(와일드카드) 3가지를 이용할 수 있다.
? : 와일드카드로의 줄임 표현으로 모든 타입이 가능하다는 의미이다.
extends T :
상한 경계로 T와 T의 자손 타입에 대해서만 지정 가능하다.
예를 들어 아래 상속관계를 그린 아래 이미지를 보자. T를 Food class로 지정 시 Food 클래스를 상속한 Water, Pizza, HawaiiPizza, CombiPizza 모두 사용가능하다.
super T :
하한 경계로 T와 T의 부모 타입에 대해서만 지정 가능하다.
extends의 예시와 동일하게 아래 이미지를 보면 T를 Pizza로 지정 시 Pizza, Food만 사용이 가능하고 HawaiiPizza, CombiPizza 의 경우 자식 타입이므로 사용이 불가하다.
3. 제네릭의 사용 - extends
extends는 <K extends T>, <? extends T> 2가지 형태로 사용이 된다. 2개의 사용법과 의미는 각각 다르게 사용된다.
<K extends T>
- 타입 파라미터에 지정되는 구체적인 타입을 제한할 경우에 사용된다.
- 클래스 선언시, 그리고 메소드의 타입파라미터에 사용이 가능하다.
- 위, 2번에서 설명한 것과 같이 T와 T의 자손 타입에 대해서만 가능하도록 제한을 한다.
//class 타입 형태로 사용
public Class MakeFood<T extends Food>{ ... }
// 메소드의 타입 파라미터 형태로 사용
public <T extends Number) int add(T t){ ...{
(메소드의 매겨변수 사용 시에는 에러가 발생한다.)
<? extends T>
참조형 매개변수의 자료형을 T와 T의 자손 타입에 대해서만 가능하도록 제한을 한다.
메소드의 매개변수, 리턴타입에 사용가능하다.
// 메소드의 매겨변수에 사용
public void printData(List<? extends Food> list){ ... }
// 메소드의 리턴타입에 사용
public List<? extends Food> printData() { ... }
4. 제네릭의 사용 - super
super는 <? super T> 형태로만 사용이 가능하다. <K super T>는 사용이 불기하다.
<? super T>
참조형 매개변수의 자료형을 T와 T의 보모 타입에 대해서만 가능하도록 제한을 한다.
extends와 동일하게 매개변수, 리턴타입에 사용가능하다
5. 제네릭 제약 예제 - extends, super
예제를 통해 제네릭 제약을 살펴보자
간단하게 음식 클래스를 생성하고 음식 종류별로 상위 개념을 상속 받는 형태이다.
상속 관계는 아래 이미지와 같다.
각각의 부모 클래스와 자식 클래스들은 아래와 같다.
<? extends T>, <K extend T> 테스트를 위해 메소드와 클래스를 다음과 같이 구현했다.
<K extends T> : MakeFood 클래스에서 제네릭 타입으로 <T extends Food> 를 사용하였다.
<? extends T> : WildTest 클래스에서 각각의 데이터를 출력하기 위한 printData메소드를 생성.
제네릭 제약으로 <? extends Pizza> 사용.
<? super T> : WildTest 클래스에서 각각의 데이터를 출력하기 위한 printData2메소드를 생성.
제네릭 제약으로<? super Pizza> 사용.
// Food Class
public abstract class Food {
String name;
String price;
}
//Food를 부모로 하는 Pizza Class
public class Pizza extends Food {
private String kind;
public Pizza(String name){
this.name = name;
}
public Pizza(String name, String price, String kind){
this.name = name;
this.kind = kind;
this.price = price;
}
@Override
public String toString() {
return "Pizza{" +"name='" + name + '\'' +'}';
}
}
//Pizza를 부모로하는 하와이 피자 Class
public class CombiPizza extends Pizza{
public CombiPizza(String name, String price, String kind) {
super(name, price, kind);
}
public CombiPizza(String name) {
super(name);
}
@Override
public String toString() {
return "CombiPiazza{" +"name='" + name + '\'' +'}';
}
}
//Pizza를 부모로하는 콤비네이션 피자 Class
public class HawaiiPizza extends Pizza{
public HawaiiPizza(String name, String price, String kind) {
super(name, price, kind);
}
public HawaiiPizza(String name) {
super(name);
}
@Override
public String toString() {
return "HawaiiPizza{" +"name='" + name + '\'' +'}';
}
}
//Food를 상속받는 Water Class
public class Water extends Food{
public Water(String name){
this.name = name;
}
public Water(String name, String price){
this.name = name;
this.price = price;
}
@Override
public String toString() {
return "Water{" +"name='" + name + '\'' +'}';
}
}
public class WildTest {
public static void main(String args[]){
List<Pizza> pizzaList = new ArrayList<>();
pizzaList.add(new Pizza("pizza"));
printData(pizzaList);
// 출력 결과 -> Pizza{name='pizza'}
printData2(pizzaList);
// 출력 결과 -> Pizza{name='pizza'}
List<HawaiiPizza> hawaiiPizzaList = new ArrayList<>();
hawaiiPizzaList.add(new HawaiiPizza("hawaii"));
printData(hawaiiPizzaList);
// 출력 결과 -> HawaiiPizza{name='hawaii'}
//printData2(hawaiiPizzaList); <- super 제한으로 오류 발생!!
List<CombiPizza> combiPiazzaList = new ArrayList<>();
combiPiazzaList.add(new CombiPizza("Combination"));
printData(combiPiazzaList);
// 출력결과 -> CombiPiazza{name='Combination'}
List<Water> waterList = new ArrayList<>();
waterList.add(new Water("Arisu"));
waterList.add(new Water("Isis"));
//printData(waterList);// <- 오류 발생!!
MakeFood<Water> mf = new MakeFood();
mf.setFood(new Water("Arisu-1"));
mf.toMakeFood();
// 결과 -> -------------- Make Food => T extends Food -------------------
// Water{name='Arisu-1'}
}
/*
* 와일드 카드(?)를 이용하여 Pizza 클래스를 하위로 제한
* 매개변수로 가능한 형태는 Pizza class, 그리고 Pizza클래스를 상속한 자식 Class만 가능하다
*/
public static void printData(List<? extends Pizza> list){
for(Food obj : list){
System.out.println(obj);
}
}
/*
* 와일드 카드(?)를 이용하여 Pizza 클래스를 상위로 제한
* 매개변수로 가능한 형태는 Pizza class, 그리고 Pizza클래스의 부모 Class만 가능하다
*/
public static void printData2(List<? super pizza> list){
for(Food obj : list){
System.out.println(obj);
}
}
}
/*
* 음식 생성 클래스
* 제네릭 제한으로 Food Class와 Food Class를 상속 받는 자식 Class만 가능하다
*/
public class MakeFood<T extends Food> {
private T Food;
public void setFood(T Food){
this.Food = Food;
}
public void toMakeFood(){
System.out.println("-------------- Make Food => T extends Food -------------------");
System.out.println(Food.toString());
}
}
'Backend > Java' 카테고리의 다른 글
객체간 맵핑을 편리하게 하기 위한 Mapstruct (0) | 2023.12.13 |
---|---|
Java - Generic(제네릭) 1편 (0) | 2022.04.22 |
JAVA 버전별 특징(Java7~10) (0) | 2022.03.10 |
래퍼 클래스(wrapper class)? (0) | 2022.03.03 |
인터페이스를 사용하는 이유 (0) | 2016.12.13 |