리플렉션 기능을 이용하면 프로그램에서 임의의 클래스에 접근할 수 있으며 Class 객체가 주어지면 클래스 정보를 통해 다음과 같은 인스턴스를 가져올 수 있습니다.
- 생성자
- 메서드
- 필드
1. 생성자
- 생성자 시그니처를 가져올 수 있음
- 생성자 인스턴스를 통해 객체를 생성할 수 있음
2. 메서드
- 메서드 시그니처를 가져올 수 있음
- 메서드 인스턴스를 통해 메서드를 실행시킬 수 있음
3. 필드
- 필드 타입, 멤버 필드명 등을 가져올 수 있음
MyClass.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.tistory.jaimemin.effectivejava.ch09.item65; | |
import lombok.AllArgsConstructor; | |
import lombok.Data; | |
import lombok.NoArgsConstructor; | |
@Data | |
@NoArgsConstructor | |
@AllArgsConstructor | |
public class MyClass { | |
private String name; | |
private int age; | |
public void displayInfo() { | |
System.out.println("이름: " + name + ", 나이: " + age); | |
} | |
} | |
ReflectionExample.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.lang.reflect.Constructor; | |
import java.lang.reflect.Field; | |
import java.lang.reflect.Method; | |
import lombok.extern.slf4j.Slf4j; | |
@Slf4j | |
public class ReflectionExample { | |
public static void main(String[] args) { | |
try { | |
// 클래스 이름을 이용하여 Class 객체 가져오기 | |
Class<?> clazz = Class.forName("com.tistory.jaimemin.effectivejava.ch09.item65.MyClass"); | |
// 생성자 정보 가져오기 | |
Constructor<?>[] constructors = clazz.getDeclaredConstructors(); | |
// 첫 번째 생성자를 사용하여 객체 생성 | |
Object instance = constructors[0].newInstance(); | |
// 메서드 정보 가져오기 | |
Method[] methods = clazz.getDeclaredMethods(); | |
// 메서드 호출 예제 | |
for (Method method : methods) { | |
if (method.getName().equals("displayInfo")) { | |
// 매개변수 타입에 맞게 인자 전달 | |
method.invoke(instance); | |
} | |
} | |
// 필드 정보 가져오기 | |
Field[] fields = clazz.getDeclaredFields(); | |
// 필드 값 설정 및 가져오기 예제 | |
for (Field field : fields) { | |
if (field.getName().equals("name")) { | |
// 필드에 접근 가능하도록 설정 | |
field.setAccessible(true); | |
field.set(instance, "jaimemin"); | |
} else if (field.getName().equals("age")) { | |
// 필드에 접근 가능하도록 설정 | |
field.setAccessible(true); | |
field.set(instance, 29); | |
} | |
} | |
// displayInfo() 메서드 호출하여 변경된 정보 출력 | |
for (Method method : methods) { | |
if (method.getName().equals("displayInfo")) { | |
// 매개변수 타입에 맞게 인자 전달 | |
method.invoke(instance); | |
} | |
} | |
} catch (Exception e) { | |
log.error("" + e); | |
} | |
} | |
} |

리플렉션의 단점
앞서 코드에서 볼 수 있었다시피 리플렉션은 강력한 기능이지만 다음과 같은 단점들이 존재합니다.
- 컴파일 시점 타입 검사가 주는 이점을 누릴 수 없음
- 리플렉션을 이용하면 코드가 지저분해지고 장황해짐
- 성능 저하
1. 컴파일 시점 타입 검사가 주는 이점을 누릴 수 없음
- 예외 검사 및 컴파일 에러를 잡아 낼 방법이 없음
- 프로그램이 리플렉션 기능을 써서 존재하지 않는 혹은 private 메서드를 호출하려고 하면 런타임 오류 발생
MyClass에 private 메서드를 추가하면 ReflectionExample 코드를 돌릴 때 IllegalArgumentException 발생


