Java Reflection은 실행 중인 프로그램의 클래스, 인터페이스, 필드, 메서드 등의 정보를 동적으로 분석하고 조작할 수 있는 기능을 제공하는 Java의 API다. Reflection을 사용하면 컴파일 시간에 알려지지 않은 클래스의 정보를 검색하고, 객체를 생성하고, 메서드를 호출하고, 필드에 접근할 수 있다.
Reflection을 사용하여 다음과 같은 작업을 수행할 수 있습니다:
-
클래스 정보 가져오기: Reflection을 사용하면 클래스의 이름, 상위 클래스, 인터페이스, 필드, 메서드 등의 정보를 가져올 수 있다.
-
객체 생성하기: Reflection을 사용하여 동적으로 클래스의 인스턴스를 생성할 수 있다. 이는 프로그램 실행 중에 클래스를 동적으로 로드하고 인스턴스를 생성하는 데 사용될 수 있다.
-
메서드 호출하기: Reflection을 사용하여 클래스의 메서드를 호출할 수 있다. 메서드 이름, 매개변수 타입 및 인수를 동적으로 지정할 수 있다.
-
필드에 접근하기: Reflection을 사용하여 클래스의 필드에 접근하고 값을 설정하거나 가져올 수 있습니다. 필드의 이름을 동적으로 지정하여 필드를 조작할 수 있다.
Reflection 예시
Class 객체 가져오기
Class<MyClass> myClass = MyClass.class; String className = "com.example.MyClass";
Class<?> myClass = Class.forName(className); MyClass myObject = new MyClass();
Class<? extends MyClass> myClass = myObject.getClass(); Class 정보 가져오기
import java.lang.reflect.*;
public class ReflectionExample {
public static void main(String[] args) throws Exception {
Class<?> myClass = MyClass.class;
// 클래스 이름 가져오기
String className = myClass.getName();
System.out.println("Class Name: " + className);
// 상위 클래스 가져오기
Class<?> superClass = myClass.getSuperclass();
System.out.println("Superclass: " + superClass.getName());
// 인터페이스 가져오기
Class<?>[] interfaces = myClass.getInterfaces();
System.out.println("Interfaces:");
for (Class<?> iface : interfaces) {
System.out.println("- " + iface.getName());
}
// 모든 메서드 가져오기
Method[] methods = myClass.getMethods();
System.out.println("Methods:");
for (Method method : methods) {
System.out.println("- " + method.getName());
}
// 모든 필드 가져오기
Field[] fields = myClass.getFields();
System.out.println("Fields:");
for (Field field : fields) {
System.out.println("- " + field.getName());
}
// 생성자 가져오기
Constructor<?>[] constructors = myClass.getConstructors();
System.out.println("Constructors:");
for (Constructor<?> constructor : constructors) {
System.out.println("- " + constructor.getName());
}
}
} 생성자 사용하기
import java.lang.reflect.*;
public class ReflectionConstructorExample {
public static void main(String[] args) throws Exception {
Class<?> myClass = MyClass.class;
// 매개변수가 없는 생성자 호출
Constructor<?> constructor1 = myClass.getConstructor();
Object instance1 = constructor1.newInstance();
System.out.println(instance1);
// 매개변수가 있는 생성자 호출
Constructor<?> constructor2 = myClass.getConstructor(String.class, int.class);
Object instance2 = constructor2.newInstance("Example", 42);
System.out.println(instance2);
}
} 매개변수가 있는 경우, 매개변수 타입 클래스를 순선대로 전달한다. 생성자를 가져왔다면 newInstance 메서드를 사용해서 생성자를 호출한다.
Field 조작하기
import java.lang.reflect.*;
public class ReflectionFieldExample {
public static void main(String[] args) throws Exception {
Class<?> myClass = MyClass.class;
// 필드에 값 설정
Field field = myClass.getField("myField");
Object instance = myClass.newInstance();
field.set(instance, 42);
// 필드 값 가져오기
int fieldValue = (int) field.get(instance);
System.out.println("Field value: " + fieldValue);
}
} getField() 메서드로 필드정보를 가져온 뒤, set() 메서드를 호출하면서 필드가 속한 인스턴스와 설정할 값을 전달한다. 필드 값을 가져올 때는 get() 메서드를 사용한다.
💡 Field와 Method 정보를 가져올 때 getField() 또는 getMethod() 메소드를 사용한다. 이 외에도 getDeclaredFields(), getDeclaredMethods(), getDeclaredAnnotations() 등이 있는데 앞에 declared가 붙은 메소드의 경우 상속받은 클래스와 인터페이스를 제외한 해당 클래스에서 직접 정의된 내용만 가져온다.
Method 다루기
import java.lang.reflect.*;
public class ReflectionMethodExample {
public static void main(String[] args) throws Exception {
Class<?> myClass = MyClass.class;
// 매개변수가 없는 메서드 호출
Method method1 = myClass.getMethod("myMethod");
Object instance1 = myClass.newInstance();
method1.invoke(instance1);
// 매개변수가 있는 메서드 호출
Method method2 = myClass.getMethod("myMethod", String.class, int.class);
Object instance2 = myClass.newInstance();
method2.invoke(instance2, "Hello, Reflection!", 42);
}
} 위 예시에서는 MyClass 클래스의 메서드를 Reflection을 사용하여 호출한다. getMethod() 메서드를 사용하여 메서드 객체를 가져온 후, invoke() 메서드를 호출하여 메서드를 실행한다. 첫 번째 예시에서는 매개변수가 없는 myMethod 메서드를 호출한다. getMethod() 메서드에 메서드 이름을 전달한다. 두 번째 예시에서는 **String**과 int 타입의 매개변수를 갖는 myMethod 메서드를 호출한다. getMethod() 메서드에 메서드 이름과 해당 매개변수 타입들을 순서대로 전달한다. 각각의 invoke() 메서드 호출은 첫 번째 인수로 메서드가 속한 객체를 전달하고, 두 번째 인수부터는 메서드의 매개변수들을 전달한다.
Reflection을 사용하여 메서드를 호출할 때는 예외 처리에 유의해야 한다. NoSuchMethodException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException 등의 예외가 발생할 수 있으므로 적절히 처리해야 한다.
또한 메서드가 private이거나 protected인 경우에는 getDeclaredMethod() 메서드를 사용하고, 필요에 따라 **setAccessible(true)**를 호출하여 접근 가능하게 설정해야 한다.
어노테이션(Annotations)
import java.lang.annotation.*;
import java.lang.reflect.*;
// 예시로 사용할 어노테이션 정의
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface MyAnnotation {
String value();
}
public class ReflectionAnnotationExample {
// 어노테이션이 적용된 메서드
@MyAnnotation("Hello, Annotation!")
public static void myMethod() {
System.out.println("Inside myMethod");
}
public static void main(String[] args) throws Exception {
// 어노테이션 가져오기
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
// 어노테이션 값 사용
if (annotation != null) {
String value = annotation.value();
System.out.println("Annotation value: " + value);
}
}
} 위의 예시에서는 **MyAnnotation**이라는 어노테이션을 정의하고, ReflectionAnnotationExample 클래스의 myMethod 메서드에 어노테이션을 적용했다. getAnnotation() 메서드를 사용하여 해당 메서드에 적용된 어노테이션 객체를 가져온다. MyAnnotation 타입의 어노테이션이 적용되어 있으므로, **getAnnotation(MyAnnotation.class)**을 호출합니다. 반환된 어노테이션 객체를 사용하여 어노테이션의 값을 가져와서 출력합니다.
Reflection을 사용하여 어노테이션을 다룰 때에는 적절한 예외 처리를 수행해야 한다. NoSuchMethodException, NoSuchFieldException, SecurityException, IllegalAccessException 등의 예외가 발생할 수 있으므로 적절히 처리해야 한다.
또한, 어노테이션의 유지 정책(Retention)과 대상(Target)에 주의해야 한다. 예시에서는 **@Retention(RetentionPolicy.RUNTIME)**과 **@Target(ElementType.METHOD)**을 사용하여 어노테이션을 실행 시간에 유지하고 메서드에 적용하도록 정의했다.
Reflection이 사용되는 경우
-
런타임에 동적으로 클래스를 로드하고 인스턴스를 생성해야 하는 경우.
-
애플리케이션에서 외부 구성 파일로부터 클래스 이름을 동적으로 읽어와야 하는 경우.
-
프레임워크 또는 라이브러리에서 일반화된 방법으로 객체를 검사, 조작 또는 확장해야 하는 경우.
-
디버깅 도구 또는 테스트 도구에서 객체의 정보를 동적으로 분석하고 조작해야 하는 경우.
Reflection은 강력하고 유연한 기능을 제공하지만, 오용하면 성능 저하, 유지 보수의 어려움, 타입 안정성의 상실 등의 문제가 발생할 수 있다. 일반적으로 메소드를 호출한다면, 컴파일 시점에 분석된 클래스를 사용하지만 리플렉션은 런타임 시점에 클래스를 분석하므로 속도가 느리다. 따라서 컴파일 시점에 타입 체크도 불가능하며 객체의 추상화가 깨지는 문제가 있다.