본문 바로가기
개발 지식/Java

[Java] 제네릭(Generic)이 무엇일까 그리고 활용법

by 에르주 2022. 1. 9.
반응형

애플리케이션 외부 데이터를 불러오기 위해 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의 객체를 매개변수로 받아 출력해보려고 한다.

아앗,, 매개변수가 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();

generic 출력 결과

 


추가로 <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가 무엇이냐

 

단순하게 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 2개의 값

 


추가해서 <?> 와 같은 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

 

GitHub - pminsu01/JavaStudy: 자바 자체 스터디

자바 자체 스터디. Contribute to pminsu01/JavaStudy development by creating an account on GitHub.

github.com

 

반응형

댓글