Java (제네릭 Generic)

최병현·2026년 1월 13일

java

목록 보기
20/38
post-thumbnail

이 내용은 Backend / OOP logic in Java 영역에서 “타입 안전성(Type Safety)”과 “재사용성(Reuse)”을 동시에 확보하기 위한 핵심 문법이다. 이번 과제의 핵심 문제는 printResult(Person person)처럼 특정 클래스에 의존하면, Dog처럼 “구조는 같은데 타입이 다른 객체”를 처리할 수 없다는 점이다.

이를 오버로딩으로 해결하면 메서드가 계속 늘어나고, 유지보수가 어려워진다. 따라서 공통 규약(Interface) + Generic 구조로 확장 가능한 설계를 만든다.


1. Generic 개념 요약

제네릭은 클래스/메서드가 다룰 타입을 매개변수화하여, 하나의 코드로 다양한 타입을 처리할 수 있도록 해준다.

  • T(Type): 일반 타입
  • E(Element): 컬렉션 요소
  • K(Key), V(Value): Map의 Key/Value

효과:

  • 재사용성: 동일 로직을 여러 타입에 재활용
  • 타입 안정성: 컴파일 단계에서 타입 오류 방지

2. BMI 과제에서의 문제점

기존 구조:

public void printResult(Person person) { ... }

이 방식의 한계:

  • Person은 OK
  • Dog는 구조가 같아도 타입이 달라서 컴파일 에러
  • 오버로딩을 쓰면 → 타입 늘어날수록 메서드 증가

해결 전략:

  • ① BMI 계산에 필요한 “공통 규약”을 인터페이스로 정의
  • ② 그 규약을 구현한 타입만 제네릭으로 받기

3. 공통 규약 정의 (Interface)

BMI 계산에 필요한 데이터는 3개뿐이다.

  • name
  • height (cm)
  • weight (kg)
package ch18_generic.bmi;

public interface BmiTarget {
    String getName();
    double getHeight();
    double getWeight();
}

이제 BMI 계산 대상이 되려면 BmiTarget을 구현해야 한다.


4. Person 클래스

public class Person implements BmiTarget {

    private String name;
    private double height;
    private double weight;

    public Person(String name, double height, double weight) {
        this.name = name;
        this.height = height;
        this.weight = weight;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public double getHeight() {
        return height;
    }

    @Override
    public double getWeight() {
        return weight;
    }
}

5. Dog 클래스 (확장 예시)

public class Dog implements BmiTarget {

    private String name;
    private double height;
    private double weight;

    public Dog(String name, double height, double weight) {
        this.name = name;
        this.height = height;
        this.weight = weight;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public double getHeight() {
        return height;
    }

    @Override
    public double getWeight() {
        return weight;
    }
}

Person이든 Dog든, BmiTarget을 구현했기 때문에 이제 동일한 BMI 로직을 사용할 수 있다.


6. Bmi 클래스 (Generic 적용)

T extends BmiTarget → “BmiTarget을 구현한 타입만 허용”한다는 제네릭 제한(bound)이다.

public class Bmi<T extends BmiTarget> {

    public double calcBmi(T target) {
        double heightMeter = target.getHeight() / 100.0;
        return target.getWeight() / (heightMeter * heightMeter);
    }

    public void printResult(T target) {
        double bmi = calcBmi(target);
        String result;

        if (bmi < 18.5) {
            result = "저체중";
        } else if (bmi < 23) {
            result = "정상";
        } else if (bmi < 25) {
            result = "비만전단계";
        } else if (bmi < 30) {
            result = "1단계 비만";
        } else if (bmi < 35) {
            result = "2단계 비만";
        } else {
            result = "3단계 비만";
        }

        System.out.printf(
            "%s 님의 키는 %.1f cm, 몸무게는 %.1f kg, bmi 지수는 %.2f 으로 %s입니다.%n",
            target.getName(),
            target.getHeight(),
            target.getWeight(),
            bmi,
            result
        );
    }
}

7. PersonMain 실행부

public class PersonMain {
    public static void main(String[] args) {

        Person person1 = new Person("김일", 172, 68);
        Dog dog1 = new Dog("강아지", 52, 12);

        Bmi<Person> bmiPerson = new Bmi<>();
        bmiPerson.printResult(person1);

        Bmi<Dog> bmiDog = new Bmi<>();
        bmiDog.printResult(dog1);
    }
}

8. 동작 원리 정리

1) BmiTarget 인터페이스가 “BMI 계산에 필요한 최소 규약”을 정의한다.

2) Person, Dog는 해당 규약을 구현한다. → 구조는 다르지만 “BMI 대상”이라는 공통 개념을 가진다.

3) Bmi<T extends BmiTarget> → T는 반드시 BmiTarget을 구현한 타입만 올 수 있다.

4) 따라서 printResult()는 Person, Dog, 그리고 향후 추가될 어떤 객체든 인터페이스만 지키면 그대로 재사용 가능하다.


9. 와일드카드(?)와의 연결

만약 “구체 타입은 모르지만, BmiTarget 계열만 받겠다”면 다음과 같이 쓸 수 있다.

public void printAnyBmi(Bmi<? extends BmiTarget> bmi, BmiTarget target) {
    bmi.printResult(target);
}
  • ? extends T: T 또는 하위 타입 → 읽기 전용
  • ? super T: T 또는 상위 타입 → 쓰기 전용

10. 주의점

  • 제네릭은 “아무 타입이나” 받으라고 쓰는 것이 아니다.
  • <T extends BmiTarget>처럼 bound(제한)을 반드시 걸어야 설계가 안전해진다.
  • “공통 동작이 없는 타입”을 억지로 제네릭으로 묶으면 캐스팅 지옥이 된다.

11. 정리

이번 과제의 핵심은 “제네릭 문법”이 아니라,

  • 공통 규약(Interface) 설계
  • 그 규약을 기준으로 제네릭 제한(bound) 적용

이라는 구조적 사고다. 이 패턴은 이후 Spring Boot DTO, Response Wrapper, 공통 API 응답 구조 등에서 그대로 사용된다.

즉, 이번 BMI 실습은 단순 계산 과제가 아니라, “확장 가능한 객체 설계”의 기초 훈련이라고 보면 된다.

profile
Develop

0개의 댓글