1. 필드 정보 가져오기
getFields()와 getDeclaredFields()의 차이
Java에서 각 클래스는 클래스 내부에 선언된 변수인 필드(fields)를 가집니다. 리플렉션을 사용하면 필드 이름, 타입, 접근 제한자(public/private) 등을 알아내고, 런타임에 접근할 수도 있습니다. 진입점은 Class<?> 객체입니다.
Class가 제공하는 필드 관련 핵심 메서드:
| 메서드 | 반환 내용 |
|---|---|
|
클래스와 그 부모 및 인터페이스의 모든 public 필드 배열 |
|
해당 클래스에 선언된 모든 필드 배열 |
비유하자면: getFields()는 전시 공간(public)만 도는 투어이고, getDeclaredFields()는 기술 구역(private, protected, package-private)까지 포함합니다.
예: 클래스의 모든 필드 출력
다음과 같은 클래스가 있다고 합시다:
public class Person {
public String name;
private int age;
protected String email;
}
필드 정보를 가져봅시다:
import java.lang.reflect.Field;
Class<?> clazz = Person.class;
// 모든 public 필드(상속된 것 포함)
System.out.println("Public fields:");
for (Field field : clazz.getFields()) {
System.out.println(field.getName() + " : " + field.getType().getSimpleName());
}
// 선언된 모든 필드(private 포함, 자신의 것만)
System.out.println("\nDeclared fields:");
for (Field field : clazz.getDeclaredFields()) {
System.out.println(field.getName() + " : " + field.getType().getSimpleName());
}
결과:
Public fields:
name : String
Declared fields:
name : String
age : int
email : String
필드의 접근 제한자/수정자는 어떻게 확인할까?
각 필드(Field)는 접근 제한자(public/private/protected, static, final 등)를 가집니다. getModifiers()로 값을 얻은 뒤 Modifier를 사용해 문자열로 변환할 수 있습니다:
import java.lang.reflect.Modifier;
for (Field field : clazz.getDeclaredFields()) {
int mods = field.getModifiers();
System.out.println(field.getName() + " : " + Modifier.toString(mods));
}
2. 메서드 정보 가져오기
getMethods()와 getDeclaredMethods()
메서드는 객체가 수행할 수 있는 동작입니다. 리플렉션을 사용하면 클래스에 어떤 메서드가 있는지, 그 파라미터, 반환 타입, 한정자, 애너테이션 등을 확인할 수 있습니다.
| 메서드 | 반환 내용 |
|---|---|
|
클래스와 부모의 모든 public 메서드(Object 포함) |
|
해당 클래스에 선언된 모든 메서드(private 포함) |
예: 클래스의 모든 메서드 출력
import java.lang.reflect.Method;
System.out.println("Public methods:");
for (Method method : clazz.getMethods()) {
System.out.println(method.getName());
}
System.out.println("\nDeclared methods:");
for (Method method : clazz.getDeclaredMethods()) {
System.out.println(method.getName());
}
결과(Person 기준):
Public methods:
getClass
hashCode
equals
toString
notify
notifyAll
wait
wait
wait
Declared methods:
(아무것도 없음, Person에 자체 메서드가 없는 경우)
Person에 메서드를 하나 추가해 봅시다:
public class Person {
public String name;
private int age;
protected String email;
public void sayHello() {
System.out.println("Hi!");
}
}
이제 getDeclaredMethods()가 해당 메서드도 보여줍니다.
메서드의 매개변수와 반환 타입을 확인하는 방법
for (Method method : clazz.getDeclaredMethods()) {
System.out.print(Modifier.toString(method.getModifiers()) + " ");
System.out.print(method.getReturnType().getSimpleName() + " ");
System.out.print(method.getName() + "(");
Class<?>[] params = method.getParameterTypes();
for (int i = 0; i < params.length; i++) {
System.out.print(params[i].getSimpleName());
if (i < params.length - 1) System.out.print(", ");
}
System.out.println(");");
}
출력:
public void sayHello();
3. 생성자 정보 가져오기
생성자는 객체 생성을 위해 사용하는 특별한 메서드입니다.
| 메서드 | 반환 내용 |
|---|---|
|
모든 public 생성자 |
|
클래스에 선언된 모든 생성자 |
예: 클래스의 모든 생성자 출력
import java.lang.reflect.Constructor;
System.out.println("Constructors:");
for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
System.out.print(clazz.getSimpleName() + "(");
Class<?>[] params = constructor.getParameterTypes();
for (int i = 0; i < params.length; i++) {
System.out.print(params[i].getSimpleName());
if (i < params.length - 1) System.out.print(", ");
}
System.out.println(");");
}
클래스에 기본 생성자만 있어도 출력됩니다.
4. 애너테이션: 클래스, 메서드 또는 필드에 무엇이 붙었는지 확인하기
애너테이션은 클래스, 메서드, 필드, 매개변수에 부착할 수 있는 특별한 메타데이터입니다. 리플렉션을 통해 요소에 어떤 애너테이션이 있는지 확인할 수 있습니다.
애너테이션 가져오기
- 클래스: clazz.getAnnotations()
- 메서드: method.getAnnotations()
- 필드: field.getAnnotations()
예: @Deprecated 애너테이션 존재 여부 확인
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(Deprecated.class)) {
System.out.println(method.getName() + " is @Deprecated");
}
}
예: 클래스의 모든 애너테이션 출력
for (var annotation : clazz.getAnnotations()) {
System.out.println(annotation);
}
5. 실습: 클래스 구조 분석 미니 프로그램
지금까지를 종합하여 클래스 이름을 받아 구조(필드, 메서드, 생성자, 애너테이션)를 출력하는 작은 유틸리티를 만들어 봅시다.
코드 예시: ClassInspector
import java.lang.reflect.*;
import java.util.Scanner;
public class ClassInspector {
public static void main(String[] args) throws Exception {
Scanner scanner = new Scanner(System.in);
System.out.print("클래스의 정규 이름을 입력하세요(예: java.util.ArrayList): ");
String className = scanner.nextLine();
Class<?> clazz = Class.forName(className);
System.out.println("\n== 클래스: " + clazz.getName() + " ==");
// 클래스 애너테이션
System.out.println("애너테이션:");
for (Annotation annotation : clazz.getAnnotations()) {
System.out.println(" " + annotation);
}
// 필드
System.out.println("\n필드:");
for (Field field : clazz.getDeclaredFields()) {
System.out.println(" " + Modifier.toString(field.getModifiers())
+ " " + field.getType().getSimpleName()
+ " " + field.getName());
}
// 메서드
System.out.println("\n메서드:");
for (Method method : clazz.getDeclaredMethods()) {
System.out.print(" " + Modifier.toString(method.getModifiers())
+ " " + method.getReturnType().getSimpleName()
+ " " + method.getName() + "(");
Class<?>[] params = method.getParameterTypes();
for (int i = 0; i < params.length; i++) {
System.out.print(params[i].getSimpleName());
if (i < params.length - 1) System.out.print(", ");
}
System.out.println(");");
}
// 생성자
System.out.println("\n생성자:");
for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
System.out.print(" " + Modifier.toString(constructor.getModifiers())
+ " " + clazz.getSimpleName() + "(");
Class<?>[] params = constructor.getParameterTypes();
for (int i = 0; i < params.length; i++) {
System.out.print(params[i].getSimpleName());
if (i < params.length - 1) System.out.print(", ");
}
System.out.println(");");
}
}
}
작동 방식
- 사용자가 클래스의 정규 이름(예: "java.util.ArrayList" 또는 본인 클래스)을 입력합니다.
- 프로그램이 클래스를 동적으로 로드하여 애너테이션, 필드, 메서드, 생성자를 출력합니다.
- JDK 표준 클래스들로 시도해 보세요 — 내부에 흥미로운 내용이 얼마나 많은지 볼 수 있습니다!
6. 유용한 포인트와 시각화
시각화: 리플렉션으로 클래스에서 알 수 있는 것들
graph TD
A[Class<?>] --> B["getFields/getDeclaredFields"]
A --> C[getMethods/getDeclaredMethods]
A --> D[getConstructors/getDeclaredConstructors]
A --> E[getAnnotations]
B --> F[Field: 타입, 이름, 수정자]
C --> G[Method: 반환 타입, 매개변수]
D --> H[Constructor: 매개변수]
E --> I["Annotation[]"]
표: 어디에서 정보를 찾을까
| 무엇을 알고 싶은가 | 리플렉션으로 얻는 방법 |
|---|---|
| 모든 public 필드 | |
| 선언된 모든 필드 | |
| 모든 public 메서드 | |
| 선언된 모든 메서드 | |
| 모든 public 생성자 | |
| 선언된 모든 생성자 | |
| 클래스 애너테이션 | |
| 메서드/필드 애너테이션 | |
| 수정자 | |
7. 리플렉션 사용할 때 흔한 실수
오류 №1: getFields()와 getDeclaredFields()를 혼동.
private 필드를 찾고 있다면 getFields()가 아니라 getDeclaredFields()를 사용하세요. 전자는 클래스에 선언된 모든 필드를 반환하고, 후자는 오직 public 필드(그리고 상속된 필드까지!)만 반환합니다.
오류 №2: Checked 예외를 처리하지 않음.
많은 리플렉션 API가 예외를 던집니다(예: ClassNotFoundException, SecurityException). 반드시 처리하거나 메서드 시그니처에 선언하세요.
오류 №3: 접근 제한자를 고려하지 않음.
private 필드와 메서드에 접근하려면 setAccessible(true)를 호출해야 하며, 그렇지 않으면 IllegalAccessException이 발생합니다. 예:
Field field = clazz.getDeclaredField("age");
field.setAccessible(true); // private 필드에 대한 접근 허용
int value = (int) field.get(person);
오류 №4: 자신이 작성한 멤버만 보일 것으로 기대함.
getMethods()와 getFields()는 현재 클래스뿐 아니라 모든 부모 클래스(그리고 Object까지)의 public 멤버를 반환합니다. 자신이 작성한 것만 보일 거라 기대했다면 놀랄 수 있습니다.
오류 №5: 애너테이션 존재 여부를 올바르게 확인하지 않음.
특정 애너테이션을 확인할 때는 isAnnotationPresent()를 사용하세요. 불필요하게 애너테이션 배열을 직접 순회하지 마십시오.
GO TO FULL VERSION