이 내용은 Backend / OOP logic in Java 영역에서 “타입 안전성(Type Safety)”과 “재사용성(Reuse)”을 동시에 확보하기 위한 핵심 문법이다. 이번 과제의 핵심 문제는 printResult(Person person)처럼 특정 클래스에 의존하면, Dog처럼 “구조는 같은데 타입이 다른 객체”를 처리할 수 없다는 점이다.
이를 오버로딩으로 해결하면 메서드가 계속 늘어나고, 유지보수가 어려워진다. 따라서 공통 규약(Interface) + Generic 구조로 확장 가능한 설계를 만든다.
제네릭은 클래스/메서드가 다룰 타입을 매개변수화하여, 하나의 코드로 다양한 타입을 처리할 수 있도록 해준다.
효과:
기존 구조:
public void printResult(Person person) { ... }
이 방식의 한계:
해결 전략:
BMI 계산에 필요한 데이터는 3개뿐이다.
package ch18_generic.bmi;
public interface BmiTarget {
String getName();
double getHeight();
double getWeight();
}
이제 BMI 계산 대상이 되려면 BmiTarget을 구현해야 한다.
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;
}
}
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 로직을 사용할 수 있다.
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
);
}
}
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);
}
}
1) BmiTarget 인터페이스가 “BMI 계산에 필요한 최소 규약”을 정의한다.
2) Person, Dog는 해당 규약을 구현한다. → 구조는 다르지만 “BMI 대상”이라는 공통 개념을 가진다.
3) Bmi<T extends BmiTarget> → T는 반드시 BmiTarget을 구현한 타입만 올 수 있다.
4) 따라서 printResult()는 Person, Dog, 그리고 향후 추가될 어떤 객체든 인터페이스만 지키면 그대로 재사용 가능하다.
만약 “구체 타입은 모르지만, BmiTarget 계열만 받겠다”면 다음과 같이 쓸 수 있다.
public void printAnyBmi(Bmi<? extends BmiTarget> bmi, BmiTarget target) {
bmi.printResult(target);
}
이번 과제의 핵심은 “제네릭 문법”이 아니라,
이라는 구조적 사고다. 이 패턴은 이후 Spring Boot DTO, Response Wrapper, 공통 API 응답 구조 등에서 그대로 사용된다.
즉, 이번 BMI 실습은 단순 계산 과제가 아니라, “확장 가능한 객체 설계”의 기초 훈련이라고 보면 된다.