JAVA 강의정리 - 제네릭

hanana·2024년 6월 20일

김영한님 JAVA 강의

목록 보기
1/1

본 포스팅은 인프런 김영한님의 김영한의-실전-자바-중급 편 강의를 수강 후
강의내용을 참고하여 작성하였습니다.
https://www.inflearn.com/course/%EA%B9%80%EC%98%81%ED%95%9C%EC%9D%98-%EC%8B%A4%EC%A0%84-%EC%9E%90%EB%B0%94-%EC%A4%91%EA%B8%89-2/dashboard


서론

제가 영한님 자바강의를 수강하게 된 주제입니다.

Q. 자바에서 제네릭이 뭘까요?
-> 타입을 나중에 결정해서 다양한 타입을 받게 해주는 타입...?
Q. 그냥 Object 쓰면 되지 않나요? 왜 제네릭을 써야하요?
-> 어....... 그러게요....

스스로 제네릭에 관련되서 위와 같은 질문을 던져보았을때 명쾌한 대답을 할 수 없었고
찾아보더리도 금방 까먹기 일수였습니다.
매우 궁금했던 내용이였던지라 강의를 수강하고 한번 정리해보려고 합니다.


1. 예제상황

아래와 같은 코드가 있다고 가정합니다.


***** 동물 클래스 - start *****

public abstract class Animal {
    private String name;
    
    public Animal(String name) {
    	this.name = name;
    }
    public getName() {
    	return name;
    }
}

----

public class Cat extends Animal{
    public Dog(String name) {
        super(name);
    }
}

----

public class Dog extends Animal{
    public Dog(String name) {
        super(name);
    }
}
***** 동물 클래스 - end *****

***** 동물병원 클래스 - start *****

public class DogHospital {
    private Dog dog;
 
    public void set(Dog dog) {
    	this.dog = dog;
    }
 
    public Dog get() {
    	return dog;
    }
}

----
public class CatHospital {
    private Cat cat;
 
    public void set(Cat cat) {
    	this.cat = cat;
    }
 
    public Cat get() {
    	return cat;
    }
}
***** 동물병원 클래스 - end *****

동물병원클래스동물클래스를 필드로 가집니다.
고양이병원은 고양이를, 강아지병원은 강아지를 필드로 받습니다.

아래는 해당 클래스를 사용하는 예제 클래스입니다.
main 클래스

public static void main(String[] args) {
    DogHospital dogHospital = new DogHospital();
    dogHospital.set(new Dog("멍멍이1"));
    Dog dog = dogHospital.get();
    System.out.println("강아지이름 : " + dog.getName());
    
    CatHospital catHospital = new CatHospital();
    catHospital.set(new Cat("냥냥이1"));
    Cat cat = catgHospital.get();
    System.out.println("고양이 이름 : " + cat.getName());
}

실행결과 :
강아지이름 : 멍멍이1
고양이이름 : 냥냥이1

1-1. 문제점

만약 두더지 라는 동물이 추가되었다고 가정합니다.
메인 클래스에서 두더지를 병원에 보내기 위해선 두더지 병원 클래스가 새로 생성되야 합니다.
현재 구조에서는 동물(타입)이 한종류씩 추가될 때마다 병원 클래스도 같이 만들어야 하는 문제점이 있습니다.
이를 해결하기 위해 다형성을 활용해봅니다.


2. Object 클래스 사용

자바에서 모든 객체의 최상위 부모는 Object 클래스입니다.
부모클래스는 자식클래스를 담을 수 있으므로 Obejct를 활용하면 모든 동물(타입)을 담을 수 있습니다.

ObejctHosptial 클래스

public class ObjectHospital {
    private Object obj;
 
    public void set(Dog obj) {
    	this.obj = obj;
    }
 
    public Object get() {
    	return obj;
    }
}

이로인해 main 클래스는 아래와 같이 리팩토링이 가능합니다.

main 클래스

public static void main(String[] args) {
    ObjectHospital objHospital1 = new ObjectHospital();
    objHospital1.set(new Dog("멍멍이1"));
    Dog dog = (Dog) objHospital1.get(); // ** 다운캐스팅 필요 **
    System.out.println("강아지이름 : " + dog.getName());
    
    ObjectHospital objHospital2 = new ObjectHospital();
    objHospital2.set(new Cat("냥냥이1"));
    Cat cat = (Cat) objHospital2.get(); // ** 다운캐스팅 필요 **
    System.out.println("고양이 이름 : " + cat.getName());
}

실행결과 :
강아지이름 : 멍멍이1
고양이이름 : 냥냥이1

2-1. 문제점

얼핏 보면 다형성을 활용해서 잘 동작하는것 처럼 보이지만
현재의 구조에는 몇가지 문제가 있습니다.