2. 리플렉션을 이용하면 코드가 지저분하고 장황해짐
- 원래라면 instance.displayInfo()만 호출하면 되는데
- displayInfo() 메서드를 호출하기 전 작성해야 할 코드가 너무 많음
- 지루한 일이고 가독성 측면에서 좋지 않음
3. 성능 저하
- 리플렉션을 통한 메서드 호출은 일반 메서드 호출보다 훨씬 느림
제가 직접 돌려본 결과 별 차이 없었음...
리플렉션은 아주 제한된 형태로만 사용해야 단점을 회피할 수 있음
- 컴파일 시점에 이용할 수 없는 클래스를 사용해야만 하는 프로그램은 비록 컴파일 타임이라도 적절한 인터페이스나 상위 클래스를 이용할 수는 있음
- 프로그램이 실행 중에 동적으로 클래스를 로드하고 사용해야 하는 경우가 있음
- 이런 경우에는 컴파일 시에는 해당 클래스에 대한 정보가 없기 때문에 정적인 방식으로는 접근할 수 없음
- 하지만 리플렉션을 사용하면 실행 중에 해당 클래스를 로드하고 그에 따른 작업을 수행할 수 있음
- 이때는 적절한 인터페이스나 상위 클래스를 이용하여 코드의 유연성을 유지할 수 있음
- 리플렉션은 인스턴스 생성에만 이용하고 이렇게 만든 인터페이스나 상위 클래스로 참조해 사용하는 것을 권장
리플렉션을 사용했을 때 단점을 보여주는 코드
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class ReflectiveInstantiation { | |
// Reflective instantiation with interface access | |
public static void main(String[] args) { | |
// Translate the class name into a Class object | |
Class<? extends Set<String>> cl = null; | |
try { | |
cl = (Class<? extends Set<String>>) // Unchecked cast! | |
Class.forName(args[0]); | |
} catch (ClassNotFoundException e) { | |
fatalError("Class not found."); | |
} | |
// Get the constructor | |
Constructor<? extends Set<String>> cons = null; | |
try { | |
cons = cl.getDeclaredConstructor(); | |
} catch (NoSuchMethodException e) { | |
fatalError("No parameterless constructor"); | |
} | |
// Instantiate the set | |
Set<String> s = null; | |
try { | |
s = cons.newInstance(); | |
} catch (IllegalAccessException e) { | |
fatalError("Constructor not accessible"); | |
} catch (InstantiationException e) { | |
fatalError("Class not instantiable."); | |
} catch (InvocationTargetException e) { | |
fatalError("Constructor threw " + e.getCause()); | |
} catch (ClassCastException e) { | |
fatalError("Class doesn't implement Set"); | |
} | |
// Exercise the set | |
s.addAll(Arrays.asList(args).subList(1, args.length)); | |
System.out.println(s); | |
} | |
private static void fatalError(String msg) { | |
System.err.println(msg); | |
System.exit(1); | |
} | |
} |
코드 부연 설명
- 런타임에 총 6가지의 예외를 던질 수 있음
- 리플렉션을 사용하지 않았더라면 컴파일 시점에 체크할 수 있는 예외
- 모두 Checked Exception
- 클래스명만으로 인스턴스를 생성하기 위해 무려 25줄이나 되는 코드를 작성
- 리플렉션 사용하지 않았을 경우 생성자 1줄이면 끝
- 리플렉션 예외를 각각 잡는 대신 자바 7 버전부터 도입한 상위 클래스인 ReflectiveOperationException을 잡으면 코드량을 줄일 수 있음
- ReflectiveOperationException은 ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException 등을 포함하는 예외 클래스

리플렉션은 언제 사용해야 할까?
- 스프링 프레임워크는 많은 기능에서 리플렉션을 활용합니다..
- 의존성 주입 (Dependency Injection): 스프링은 객체 간의 의존성을 관리하기 위해 리플렉션을 사용
- Aspect-Oriented Programming (AOP): 스프링에서 AOP를 구현할 때 리플렉션을 사용
- 프록시 (Proxy) 생성: 스프링은 AOP를 구현할 때 주로 프록시를 사용하며 프록시는 대상 객체를 감싸고 호출을 가로채는데, 이를 위해 리플렉션을 사용하여 프록시를 동적으로 생성
- 데이터 바인딩: 스프링 MVC에서는 HTTP 요청 파라미터를 자바 객체의 필드에 바인딩할 때 리플렉션을 사용합니다. 즉, 요청 파라미터와 매핑된 객체를 생성하고, 객체의 필드에 값을 설정하기 위해 리플렉션을 사용
- 컨테이너 초기화 및 구성: 스프링은 XML 또는 어노테이션 기반의 설정을 통해 애플리케이션 컨텍스트를 초기화하고 구성
- 리플렉션을 사용하여 클래스를 로드하고, 필요한 설정을 적용하며, 빈을 등록
- 컴파일 시점에는 알 수 없는 클래스를 사용하는 프로그램을 작성한다면 리플렉션을 사용해야 함
- 단, 되도록 객체 생성에만 사용하고, 생성한 객체를 이용할 때는 적절한 인터페이스나 컴파일 시점에 알 수 있는 상위 클래스로 형변환해 사용하는 것을 권장
참고
이펙티브 자바
반응형
'JAVA > Effective Java' 카테고리의 다른 글
[아이템 67] 최적화는 신중히 하라 (0) | 2024.03.19 |
---|---|
[아이템 66] 네이티브 메서드는 신중히 사용하라 (0) | 2024.03.19 |
[아이템 64] 객체는 인터페이스를 사용해 참조하라 (0) | 2024.03.18 |
[아이템 63] 문자열 연결은 느리니 주의하라 (2) | 2024.03.17 |
[아이템 62] 다른 타입이 적절하다면 문자열 사용을 피하라 (0) | 2024.03.17 |