본문 바로가기

Language/Java

[JAVA] Generic Class

1. 제네릭이란? 

클래스나 매소드의 데이터 타입을 나중에 확정하는 기법이다. 

 

*제네릭의 원리?

코드를 작성하여 실행하기 까지 크게 3가지의 단계로 나뉜다. 

크게 개발자의 소스코드 작성 시점, 컴파일 타임, 런 타임이 있는데, 보통 변수, 매소드, 클래스의 데이터 타입은 소스코드 작성 시 바로 바로 체크한다. 하지만 제네릭을 사용하면 데이터 타입 체크 시간을 컴파일 타임으로 미룰 수 있다. 

2. 제네릭이 필요한 시점. 

같은 기능의 매소드나 클래스 이더라도, 인수로 받고 반환하는 값의 자료형이 다르면, 전부 따로 따로 정의 해줘야 한다. 

매소드의 경우 저번 포스팅의 예시와 같이, Int 배열 전체 순회하며 원소를 프린트 하는 매소드를 String 배열에 관해 쓸 수 없다. 따라서 전부 다 따로 정의 해줘야 한다.

해당 예시에 대한 설명.

[JAVA] Generics :: 정신과 시간의 방 (tistory.com)

 

똑같이 클래스에서도, 인수로 들어오는 값의 자료형에 따라 생성자의 인스턴스 변수를 늘려줘야 하거나, 아니면 자료형에 따라 같은 기능이더라도 새로운 클래스를 만들어 줘야 한다. 

예시는 다음과 같다. 

 

스타벅스에서는 고객이 스타벅스 앱으로 닉네임을 설정하여 놓으면, 커피 준비가 완료됨을 알릴 때, 닉네임을 불러서 안내한다. 닉네임 설정을 안 해놓으면, 대기 번호를 불러서 안내한다.

닉네임은 String 자료형이고, 대기 번호는 int 자료형이므로, 손님을 부른다는 기능은 같지만, 각각 다른 클래스를 만들어 관리 해야한다. 

public class _02_GenericClass {
    public static void main(String[] args) {
        
        CoffeeByNumber c1 = new CoffeeByNumber(33);
        c1.ready();

        CoffeeByNickname c2 = new CoffeeByNickname("유재석");
        c2.ready();
        }
 }
 
 
 //----------------------------------------------- 밑은 따로 정의된 클래스들--------------
 
 public class CoffeeByNickname {
    public String nickname;

    public CoffeeByNickname(String nickname) {
        this.nickname = nickname;
    }

    public void ready(){
        System.out.println("커피 준비 완료 : " + nickname );
    }
}

//------------------------------------------------------------------

public class CoffeeByNumber {
    public int waitingNumber;

    public CoffeeByNumber(int waitingNumber) {
        this.waitingNumber = waitingNumber;
    }

    public void ready() {
        System.out.println("커피 준비 완료 : " + waitingNumber);

    }
}

 

(1) 제네릭을 쓰지 않는 해결 방법 

그렇다면 제네릭을 쓰지 않고 해당 코드 중복을 없앨 방도가 없을까? 

있다. Object 클래스를 사용하면 된다. Object 클래스는 모든 클래스의 조상이 되는 클래스 이므로, 다형성을 이용해 클래스 내 인스턴스 변수를 선언하면, 어떤 생성자안에 어떤 자료형의 값도 받을 수 있다. 

 

**참고**

다형성이란 하나의 객체가 여러가지 타입을 가질 수 있는 것을 말하고, Java에서는 부모 클래스의 참조변수가 자식 클래스의 객체를 참조할 수 있는 것으로 구현 해놓았다. 

 

따라서 Object 클래스의 참조변수는 어떠한 값이든 참조할 수 있다. 

이를 이용해 새로운 클래스를 짜보자. 

//object 클래스 이용

public class CoffeeByName {

	// Object를 자료형으로 변수 선언
    //해당 값의 인수로는 Integer, Double, String, 예전에 만든 BlackBox 클래스도 전부 가능
    public Object name;

