1-1. 제네릭이 필요한 이유

shin·2024년 6월 9일
  • 대부분의 최신 프로그래밍 언어는 제네릭(Generic) 개념을 제공함
  • 자바에서 제네릭을 제대로 이해하는 것은 어렵기 때문에, 먼저 제네릭을 전혀 사용하지 않고 코드를 작성한 후 코드에 제네릭을 점진적으로 도입
  • 기존 방식으로는 해결이 어려운 코드 중복을 제네릭이 어떻게 해결하는지 자연스럽게 이해할 수 있도록 함
  • 제네릭이 필요한 이유를 코드를 통해 학습

제네릭이 필요한 이유

IntegerBox

package generic.ex1;

public class IntegerBox {

	private Integer value;
    
	public void set(Integer value) {
 		this.value = value;
    }
 
 	public Integer get() {
 		return value;
    }
    
}
  • 숫자를 보관하고 꺼낼 수 있는 단순한 기능 제공

StringBox

package generic.ex1;

public class StringBox {

	private String value;
	
    public void set(String object) {
		this.value = object;
    }
    
	public String get() {
		return value;
    }
    
}
  • 문자열을 보관하고 꺼낼 수 있는 단순한 기능을 제공

메인 클래스 : BoxMain1

 package generic.ex1;
 
 public class BoxMain1 {
 
 	public static void main(String[] args) {
 
        IntegerBox integerBox = new IntegerBox();
        integerBox.set(10); //오토 박싱

        Integer integer = integerBox.get();
        System.out.println("integer = " + integer);

        StringBox stringBox = new StringBox();
        stringBox.set("hello");

        String str = stringBox.get();
        System.out.println("str = " + str);
    
    }
    
}

실행 결과

integer = 10
str = hello
  • 코드를 보면 먼저 숫자를 보관하는 IntegerBox를 생성하고, 그곳에 숫자 10을 보관하고, 꺼낸 다음에 출력함
  • 참고로 오토 박싱에 의해 intInteger로 자동 변환됨
  • 다음으로 문자열을 보관하는 StringBox를 생성하고 그곳에 문자열 "hello"를 보관하고, 꺼낸 다음에 출력함

문제

  • 이후에 Double, Boolean을 포함한 다양한 타입을 담는 박스가 필요하다면 각각의 타입별로 DoubleBox, BooleanBox와 같이 클래스를 새로 만들어야 함
  • 담는 타입이 수십개라면, 수십개의 XxxBox 클래스를 생성해야 함
  • 이 문제를 아래 방식을 통해 해결해야 함

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

  • Object는 모든 타입의 부모
  • 다형성(다형적 참조)를 사용해서 위 문제 해결 시도

ObjectBox

package generic.ex1;

public class ObjectBox {

	private Object value;
    
	public void set(Object object) {
 		this.value = object;
    }
 
 	public Object get() {
 		return value;
 	}   
    
}
  • 내부에 Object value를 가지고 있음
  • Object는 모든 타입의 부모
  • 부모는 자식을 담을 수 있으므로 세상의 모든 타입을 ObjectBox에 보관할 수 있음

메인 클래스 : BoxMain2

package generic.ex1;

public class BoxMain2 {

	public static void main(String[] args) {

		ObjectBox integerBox = new ObjectBox();
   	 	integerBox.set(10);
		Integer integer = (Integer) integerBox.get(); //Object -> Integer 캐스팅
		System.out.println("integer = " + integer);

 		ObjectBox stringBox = new ObjectBox();
    	stringBox.set("hello");
 		String str = (String) stringBox.get(); //Object -> String 캐스팅
		System.out.println("str = " + str);
    
 		//잘못된 타입의 인수 전달시
    	integerBox.set("문자100");
 		Integer result = (Integer) integerBox.get(); // String -> Integer 캐스팅 예외

		System.out.println("result = " + result);

	}

}

실행 결과

