애플리케이션 외부 데이터를 불러오기 위해 API 호출이나 SDK 그리고 라이브러리를 사용하다보면 제네릭 클래스를 활용하는 것을 많이 볼 수 있다.
이번 포스팅은 제네릭을 활용하는 이유와 간단한 예시를 통해 이야기해보고자 한다.
자바 개발자라면 모두 알다시피 제네릭 클래스를 활용하는 이유는 다음과 같다.
메소드와 클래스의 매개변수를 나중에 확정한다.
즉 자바의 타입 안정을 보장한다.
더 자세한 부분은 코드로 이야기해보자.
public class GenericEx {
public static void main(String[] arg){
IntPrintGeneric intPrintGeneric = new IntPrintGeneric(400);
intPrintGeneric.GenericPrint();
}
static class IntPrintGeneric {
int genericNum;
public IntPrintGeneric(int genericNum){
this.genericNum = genericNum;
}
public void GenericPrint(){
System.out.println("GenericNum : " + genericNum);
}
}
}
/*
결과값:
GenericNum : 400
*/
이 코드는 간단하다 IntPrintGeneric Class를 생성하였고 변수형이 int인 genericNum값을 받아 콘솔창에 보여준다.
그렇다면 String 또는 Double 값을 받아 Print해주는 메소드는 어떻게 구현해야 할까?
간단히 생각하자면 다음과 같이 개발을 할 수 있다.
public class GenericEx {
public static void main(String[] arg){
IntPrintGeneric intPrintGeneric = new IntPrintGeneric(400);
intPrintGeneric.GenericPrint();
DoublePrintGeneric doublePrintGeneric = new DoublePrintGeneric(29.99);
doublePrintGeneric.GenericPrint();
StringPrintGeneric stringPrintGeneric = new StringPrintGeneric("LGU+");
stringPrintGeneric.GenericPrint();
}
// Int Print
static class IntPrintGeneric {
int genericNum;
public IntPrintGeneric(int genericNum){
this.genericNum = genericNum;
}
public void GenericPrint(){
System.out.println("GenericNum : " + genericNum);
}
}
// Double print
static class DoublePrintGeneric{
Double genericDouble;
public DoublePrintGeneric(Double genericDouble){
this.genericDouble = genericDouble;
}
public void GenericPrint(){
System.out.println("GenericNum Double : " + genericDouble);
}
}
// String Print
static class StringPrintGeneric {
String genericString;
public StringPrintGeneric(String genericString){
this.genericString = genericString;
}
public void GenericPrint(){
System.out.println("GenericNum String : " + genericString);
}
}
}
/*
결과값 :
GenericNum : 400
GenericNum Double : 29.99
GenericNum String : LGU+
*/
메소드 안의 내부 로직은 유사하지만 변수타입이 모두 다르기 때문에 총 3가지의 메소드를 만들어 냈다.
이는 코드내 복잡성을 높일 뿐만 아니라 중복 사용하는 코드들이 있어 바람직하지 못하다.
이를 Generic Class를 통해 하나의 메소드 합칠 수 있다.
public class GenericEx {
public static void main(String[] arg){
PrintGeneric printGeneric1 = new PrintGeneric(400);
printGeneric1.GenericPrint();
PrintGeneric printGeneric2 = new PrintGeneric(29.99);
printGeneric2.GenericPrint();
PrintGeneric printGeneric3 = new PrintGeneric("LGU+");
printGeneric3.GenericPrint();
}
static class PrintGeneric <T>{
T genericT;
public PrintGeneric(T genericT) {
this.genericT = genericT;
}
public void GenericPrint(){
System.out.println("GenericNum : " + genericT);
}
}
/*
결과값 :
GenericNum : 400
GenericNum : 29.99
GenericNum : LGU+
*/
PrintGeneric 클래스가 <T> Generic 선언하게 되면 파라미터값마다 메소드를 개발할 필요가 없으며
해당 메소드 사용시 개발자가 변수 타입을 직접 지정할 수 있다.
그럼 매개변수 값이 임의의 클래스를 상속받은 객체가 와야 하는 경우 어떻게 될까?
///////////////// Eat 클래스 /////////////////
public class Eat {
int kcal;
String kindValue;
public Eat(int kcal, String kindValue) {
this.kcal = kcal;
this.kindValue = kindValue;
}
// Generic 1개 Argument
public <T extends Eat> void printEat(T genericT) {
System.out.println(genericT.kcal);
System.out.println(genericT.kindValue);
}
}
///////////////// Pizza 클래스 /////////////////
public class Pizza extends Eat{
public Pizza(int kcal, String amount) {
super(kcal, amount);
}
}
///////////////// Rice 클래스 /////////////////
public class Rice extends Eat{
public Rice(int kcal, String amount) {
super(kcal, amount);
}
}
나는 예를 들어 Eat라는 클래스를 상속받는 Rice와 Pizza의 클래스를 생성했고
static class PrintGeneric <T extends Eat & Serializable> {
T genericT;
public PrintGeneric(T genericT) {
this.genericT = genericT;
}
public void GenericPrint(){
System.out.println();
System.out.println("===== Generic Print =====");
System.out.println("클래스명 : " + genericT.getClass().getName());
System.out.println("칼로리 : " + genericT.kcal);
System.out.println("음식의 종류 : " + genericT.kindValue);
}
}
}
Eat의 객체를 매개변수로 받아 출력해보려고 한다.
앗, 기존에 int, Double, String값을 매개변수로 받았던 PrintGeneric Class 호출 시 오류가 난다.
PrintGeneric Class에서 T값을 Eat 상속 객체로 설정했기 때문이다.
그러므로 다음과 같이 Pizza 및 Rice 객체를 생성자로 호출하여 변수를 설정해야 한다.
PrintGeneric<Pizza> pizzaPrintGeneric = new PrintGeneric(new Pizza(500, "고르곤 졸라 피자"));
pizzaPrintGeneric.GenericPrint();
PrintGeneric<Rice> ricePrintGeneric = new PrintGeneric(new Rice(300,"갓 지은 햇반"));
ricePrintGeneric.GenericPrint();
추가로 <T, V> 와 같은 Generic이 한개가 아닌 두개의 Argument값으로 받아 처리하는 것도 가능하다.
// main class
String genericTString = doubleParam("minsupark", "erjuer01");
System.out.println("Generic T의 값은 : " + genericTString);
// Generic 2개를 Argument값으로 받는 method
// 그리고 Generic T를 반환한다.
// 오류가 발생한다.
private static T doubleParam(T genericT, V genericV) {
System.out.println(genericT);
System.out.println(genericV);
return genericT;
}
단순하게 T, V 이렇게 Argument값으로 구현하게 되면 Java에서는 T가 Generic인지 아니면 어떤 다른 값인지 확인할 수 없기 때문에
// Generic 2개를 Argument값으로 받는 method
// 그리고 Generic T를 반환한다.
private static <T, V> T doubleParam(T genericT, V genericV) {
System.out.println(genericT);
System.out.println(genericV);
return genericT;
}
메소드 이름 앞에 <T, V>를 통해 T값이 Generic이라는 것을 명시해야한다.
Run 돌려보면 다음과 같이 정상적으로 표시 된다.
추가해서 <?> 와 같은 Generic 표현식을 쓰는 경우도 있는데 다음과 같은 예시를 통해 간단히 살펴보자.
말 그대로 Question! 어떤값이 올지 모른다는 것이다.
// main Method
List<String> stringList = Arrays.asList("2022년" ,"새해 복", "많이 받았으면" ,"좋겠네");
questionList(stringList);
// print Method
private static void questionList(List<?> list){
System.out.println(list);
}
끝.
!
샘플코드는 여기에..
https://github.com/pminsu01/JavaStudy
'개발 지식 > Java' 카테고리의 다른 글
[Java] The server selected protocol version TLS10 is not accepted by client preferences [TLS13, TLS12] (0) | 2022.03.28 |
---|---|
[Java] 함수형 인터페이스 @FunctionalInterface - 2. Consumer (1) | 2022.03.26 |
[Java] 함수형 인터페이스 @FunctionalInterface - 1. Predicate (0) | 2022.03.11 |
[Optional] orElse와 orElseGet의 차이 (0) | 2021.12.30 |
[Java] 자바란 무엇일까? JVM 메모리, 각 명칭 및 기능 (0) | 2020.07.29 |
댓글