개발을 하다 보면 가끔 이런 생각이 들 수도 있습니다.
"이 객체, 어디서 왔지?"
"지금 내가 다루고 있는 클래스에 어떤 메서드가 있는 거야?"
"사용자가 입력한 클래스 이름을 동적으로 불러와 실행하고 싶은데 가능할까?"
이럴 때 필요한 것이 바로 자바 리플렉션(Reflection)!
리플렉션은 런타임에 클래스의 정보를 가져오고 조작할 수 있는 기능입니다.
쉽게 말해, 프로그램 실행 중에 내가 원하는 클래스, 메서드, 필드 등에 접근할 수 있게 해주는 마법 같은 기능이죠.
일반적으로 우리는 코드를 작성할 때 어떤 클래스에 어떤 메서드가 있는지 미리 알고 사용합니다.
예를 들어, 다음과 같이 Car 클래스의 drive() 메서드를 호출한다고 해볼까요?
Car myCar = new Car();
myCar.drive();
이렇게 하면 Car 클래스가 컴파일 시점에 정해져 있습니다.
즉, 코드를 실행하기 전부터 어떤 클래스인지, 어떤 메서드가 있는지 컴파일러가 다 알고 있어요.
그런데 만약, 어떤 클래스가 실행 중에 동적으로 결정된다면?
예를 들어, 사용자가 입력한 문자열을 바탕으로 클래스가 결정되는 경우를 생각해봅시다.
Scanner scanner = new Scanner(System.in);
System.out.print("사용할 클래스 이름을 입력하세요: ");
String className = scanner.nextLine(); // 사용자가 "Car" 입력
// 여기에 className에 해당하는 클래스의 객체를 동적으로 생성하려면?
이럴 때 리플렉션을 사용하면 런타임에 해당 클래스 정보를 가져와 객체를 만들고, 메서드를 실행할 수도 있습니다!
자바 리플렉션을 이해하려면 Class 객체를 먼저 알아야 합니다.
자바에서 모든 클래스는 Class<?> 타입의 객체로 표현될 수 있어요.
// 1. 클래스 리터럴 사용
Class<?> clazz1 = Car.class;
// 2. 문자열로 클래스 로딩
Class<?> clazz2 = Class.forName("com.example.Car");
// 3. 객체에서 클래스 정보 가져오기
Car myCar = new Car();
Class<?> clazz3 = myCar.getClass();
이렇게 가져온 Class<?> 객체를 사용하면 클래스의 메서드, 필드, 생성자 등의 정보를 가져올 수 있습니다.
자, 이제 본격적으로 동적으로 객체를 만들고 메서드를 실행하는 방법을 알아볼까요?
// 1. Class 객체 가져오기
Class<?> clazz = Class.forName("com.example.Car");
// 2. 기본 생성자로 객체 생성
Object obj = clazz.getDeclaredConstructor().newInstance();
// 3. 특정 메서드 가져오기
Method method = clazz.getMethod("drive");
// 4. 메서드 실행
method.invoke(obj);
이제 Car 클래스의 drive() 메서드가 실행됩니다!
즉, 우리는 Car 클래스를 직접 명시하지 않고도 런타임에 동적으로 메서드를 실행할 수 있게 된 것입니다.
Spring에서는 의존성 주입(Dependency Injection)을 할 때 리플렉션을 사용합니다.
예를 들어, @Autowired를 붙여놓으면 Spring이 알아서 객체를 주입해 주는데요,
이 과정에서 리플렉션을 사용해 적절한 클래스를 찾아 객체를 생성합니다.
JSON 데이터를 객체로 변환할 때도 리플렉션을 활용합니다.
예를 들어, { "name": "Alice", "age": 25 } 같은 JSON을 User 객체로 변환할 때,
필드 이름을 자동으로 매칭해서 값을 넣어주는 기능이 리플렉션 덕분에 가능합니다.
JUnit에서는 @Test가 붙은 메서드들을 자동으로 찾아 실행하는데,
이 과정에서도 리플렉션이 사용됩니다.
이클립스 같은 IDE에서는 사용자가 플러그인을 추가할 수 있는데,
어떤 플러그인이 로드될지는 미리 알 수 없으므로 리플렉션을 활용해 동적으로 로드합니다.
근데 꼭 리플레이션이 좋을걸까요? 사실, 위처럼 리플렉션을 사용하지 않아도 다형성을 활용하면 같은 기능을 구현할 수 있습니다! (한국말은 끝까지 들어야합니다!)
interface Vehicle {
void drive();
}
class Car implements Vehicle {
public void drive() {
System.out.println("🚗 자동차가 달립니다!");
}
}
class Bike implements Vehicle {
public void drive() {
System.out.println("🏍️ 오토바이가 달립니다!");
}
}
public class PolymorphismExample {
public static void main(String[] args) {
Vehicle vehicle = getVehicle("Car");
vehicle.drive();
}
public static Vehicle getVehicle(String type) {
if (type.equals("Car")) return new Car();
else if (type.equals("Bike")) return new Bike();
return null;
}
}
위 코드처럼 다형성을 이용하면 리플렉션 없이도 동일한 기능을 구현할 수 있습니다.
| 비교 항목 | 리플렉션(Reflection) | 다형성(Polymorphism) |
|---|---|---|
| 유연성 | 매우 높음 (런타임 동적 처리 가능) | 낮음 (컴파일 타임에 클래스 결정) |
| 성능 | 느림 (리플렉션 호출은 최적화 어려움) | 빠름 (JVM 최적화 가능) |
| 안정성 | 낮음 (런타임 오류 발생 가능) | 높음 (컴파일 타임에 체크됨) |
| 사용 사례 | 프레임워크(Spring), JSON 변환 | 일반적인 객체 지향 프로그래밍 |
| 보안 | 낮음 (private 접근 가능) | 높음 (캡슐화 유지) |
결론적으로, 리플렉션은 꼭 필요한 경우가 아니면 사용을 피하는 것이 좋습니다.
리플렉션은 컴파일 시점에 알 수 없는 클래스나 메서드에 동적으로 접근할 수 있는 강력한 기능입니다.
하지만 무작정 사용하기보다는 다형성을 활용할 수 있는지 먼저 고려하는 것이 중요합니다.
✅ 프레임워크 개발 (Spring, Hibernate)
✅ JSON 변환 (Jackson, Gson)
✅ 테스트 자동 실행 (JUnit)
✅ 플러그인 시스템
이런 곳에서 자주 활용되니, 기본 개념만 잘 이해하고 있어도 실무에서 큰 도움이 될 거예요!
🧐 여러분은 리플렉션을 어디에 사용해보고 싶으신가요?
댓글로 의견 남겨주시면 재미있게 이야기 나눠봐요! 🚀