integer = 10
str = hello
Exception in thread "main" java.lang.ClassCastException: class java.lang.String 
cannot be cast to class java.lang.Integer (java.lang.String and 
java.lang.Integer are in module java.base of loader 'bootstrap')
 at generic.ex1.BoxMain2.main(BoxMain2.java:24)
  • 이 방식에는 몇 가지 문제가 있음


반환 타입이 맞지 않는 문제

  • 먼저 integerBox를 만들어서 숫자 10을 보관함
  • 숫자를 입력하는 부분에는 문제가 없어보이지만, integerBox.get()을 호출할 때 문제가 나타남

  • integerBox.get()의 반환 타입은 Object
Object obj = integerBox.get();

  • Integer = Object는 성립하지 않음
  • 자식은 부모를 담을 수 없음
  • 따라서 다음과 같이 (Integer) 타입 캐스팅 코드를 넣어서 Object 타입을 Integer 타입으로 직접 다운 캐스팅해야 함
 Integer integer = (Integer) integerBox.get() //1
 Integer integer = (Integer) (Object)value //2
 Integer integer = (Integer)value //3

  • StringBox의 경우도 마찬가지임
  • stringBox.get()Object를 반환하므로 다음과 같이 다운 캐스팅해야 함
 String str = (String) stringBox.get();


잘못된 타입의 인수 전달 문제

 integerBox.set("문자100");
  • 개발자의 의도는 integerBox에는 변수 이름과 같이 숫자 타입이 입력되기를 기대함
  • 하지만 set(Obejct..) 메서드는 모든 타입의 부모인 Object를 매개변수로 받기 때문에 세상의 어떤 데이터도 입력받을 수 있음
  • 따라서 이렇게 문자열을 입력해도 자바 언어 입장에서는 아무런 문제가 되지 않음

  • 잘못된 타입의 값을 전달하면 값을 꺼낼 때 문제가 발생함
 Integer result = (Integer) integerBox.get(); //1
 Integer result = (Integer) "문자100"; //2
 Integer result = (Integer) "문자100"; //3. 예외 발생 String을 Integer로 캐스팅할 수 없다.
  • 숫자가 들어가 있을 것으로 예상한 박스에는 문자열이 들어가 있었음
  • 결과적으로 다운 캐스팅시에 StringInteger로 캐스팅 할 수 없다는 예외가 발생하고 프로그램이 종료됨


정리

  • 다형성을 활용한 덕분에 코드의 중복을 제거하고 기존 코드를 재사용할 수 있게 됨

    • 하지만 입력할 때 실수로 원하지 않는 타입이 들어갈 수도 있는 타입 안전성 문제가 발생함
    • 예를 들어 integerBox에는 숫자만 넣어야 하고, stringBox에는 문자열만 입력할 수 있어야 함
    • 하지만 박스에 값을 보관하는 set()의 매개변수가 Object이기 때문에 다른 타입의 값을 입력할 수 있음
  • 반환 시점에도 Object를 반환하기 때문에 원하는 타입을 정확하게 받을 수 없음

    • 항상 위험한 다운 캐스팅을 시도해야 함
  • 결과적으로 이 방식은 타입 안전성이 떨어짐


  • 지금까지 개발한 프로그램은 코드 재사용과 타입 안전성을 한 번에 잡을 수 없음
    • 코드 재사용성을 늘리기 위해 Object와 다형성을 사용하면 타입 안전성이 떨어지는 문제가 발생함
  • BoxMain1 : 각각의 타입별로 IntegerBox, StringBox와 같은 클래스를 모두 정의
    • 코드 재사용X
    • 타입 안전성O
  • BoxMain2 : ObjectBox를 사용해서 다형성으로 하나의 클래스만 정의
    • 코드 재사용O
    • 타입 안전성X

💡 제네릭을 사용하면 코드 재사용성과 타입 안전성을 모두 챙길 수 있음



강의 출처 : 김영한의 실전 자바 - 중급 2편

profile
Backend development

0개의 댓글