[Java] 제네릭 (상한, 하한)

한상희·2024년 3월 10일

자바

목록 보기
2/9
post-thumbnail

앞서 제네릭에 대한 상한, 하한을 먼저 보기전에 [Java] 제네릭 (공변, 반공변, 불공변)에 대한 내용을 미리 익히면 좀더 수월하게 이해가 될꺼 같습니다.


상한, 하한 쓰는 이유

한 마디로 말하자면 코드 안정성을 높이기 위해 상한, 하한에 대한 개념을 배운다.

상한 제한

// 박스
class Box<T> {
    private T ob;     
    public void set(T o) { ob = o; }
    public T get() { return ob; }
}

// 장난감
class Toy {
    @Override 
    public String toString() { 
        return "I am a Toy";
    }
}

// 박스 input, output
class BoxController {
    public static void outBox(Box<Toy> box) {
        Toy t = box.get();    // 박스에서 꺼내기
        System.out.println(t);
    }

    public static void inBox(Box<Toy> box, Toy n) {
        box.set(n);    // 박스에 넣기
    } 
}

class Main {
    public static void main(String[] args) {
        Box<Toy> box = new Box<>(); // 박스 생성
        BoxHandler.inBox(box, new Toy()); // 박스에 장난감 넣기
        BoxHandler.outBox(box); // 장난감 꺼내기
    }
}

항상 책이나 블로그 등에서 잘 만들어진 코드는 다음과 같다고 강조한다.
"필요한 만큼만 기능을 허용하며, 코드의 오류가 컴파일 과정에서 최대한 발견하도록 한다."

그럼 위에 예제의 내용은 잘 만들어진 코드일까? 물론 코드에는 문제는 없다. 하지만 잘 만들어진 코드는 아니다. 그럼 다시 BoxController 클래스를 보자.

// 박스 input, output
class BoxController {
    public static void outBox(Box<Toy> box) {
        Toy t = box.get();    // 박스에서 꺼내기
        System.out.println(t);
    }

    public static void inBox(Box<Toy> box, Toy n) {
        box.set(n);    // 박스에 넣기
    } 
}

여기서 박스에 넣는 코드 outBox를 보자.

// 매개변수 box가 참조하는 상자에서 인스터스를 꺼내는 기능
public static void outBox(Box<Toy> box) {
  Toy t = box.get();    // 박스에서 꺼내기
  System.out.println(t);
}

이 코드에서는 box.get()을 하고 있다. 하지만 box.set()도 가능하다.
그럼 다음과 같은 대참사가 일어날 수 있다.

public static void outBox(Box<Toy> box) {
	box.get();
	bpx.set(new Toy);
};

이러한 실수를 하게 되면 컴파일 과정에서 발견되지 않는다. 때문에 box 대상으로 get은 가능하지만 set은 불가능한 제한을 거는것이 좋다.
그러면 다음과 같이 정의하는 것이 좋다.

// (Box<Toy> box) -> (Box<? extends Toy> box)
public static void outBox(Box<? extends Toy> box) {
	box.get(); // OK
	box.set(new Toy); // Error
}

이렇게 하면 outBox 메소드는 "읽기"만 가능하다.

하한 제한

이 코드의 문제점도 똑같다. set, get이 전부 된다.

class BoxController {
	...
	public static void inBox(Box<Toy> box, Toy n) [
		box.set(n); // 상자에 넣기
	}
}

하한 제한을 거는 법은 다음과 같다.

class BoxController {
	...
	public static void inBox(Box<? super Toy> box, Toy n) [
		box.set(n); // 상자에 넣기
		Toy myToy = box.get(); // 안됨 Error!
	}
}

즉 하한 제한은 쓰기만 가능하다.

정리

상한, 하한을 거는 이유는

  • 코드 안정성을 높이기 위해
  • 필요한 만큼만 기능을 허용
  • 코드의 오류가 컴파일 과정에서 최대한 발견하기 위해

상한 제한은 "읽기"만 가능(<? extends T>)
하한 제한은 "쓰기"만 가능(<? super T>)


참고: 윤성우의 열혈 Java 프로그래밍

profile
안녕하세요

0개의 댓글