저번 포스팅에서, 제네릭을 도입하여 코드의 타입 안전성과, 코드 재사용성을 늘려보았다.

오늘은 1편에 이어 제네릭에 대해 두번째 포스팅을 해보고자 한다.


동물 병원을 만들어보자

예를 들어, 아래와 같이 Animal 클래스와 그의 자식 클래스인 Dog와 Cat이 있다고 하자.

public class Animal {

    private String name;
    private int size;

    public Animal(String name, int size) {
        this.name = name;
        this.size = size;
    }

    public String getName() {
        return name;
    }

    public int getSize() {
        return size;
    }

    public void sound() {
        System.out.println("동물 울음 소리");
    }

    @Override
    public String toString() {
        return "Animal{" +
                "name='" + name + '\'' +
                ", size=" + size +
                '}';
    }
}
public class Cat extends Animal {

    public Cat(String name, int size) {
        super(name, size);
    }

    @Override
    public void sound() {
        System.out.println("냥냥");
    }

}

 

public class Dog extends Animal{

    public Dog(String name, int size) {
        super(name, size);
    }

    @Override
    public void sound() {
        System.out.println("멍멍");
    }
}

 

위 코드들을 통해, 동물 병원에 대한 프로그램을 작성하려고 한다.

이때 1번째 포스팅에서 발생할 수 있는 문제점들을 제외하고, 제네릭을 도입했을 때 발생하는 문제점들에 대해 적어보겠다.자세한건 이전 포스팅 참조 부탁..

https://bdisappointed.tistory.com/53

 

[Java] 제네릭이 필요한 이유 (Generic) - 1

오늘도 즐겁고 기쁜 마음으로 제네릭에 대해 포스팅 하고자 한다. 내용이 많으니까 2부에 걸쳐 올리려고 한다. 제네릭이 필요한 이유매그네릭.. 아니 제네릭이 필요한 이유를 설명하기 전, 예시

bdisappointed.tistory.com


제네릭의 도입과 실패

예를 들어 동물 병원 코드를 아래와 같이 작성했다고 하자

/**
 * 제네릭 도입
 */
public class AnimalHospitalV2<T> {

    private T animal;

    public void set(T animal) {
        this.animal = animal;
    }

    public void checkUp() {
        //T 타입을 메서드를 정의하는 시점에는 알 수 없다. Object 의 기능만 사용 가능
        animal.toString();
        animal.equals(null);

    }


    public T getBigger(T target) {
       // return animal.getSize() > target.getSize() ? animal : target;
        return null;
    }
}

제네릭을 도입하여, 정해놓은 동물들을 위한 병원 클래스를 작성하였는데, 이제 문제점을 알아보자

1. 이 코드 만으로는 컴파일 시점에 T에 어떤 타입이 들어올지 모른다
2. 나는 동물을 의도하고 코드를 작성하였는데, 코드 어디에도 Animal에 관련된 정보가 없다.
3. 물론 운이 좋아서 Dog 나 Cat 이 들어올 수도 있지만, 최상위 클래스인 Object 도 들어올 수 있다

따라서, 컴파일러는 컴파일 시점에, 어떤 타입이 T로 들어올지 모르기 때문에, 최상위 클래스인Object를 T로 가정하고 Object가 제공하는 메서드만 사용할 수 있다. -> 즉 정작 checkUp 이나 getBigger는 못쓰게 되었다..

우리는 동물 병원을 만들고 싶었던거지, 오브젝트 병원을 만드려 한게 아닌데..

근본적인 해결방법은 다음과 같다 -> " 아 Animal과 관련된 클래스만 타입인자로 받으면 될 것 같다..?"

자 이제 Animal과 관련된 클래스만 제네릭의 타입인자로 받을 수 있게끔 해보자


타입 매개변수 제한

타입 인자의 범위를 Animal로 한정하기 위해 코드를 아래와 같이 수정했다.

import generic.animal.Animal;

/**
 * 제네릭 상한 지정
 */
public class AnimalHospitalV3<T extends Animal> {

    private T animal;

    public void set(T animal) {
        this.animal = animal;
    }

    public void checkUp() {
        System.out.println("동물 이름 : " + animal.getName());
        System.out.println("동물 크기 : " + animal.getSize());

    }


    public T getBigger(T target) {
        return animal.getSize() > target.getSize() ? animal : target;
    }
}

 

이 코드의 핵심은 아래 코드이다.

<T extends Animal>

extends 키워드를 통해 타입의 상한선을 Animal로 한정했다. -> 즉 이제 T의 타입인자는 Animal을 포함하여 그의 자식인 Dog와 Cat 밖에 들어오지 못한다.

이제 컴파일러가 이렇게 생각할 것이다, "아ㅋㅋ 범위를 지정했어야지ㅋㅋ 알겠어 이제 타입이 최대 Animal이라고 생각하고 컴파일 할게"

즉 상한선을 정해둔 덕분에 Animal이 제공하는 메서드들도 사용할 수 있게 되었다!


제네릭을 사용하지 않았을 때 문제점 및 해결

우리는 이제 제네릭을 통해 기존의 문제점들을 아래와 같이 해결할 수 있게 되었다

1. 타입 안전성, 코드 재사용성 문제 -> 제네릭 도입

2. 제네릭을 도입했지만, 여전히 아직 타입 안정성 문제 존재 -> 타입 상한선을 두어 문제 해결


한줄 요약

제네릭에 타입 매개변수 상한을 사용하여 타입 안전성을 지키고, 상위 타입의 메서드까지 사용할 수 있게 되었다!

덕분에, 코드 재사용성과 타입 안정성 두마리 토끼를 완전히 잡을 수 있게 되었다.

 

+ 추가로 제네릭을 메서드 개념에 도입한 제네릭 메서드와 제네릭을 쉽게 사용할 수 있게 도와주는 와일드 카드의 개념이 있는데, 이는 추후에 추가로 업로드 해야겠다!

그럼 오늘도 20000!