    public CoffeeByName(Object name) {
        this.name = name;
    }

    public void ready(){
        System.out.println("커피 준비 완료 : " + name);
    }
}
//main 매소드 안

// object로 정의된 name이라는 변수안에는 int 값도, String 값도 모두 넣을 수 있다.

CoffeeByName c3 = new CoffeeByName(34);
c3.ready();

CoffeeByName c4 = new CoffeeByName("박명수");
c4.ready();

(2) 문제점

하지만 Object 클래스를 이용해도 무적이 아니다. 다형성의 예시에서 부모 클래스의 참조변수가 자식 클래스의 객체를 참조 할 수 있다고 했지, 그 반대는 안된다. 

따라서 이미 Object 자료형으로 저장된 34 나 "박명수"라는 값을 다시 불러와 int 변수와 String 변수로 넣을 수 없다!

따라서 object로 저장된 값을 다시 원래의 자료형의 변수에 넣을 때는 형 변환이 필요하다. 

int c3Name = (int)c3.name;
System.out.println("주문 고객 번호 : " + c3Name);
String c4Name = (String) c4.name;
System.out.println("주문 고객 이름 : " + c4Name);

또한 제네릭과 다르게, 형 변환은 해당 변환이 맞는 변환인지 아닌지 컴파일 시간에 체크 해주지 않는다. 따라서 만약 틀린 형 변환을 했다면 run 타임 시 에러가 난다. 

//실행할 수 없는 문장임에도 오류를 안 알려줘서 코드 실행 전까지 오류인지 알 수 없다.
// int 값을 String으로 변환했다. 하지만 int 값은 String으로 변환할 수 없다.

//c4Name = (String) c3.name;

해당 내용 실행 시 뜨는 내용

3. 제네릭 클래스 

위의 문제들을 전부 해결할 수 있는 것이 제네릭 클래스나 매소드이다. 제네릭은 매소드나 클래스의 데이터 타입을 컴파일 시간에 체크하여 확정하기 때문에, 위와 같은 에러가 뜨지 않는다.

(1) 제네릭 클래스 선언

클래스 이름 옆에 꺽새로 쓰는 것 말고는 모두 원래 자료형이 적혀야 하는 위치에 쓰면 된다.

 

접근 제어자 class 클래스 이름 <T> {

            접근제어자 T 변수;

             

            // 생성자

            접근 제어자  클래스 이름 (T  인수이름){

                        this.변수이름 = 인수이름;

    }

}

 

예시)

public class Coffee <T>{
    //어떤 타입인지 모르니까 T 타입으로 받음.
    public T name;

    public  Coffee(T name) {
        this.name = name;
    }

    public void ready() {
        System.out.println("커피 준비 완료 : " + name);
    }

}

(2) 제네릭 클래스 객체 생성

클래스이름<확정시킬 자료형> 객체 이름 = new 클래스이름<>(생성자 인수 필요하면 인수 값 넣기);

Coffee<Integer> c5 = new Coffee<>(35);

Coffee<String> c6 = new Coffee<>("조세호");

해당 클래스의 자료형들은 컴파일 시간에 각 각 int와 String으로 확정이 되므로, 이후 문장들에서 해당 값들을 다른 변수에 대입 할 때 형 변환은 필요 없다. 

 

//각각 c5, c6 객체의 인스턴스 변수
int c5Name = c5.name;
String c6Name = c6.name;

4. 점검

(1)제네릭이란? (코드 작성부터 실행까지 3단계 나눠서 설명)

(2)제네릭을 써야 하는 상황

(3)제네릭을 쓰지 않고 문제 해결법, 파생된 문제점

(4)제네릭 클래스의 선언과 객체 생성

'Language > Java' 카테고리의 다른 글

[JAVA] Wrapper Class  (0) 2023.02.07
[JAVA]Generics 심화  (0) 2023.02.06
[JAVA] Generics  (0) 2023.02.02
[JAVA] Interface (인터페이스)  (0) 2023.02.02
[JAVA] 추상화와 추상화 클래스  (0) 2023.02.01