[김영한의 실전 자바 - 중급 2편] 01. 제네릭 - Generic1

Turtle·2024년 7월 4일
0
post-thumbnail

🏷️제네릭이 필요한 이유

특정 타입만을 담는 박스가 존재한다고 가정할 때 타입이 늘어남에 따라 작성해야 하는 코드의 분량이 늘어나게 된다. 이런 문제를 해결하기 위해 제네릭을 도입하게 되었다.

🏷️다형성을 통한 중복 해결 시도

public class ObjectBox {
	private Object value;

	public Object getValue() {
		return value;
	}

	public void setValue(Object value) {
		this.value = value;
	}
}
public class BoxMain {
	public static void main(String[] args) {
		ObjectBox integerBox = new ObjectBox();
		integerBox.setValue(10);
		Object value1 = integerBox.getValue();
		Integer integer = (Integer) value1;
		System.out.println("integer = " + integer);

		ObjectBox stringBox = new ObjectBox();
		stringBox.setValue("문자");
		Object value2 = stringBox.getValue();
		String string = (String) value2;
		System.out.println("string = " + string);

		// 문제가 발생하는 부분
		integerBox.setValue("문자");
		Integer value = (Integer) integerBox.getValue();
		System.out.println("value = " + value);
	}
}
  • ✔️다형성을 통한 중복 해결 시도 - 문제
    • ❗반환 타입이 맞지 않는 문제
      • integerBox를 만들어서 숫자 10을 보관했다. 숫자를 입력하는 부분에는 문제가 없어 보이지만 integerBox.getxxx()을 호출할 때 문제가 나타난다.
    • ❗잘못된 타입의 인수 전달 문제
      • integerBox에는 정수형 타입의 값이 들어가는 것을 전제로 설계했으나 문자열 타입의 값이 들어가도 문제가 발생하지 않는 것처럼 보인다. 이런 잘못된 타입의 값을 전달하고 getxxx()을 호출할 때 문제가 나타나게 된다.
  • ✔️다형성을 통한 중복 해결 시도 - 결론
    • 다형성을 활용한 덕분에 코드의 중복을 제거하고 기존 코드를 재사용할 수 있게 되었다.
    • 하지만 입력할 때 실수로 원하지 않는 타입이 들어갈 수 있는 타입 안전성 문제가 발생한다.
    • 매개변수가 Object이고 반환타입 역시 Object이기 때문에 원하는 타입을 정확하게 받을 수 없고 항상 위험한 다운 캐스팅을 시도해야 한다. 결과적으로 이 방식은 타입 안전성이 떨어진다.

🏷️제네릭 적용

public class GenericBox<T> {
	private T value;

	public T getValue() {
		return value;
	}

	public void setValue(T value) {
		this.value = value;
	}
}
  • ✔️제네릭 코드 설명
    • <>를 사용한 클래스를 제네릭 클래스라 한다. 이 기호를 다이아몬드라고 한다.
    • 제네릭 클래스를 사용할 때는 Integer, String같은 타입을 미리 결정하지 않는다.
    • 대신에 클래스명 오른쪽에 <T>와 같이 선언하면 제네릭 클래스가 된다. 여기서 T를 타입 매개변수라 한다. 이 타입 매개변수는 이후에 Integer, String으로 변할 수 있다.
    • 그리고 클래스 내부에 T타입이 필요한 곳에 T value와 같이 타입 매개변수를 적어둔다.
public class GenericBox<T> {
	private T value;

	public T getValue() {
		return value;
	}

