Programming/Java

[Java] 리플렉션 (Reflection)이란 무엇일까? (개념/ 예시)

JeongKyun 2022. 9. 23.
반응형

서론


이번 포스팅에서 다룰 내용은 '리플렉션'이다. 최근 "리플렉션이 무엇인가요?" 라는 질문을 받았는데, 제대로 된 답변을 못한 것 같다. 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에서는 메서드 호출이나 변수를 변경하려는 목적으로 많이 사용한다고 한다. 상황에 맞게 잘 쓸 수 있도록 더 많은 레퍼런스를 참고하여 보는걸 추천한다.

댓글

💲 많이 본 글