본문 바로가기

Language/JAVA

[JAVA] 제네릭 쉬운 예제로 이해하기

Generic

 

제네릭(Generic)이란?

자바에서 제네릭이란 데이터의 타입을 일반화한다는 것을 의미한다.

쉽게 말하면 T라는 만능 변수(제네릭 변수 타입 변수)에 Integer, String, Double, ... 모든 자료형이 들어올 수 있게 만드는 방법이다.

** 꼭 Wrapper 클래스를 넣어주어야 함에 유의 ! **

 

 

꼭 'T'가 아니라 어떠한 문자를 사용해도 상관없으며, 여러 개의 타입 변수는 쉼표(,)로 구분하여 명시할 수 있다.

타입 변수는 클래스에서뿐만 아니라 메소드의 매개변수나 반환값으로도 사용할 수 있다.

 

 

코드의 유연성의 굉장히 증가하여 이걸 왜 아직까지 안썼을까 하는 생각이 들었던 제네릭

코드를 통해 왜 그런지 알아보자

 

 

 

 

 

제네릭 예제 - a번째 값과 b번째 값 바꾸기

 

일반적으로 배열의 a번째 값과 b번째 값을 바꾸는 코드는 배열의 타입에 맞춰 함수를 만든다.

String 타입의 자리를 바꿔주는 strChange 함수를 구현해보자.

public class C1603GenericMain {
    public static void main(String[] args) {
//        제네릭 메서드 안 쓴 예제
//        자료형에 맞는 메서드를 각각 구현해야 함.
        String[] stArr = {"JAVA", "PYTHON", "C++"};
//        String 타입 자리를 바꿔주는 strChange 메서드
        strChange(stArr, 0, 1);
        System.out.println(Arrays.toString(stArr));
    }


    static void strChange(String[] stArr, int a, int b){
        String temp = stArr[a];
        stArr[a] = stArr[b];
        stArr[b] = temp;
    }
}

 

 

하지만 특정 위치의 두 값을 바꿔주는 작업을 여러 타입에 적용해야 한다면???

int형에 대해서도 a번째 값과 b번째 값을 바꾸고 싶다면???

intChange와 같은 다른 타입에 맞는 함수를 하나 더 만들어야 한다.

 

 

static void intChange(Integer[] intArr, int a, int b){
    Integer temp = intArr[a];
    intArr[a] = intArr[b];
    intArr[b] = temp;
}

 

 

하지만 개발자는 비슷한 코드를 또 작성하는 걸 싫어한다.

코드의 중복을 없애 클린 코드를 만들고 싶어한다.

사실 복붙 한 번이면 10초도 안걸리지만, 모든 타입에 Change 함수를 만들어야 한다면 얘기가 달라진다.

 

 

 

이럴 때 사용하는 것이 바로 제네릭이다.

제네릭은 (1)메소드와 (2)클래스에 사용할 수 있는데, 아래 코드에서는 메소드에 제네릭을 사용해봤다.

💡 메소드에 제네릭 사용하기
public class C1603GenericMain {
    public static void main(String[] args) {
//        자료형에 맞는 메서드를 각각 구현해야 함.
//        제네릭 메서드 생성 : 반환타입 왼쪽에 <T>를 선언
//        제네릭은 객체(Wrapper) 타입이 들어와야함에 유의 ex) Integer, String, ...
        String[] stArr = {"JAVA", "PYTHON", "C++"};
        Integer[] intArr = {1,2,3,4,5};

        System.out.println("-----제네릭 메서드로 swap-----");
        genericSwap(stArr, 1, 2);
        System.out.println("stArr swap 결과 : " + Arrays.toString(stArr));

        genericSwap(intArr, 0, 1);
        System.out.println("intArr swap 결과 : " + Arrays.toString(intArr));
    }
    static <T>void genericSwap(T[] arr, int a, int b){
        T temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }

}

 

기존에 구현했던 함수 형식에서 세 가지를 변경했다.

특정 타입으로 명시되어 있었던 것들을 만능 변수 T로 바꿔주는 작업이다.

 

1. 반환타입 왼쪽에 <T>추가

2. 매개변수를 T 타입 배열로 받아주기

3. 함수 안에서 T 타입의 값을 받는 temp도 T 타입으로 설정

 

 

모든 것을 제네릭 변수 타입인 T로 만들어줌으로써,

arr자리에 String이 넘어오든 Integer가 넘어오든 관계없이 T가 알아서 인식하고 연산을 수행하고 결과를 만들어준다.

증말루 편리하지 않은가?!? 앞으로 여러 가지 타입에 같은 로직을 수행해야 한다면 제네릭을 떠올리기를 바란다.

 

 

 

한 걸음 더 나아가서, 제네릭 클래스도 사용해보자.

💡 클래스에 제네릭 사용하기

 

제네릭 클래스 GenericStudent를 만들자.

쫄지 말자. 코드만 복잡해 보일뿐 어렵지 않다.

사용자의 입력에 따라 학생의 age를 정수형 혹은 문자열로 모두 받을 수 있게 하고 싶다.

 

 

메소드에서는 반환타입 왼쪽(앞쪽)에 <T>를 적어줬었는데

클래스에서는 클래스명 오른쪽(뒤쪽)에 <T>를 적어준다.

 

 

그리고 age는 두 가지 타입을 모두 받을 수 있어야 하니, age의 자료형을 T라고 설정한다.

지금까지 Integer, String이라고 썼듯이 자료형만 T라고 바꿔준다고 생각하면 쉽다.

 

 

** 매개변수로 ()안에 입력되는 타입과, 반환되는 타입 모두 T라고 변경해주어야 한다 **

class GenericStudent<T> {
    String name;
    T age;
    GenericStudent(String name, T age){
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public T getAge() {
        return age;
    }

    public void setAge(T age) {
        this.age = age;
    }
}

 

 

 

제네릭 클래스로 객체를 만들 때에는 클래스명<T>의 T자리에 원하는 타입을 명시해야 한다.

st1은 나이 값을 String 타입으로 설정하고, st2는 Integer 타입으로 설정한 후, 출력해줬다.

설정한 타입에 맞게 값이 들어가고, 출력되는 것을 확인할 수 있다.

GenericStudent<String> st1 = new GenericStudent<>("호두", "2");
GenericStudent<Integer> st2 = new GenericStudent<>("호두", 3);

System.out.println("이름은 : " + st1.getName() + ", 나이는 : " + st1.getAge());
System.out.println(st1.getAge().getClass()); //String 타입

System.out.println("이름은 : " + st2.getName() + ", 나이는 : " + st2.getAge());
System.out.println(st2.getAge().getClass()); //Integer 타입

 

 

 

(+)

추가적으로, 반환해야  하는 타입도 T타입으로 설정해 함수에 들어오는 매개변수 타입에 따라 반환값을 다르게 주고 싶다면

return type에도 T로 지정하면 된다.

출처 : JinHwan Kim

 

public class C1603GenericMain {
    public static void main(String[] args) {
        String strAge = genericAge("20");
        Integer intAge = genericAge(25);

        System.out.println(strAge.getClass());
        System.out.println(intAge.getClass());
    }

    static <T> T genericAge(T age) {
        return age;
    }
}