서론
이번 포스팅에서 다룰 내용은 '리플렉션'이다. 최근 "리플렉션이 무엇인가요?" 라는 질문을 받았는데, 제대로 된 답변을 못한 것 같다. C# 개발을 할 때 분명 사용은 해보았지만 개념적으로 설명하기엔 많이 미비한것같아 이번 기회에 정리를 해보려한다.
리플렉션(Reflection)이란?
리플렉션은 구체적인 클래스 타입을 알지 못하더라도 그 클래스의 메서드, 타입, 변수들에 접근할 수 있도록 해주는 자바 API를 말하며,
컴파일 시간이 아닌 실행 시간에 동적으로 특정 클래스의 정보를 추출할 수 있는 프로그래밍 기법이라 할 수 있다.
그럼 이 리플렉션은 언제 사용할까?
- 동적으로 클래스를 사용해야할 때 사용한다.
- 다시 말해, 작성 시점에는 어떠한 클래스를 사용해야할지 모르지만 런타임 시점에서 가져와 실행해야하는 경우 필요하다.
- 프레임워크나 IDE에서 이런 동적 바인딩을 이용한 기능을 제공한다.
- 리플렉션 사용 예시
- IntelliJ의 자동완성 기능
- 스프링 어노테이션
리플렉션을 사용하여 가져올 수 있는 정보는 다음과 같다.
- Class
- Constructor
- Method
- Field
이제 이 리플렉션을 사용하여 정보를 가져오는 방법을 예시를 통해 알아보자.
예시 작성을 위해 Parent, Child, Test.java 클래스를 만들어주자.
Parent.java
package reflectiontest;
public class Parent {
private String str1 = "1";
public String str2 = "2";
public Parent() {
}
private void method1() {
System.out.println("method1");
}
public void method2(int n) {
System.out.println("method2: " + n);
}
private void method3() {
System.out.println("method3");
}
}
Child.java
package reflectiontest;
public class Child extends Parent {
public String cstr1 = "1";
private String cstr2 = "2";
public Child() {
}
private Child(String str) {
cstr1 = str;
}
public int method4(int n) {
System.out.println("method4: " + n);
return n;
}
private int method5(int n) {
System.out.println("method5: " + n);
return n;
}
}
Test.java
package reflectiontest;
import java.lang.reflect.Method;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
class Test {
public static void main(String args[]) throws Exception {
}
}
- 아래의 값을 넣어 출력하는 로직은 모두 Test.java안에서 이뤄진 것이라 보시면 됩니다.
1. Class 찾기
- Class 객체는 클래스 또는 인터페이스를 가리킨다. (java.lang.Class)
- Class 객체는 여러 메서드를 제공한다. 그 중 getName()은 클래스의 이름을 리턴해준다.
Class clazz = Child.class;
System.out.println("Class name: " + clazz.getName());
//출력
Class name: test.Child
위와 같이 클래스 명을 가져올 수 있습니다. 그런데 만약, 클래스의 이름을 몰랐다고 했을 때는 어떻게 클래스 명을 가져올까? 아래의 내용을 보자.
Class clazz2 = Class.forName("test.Child");
System.out.println("Class name: " + clazz2.getName());
//출력
Class name: test.Child
위와 같이 Class.forName()에 클래스 이름을 인자로 전달하여 클래스 정보를 가져올 수 있다. 주의할점은 패키지 네임이 포함된 클래스 명을 써줘야한다.
2. Constructor 찾기
Class clazz = Class.forName("test.Child");
Constructor constructor = clazz.getDeclaredConstructor();
System.out.println("Constructor: " + constructor.getName());
//출력
Constructor: test.Child
위와 같이 getDeclaredConstructor을 활용하여 인자 없는 생성자를 가져올 수 있다. 그렇다면, 인자가 있는 오버로딩 생성자를 가져오는 방법은 무엇일까? 간단하다. 타입에 일치하는 인자를 넣어주면 된다.
Class clazz = Class.forName("test.Child");
Constructor constructor2 = clazz.getDeclaredConstructor(String.class);
System.out.println("Constructor(String): " + constructor2.getName());
//출력
Constructor(String): test.Child
위 두개는 인자가 없거나, 있거나 한 생성자를 가져오는 방법이고 모든 생성자를 가져오는 방법도 있다.
Class clazz = Class.forName("test.Child");
Constructor constructors[] = clazz.getDeclaredConstructors();
for (Constructor cons : constructors) {
System.out.println("Get constructors in Child: " + cons);
}
//출력
Get constructors in Child: private test.Child(java.lang.String)
Get constructors in Child: public test.Child()
위와 같이 모든 생성자를 가져올 수 있고, public 생성자만 가져오는 방법도 있다.
Class clazz = Class.forName("test.Child");
Constructor constructors2[] = clazz.getConstructors();
for (Constructor cons : constructors2) {
System.out.println("Get public constructors in Child: " + cons);
}
//출력
Get public constructors in both Parent and Child: public test.Child()
3. Method 찾기
Class clazz = Class.forName("test.Child");
Method method1 = clazz.getDeclaredMethod("method4", int.class);
System.out.println("Find out method4 method in Child: " + method1);
//출력
Find out method4 method in Child: public int test.Child.method4(int)
위와 같이 메서드를 찾을 수 있다. 만약 인자가 없거나 있는 메서드를 찾고 싶다면 아래와 같이 진행하면 된다.
//인자가 없는 경우 null을 넣어준다.
Class clazz = Class.forName("test.Child");
Method method1 = clazz.getDeclaredMethod("method4", null);
// 인자가 두 개 이상이 경우
Class clazz = Class.forName("test.Child");
Class partypes[] = new Class[1];
partypes[0] = int.class;
Method method = clazz.getDeclaredMethod("method4", partypes);
//public 메서드만
Class clazz = Class.forName("test.Child");
Method methods2[] = clazz.getMethods();
for (Method method : methods2) {
System.out.println("Get public methods in both Parent and Child: " + method);
}
위와 같이 인자 별, 또는 접근제어자 public만 추출해 낼 수 있다. 마지막으로 Field에 대해 알아보자.
4. Field 변경
Class clazz = Class.forName("test.Child");
Field field = clazz.getDeclaredField("cstr1");
System.out.println("Find out cstr1 field in Child: " + field);
//출력
Find out cstr1 field in Child: public java.lang.String test.Child.cstr1
위의 다른 예시들과 동일하게 사용하면 된다. getDeclaredField()를 사용하면 되며, 만약. public 필드만 찾고싶다면 getFields()를 사용하면 된다.
Class clazz = Class.forName("test.Child");
Field fields2[] = clazz.getFields();
for (Field field : fields2) {
System.out.println("Get public fields in both Parent and Child: " + field);
}
//출력
Get public fields in both Parent and Child: public java.lang.String test.Child.cstr1
Get public fields in both Parent and Child: public java.lang.String test.Parent.str2
이상 리플렉션의 기능 중 대표적으로 네 가지에 대해 알아보았고, 추가로 Static 메서드 호출과 필드를 변경하는 방법에 대해서도 알아보자.
Static 메서드 호출 또는 필드 변경
아래의 예시와 같이 클래스를 생성해준다.
StaticExample.java
package test;
public class StaticExample {
public static String EXAMPLE = "Example";
public static int getSquare(int num) {
System.out.println("Get square: " + num * num);
return num * num;
}
}
static 메서드 정보를 가져올 땐 invoke()라는 키워드를 사용하여 객체를 전달하는 인자에 대해 null을 넣어주면 static 메서드가 호출된다.
Class clazz = Class.forName("test.StaticExample");
Method method = clazz.getDeclaredMethod("getSquare", int.class);
method.invoke(null, 10);
//출력
Get square: 100
static 필드 정보를 가져오는 방법도 위와 동일한데, 신경써야 할 점은 set() 또는 get() 함수를 사용할 때 객체로 전달되는 인자에 대해 null을 넣어줘야 된다.
Class clazz = Class.forName("test.StaticExample");
Field fld = clazz.getDeclaredField("EXAMPLE");
fld.set(null, "Hello, World");
System.out.println("StaticExample.EXAMPLE: " + fld.get(null));
//출력
StaticExample.EXAMPLE: Hello, World
결론
필자같은 경우 리플렉션을 C#으로 만든 프로그램에서 C++로 만들어진 dll을 호출하여 dll 안에 있는 메서드 정보를 가져올 때 사용했었던 적이 있는데, Java에서는 메서드 호출이나 변수를 변경하려는 목적으로 많이 사용한다고 한다. 상황에 맞게 잘 쓸 수 있도록 더 많은 레퍼런스를 참고하여 보는걸 추천한다.
'Programming > Java' 카테고리의 다른 글
[Java] 코딩테스트 자주 사용하는 문법 정리 (0) | 2022.07.15 |
---|---|
[Java] Singleton 패턴이란? (개념/ 사용 이유/ 장단점/ 예제) (0) | 2022.05.29 |
[Java] Optional<T> 이란? (개념/ 종류 별/ 사용 방법/ 예제) (2) | 2022.05.21 |
[Java] 함수형 프로그래밍 - Function Interface / Custom Functional Interface (개념 / 예제) (0) | 2022.05.12 |
[Java] SOLID - 의존성 역전 원칙 (DIP / Dependency Inversion Principle)이란? (개념/ 예제) (1) | 2022.05.12 |
댓글