타입 안전성이 떨어진다.

ObjectHospital 클래스의 get() 메서드는 Object 타입을 반환합니다.
이로 인해 개발자가 값을 꺼내서 활용하기 위해서는 생성한 타입으로 다운캐스팅 을 해주어야 합니다.

ObjectHospital objHospital = new ObjectHospital();
objHospital.set(new Cat("냥냥이1"));
Object obj = objHospital.get();
Dog dog = (Dog) obj; //  !! ClassCastException 발생 !!

objHospital 다형성을 활용해서 고양이를 넣었지만
여전히 get()메서드의 반환타입은 Obejct입니다.
Object 클래스는 Dog로 다운캐스팅이 가능하므로 위 코드는 컴파일시점에 에러가 발생하지 않습니다.

하지만 런타임 시점에서 다운캐스팅을 시도할 때,
강아지가 있어야할 필드에 고양이가 들어가 있으므로 캐스팅 시에 예외가 발생하게 됩니다.


3. 제네릭클래스 도입하기

  • <>를 사용한 클래스를 제네릭 클래스라고 합니다.
  • 대신 클래스명 오른쪽에 <T>와 같이 선언하여 제네릭 클래스를 만들 수 있습니다.
  • 제네릭의 핵심은 '사용할 타입을 미리 결정하지 않는다.' 입니다.
    제네릭 클래스 내부에서 사용하는 타입은 클래스를 정의하는 시점이 아닌, 객체를 생성하는 시점에 결정합니다.
  • 제네릭 클래스는 integer, String과 같은 타입을 미리 결정하지 않습니다.
    T를 타입 매개변수 라고 표현하며, 이후에 Integer, String과 같은 타입으로 변할 수 있습니다.
  • T타입 역시 상속과 비슷하게 상위를 지정할 수 있습니다.

제네릭을 활용한 병원 클래스입니다.

// public class GenericHospital<T extends Animal> {
// 이러한 형태로 제네릭 타입의 변수를 
// Animal의 하위 클래스만 받을 수 있게 할 수 도 있습니다.

public class GenericHospital<T> {
    private T t;
 
    public void set(T t) {
    	this.t = t;
    }
 
    public T get() {
    	return t;
    }
}

제네릭 병원을 활용한 main클래스입니다.

public static void main(String[] args) {
	// **** 객체 생성시에 T의 타입을 Dog로 결정 ****
    GenericHospital<Dog> genericHospital = new GenericHospital<>();
    genericHospital.set(new Dog("멍멍이1"));
    // genericHospital.set(new Cat("냥냥이1")); // !! 컴파일 에러발생 !!
    Dog dog = genericHospital.get();
    // Cat cat = genericHospital.get(); // !! 컴파일 에러발생 !!
    System.out.println("강아지이름 : " + dog.getName());
}

실행결과 :
강아지이름 : 멍멍이1

제네릭병원의 필드변수를 제네릭으로 지정하고
해당 변수타입은 객체의 생성시점에 정의됩니다.
new GenericHospital<Dog>로 객체를 생성할 때
해당 객체의 T 타입은 Dog 타입으로 변합니다.
따라서 타입이 늘어날 때마다 추가적으로 클래스를 생성할 필요도 없어지며,
Object가 가지고 있는 타입안전성 문제도 함께 해결할 수 있습니다.


다시 처음질문을 해결해봅니다.

Q. 자바에서 제네릭이 뭘까요?
-> 클래스를 정의하는 시점이 아닌, 객체를 생성하는 시점에 변수의 타입을 지정하는것
Q. 그냥 Object 쓰면 되지 않나요? 왜 제네릭을 써야하요?
-> 다형성을 이용한 Object를 사용하게 되면 모든 타입을 받을 수 있으므로,
객체의 타입마다 클래스를 새로 정의하는 문제를 해결할 수 있지만,
이로인해 전혀 엉뚱한 타입의 객체를 마음대로 대입할 수도 있고,
반환값이 Object이므로 해당 객체를 사용하는 시점에 다운캐스팅 을 필요로 합니다. 이 과정에서
다운캐스팅이 불가능 한 경우 컴파일러가 이를 인지하지 못하여, 런타임시점에 ClassCastException이 발생합니다.

하지만 제네릭을 사용하는 경우
객체를 생성하는 시점부터는 해당 타입을 fix할 수 있으므로, 엉뚱한 객체를 대입하는 문제를 컴파일러가 잡을 수 있으며,
반환값은 객체를 생성하는 시점에 해당 타입임을 추론할 수 있으므로 불필요한 다운캐스팅으로 인한 문제 발생을 막을 수 있습니다.

profile
성숙해지려고 노력하지 않으면 성숙하기까지 매우 많은 시간이 걸린다.

0개의 댓글