	public void setValue(T value) {
		this.value = value;
	}
}
public class GenericBoxMain {
	public static void main(String[] args) {
		GenericBox<Integer> integerGenericBox = new GenericBox<>();
		integerGenericBox.setValue(100);
		// integerGenericBox.setValue("문자");
		Integer integer = integerGenericBox.getValue();
		System.out.println("integer = " + integer);

		GenericBox<String> stringGenericBox = new GenericBox<>();
		stringGenericBox.setValue("문자100");
		// stringGenericBox.setValue(100);
		String string = stringGenericBox.getValue();
		System.out.println("string = " + string);
	}
}
  • ✔️원하는 모든 타입 사용 가능
    • 제네릭 클래스를 사용하면 다음과 같이 GenericBox 객체를 생성하는 시점에 원하는 타입을 마음껏 지정할 수 있다.
    • 제네릭을 도입한다고 해서 앞서 설명한 GenericBox<Integer>, GenericBox<String>와 같은 코드가 실제 만들어지는 것은 아니다. 대신에 자바 컴파일러가 우리가 입력한 타입 정보를 기반으로 이런 코드가 있다고 가정하고 컴파일 과정에 타입 정보를 반영한다. 이 과정에서 타입이 맞지 않으면 컴파일 오류가 발생한다.
  • ✔️타입 추론
    • 변수를 선언할 때와 객체를 생성할 때 <Integer>가 두 번 나온다. 자바는 왼쪽에 있는 변수를 선언할 때의 <Integer>를 보고 오른쪽에 있는 객체를 생성할 때 필요한 타입 정보를 얻을 수 있다. 따라서 오른쪽 코드에서 <Integer>와 같은 타입 정보를 생략할 수 있다. 이렇게 자바가 스스로 타입 정보를 추론해서 개발자가 타입 정보를 생략할 수 있는 것을 타입 추론이라고 한다.

🏷️제네릭 용어와 관례

  • ✔️제네릭의 핵심
    • ❗사용할 타입을 미리 결정하지 않는다는 점
    • 클래스 내부에서 사용하는 타입을 클래스를 정의하는 시점에 결정하는 것이 아니라 실제 사용하는 생성 시점에 타입을 결정한다.
  • ✔️제네릭의 타입 매개변수와 타입 인자
    • 메서드의 매개변수는 사용할 값에 대한 결정을 나중으로 미루는 것이고 제네릭의 타입 매개변수는 사용할 타입에 대한 결정을 나중으로 미루는 것이다.
    • 메서드는 매개변수에 인자를 전달해서 사용할 값을 결정한다.
    • 제네릭 클래스는 타입 매개변수에 타입 인자를 전달해서 사용할 타입을 결정한다.
  • ✔️용어 정리
    • 제네릭
      • 제네릭이라는 단어는 일반적인, 범용적인이라는 영어 단어 뜻이다.
      • 풀어보면 특정 타입에 속한 것이 아니라 일반적으로, 범용적으로 사용할 수 있다는 뜻이다.
    • 제네릭 타입
      • 클래스나 인터페이스를 정의할 때 타입 매개변수를 사용하는 것을 말한다.
      • 제네릭 클래스, 제네릭 인터페이스를 모두 합쳐서 제네릭 타입이라 한다.
        • 타입은 클래스, 인터페이스, 기본형을 모두 합쳐서 부르는 말
    • 타입 매개변수
      • 제네릭 타입이나 메서드에서 사용되는 변수로 실제 타입으로 대체된다.
      • Ex. T
    • 타입 인자
      • 제네릭 타입을 사용할 때 제공되는 실제 타입이다.
      • Ex. Integer

🏷️제네릭 활용 예제

직접 클래스를 만들고 제네릭을 도입해보자. Animal 관련 클래스들은 이후 예제에서도 사용하므로 generic.animal이라는 별도의 패키지에서 관리해라.

public class AnimalMain1 {
	public static void main(String[] args) {
		Animal animal = new Animal("동물", 0);
		Dog dog = new Dog("멍멍이", 100);
		Cat cat = new Cat("야옹이", 50);

		Box<Dog> dogBox = new Box<>();
		dogBox.setValue(dog);
		Dog findDog = dogBox.getValue();
		System.out.println("findDog = " + findDog);

		Box<Cat> catBox = new Box<>();
		catBox.setValue(cat);
		Cat findCat = catBox.getValue();
		System.out.println("findCat = " + findCat);
	}
}

0개의 댓글