본문 바로가기
programming/java

[java]리플렉션(reflection)

by 힐무새 2017. 12. 4.

리플렉션이란?

리플렉션은 자바만의 독특한 기능이다. Reflection은 '투영, 반사'의 사전적 의미를 갖고 있다. 간단하게 자바의 리플렉션을 정의하자면 '객체를 이용해 클래스의 정보를 분석하는 기법'이다. 리플렉션은 구체적인 클래스 타입을 알지 못해도 그 클래스의 메서드, 타입, 변수 등을 접근할 수 있도록 해주는 API라고 보면 되겠다.

 

예를 들면 특정 클래스 이름을 인자로 전달할 시 그 이름에 해당하는 클래스 타입으로 객체를 생성하여 반환하는 메서드를 구현한다고 할 때 리플렉션을 활용할 수 있을 것이다. 

- 참조: https://kmongcom.wordpress.com/2014/03/15/%EC%9E%90%EB%B0%94-%EB%A6%AC%ED%94%8C%EB%A0%89%EC%85%98%EC%97%90-%EB%8C%80%ED%95%9C-%EC%98%A4%ED%95%B4%EC%99%80-%EC%A7%84%EC%8B%A4/

 

즉 위의 예시와 같이 수정사항이 발생할때마다 if-else문으로 코드를 구성하는 것 보다, 리플렉션을 통해 해당 클래스의 객체를 생성하는 것이 가능하다.

 

리플렉션이 가능한 이유

리플렉션이 가능한 이유는 무엇일까? 그 이유는 우리가 작성한 소스코드가 JVM이라는 프로그램 위에서 실행된다는 특성을 갖고 있기 때문이다. 우리가 자바 코드의 컴파일과 실행을 시도하면, JVM 상에 우리가 작성한 코드들(.java)는 .class라는 바이트 코드로 변환된 후 JVM 상에 로드된다. 그리고 클래스에 대한 정보는 JVM의 Runtime Data Area의 클래스 영역이라는 메모리 공간에 저장된다. 즉 런타임 시점에서 로드된 클래스들의 정보를 가져올 수 있기 때문에 이러한 리플렉션이 가능한 것이다.

 

 

클래스 이름으로 멤버 변수, 생성자, 메서드 목록 받아오기

리플렉션을 사용하기 위해서는 java.lang.reflect 패키지를 import해야 한다. 클래스의 멤벼 변수, 생성자, 메서드는 public 에 한해서 얻을 수 있다.(private는 불가능. private도 접근 가능한 방법이 있다곤 하지만, 추천하진 않는다)

 

import java.lang.reflect.*;
public class Main {
  public static void main(String[] args) {
    if (args.length != 1) {
      return;

    }
    /* 입력으로 클래스 이름을 받아옴. 
    현재 프로젝트 상의 클래스를 제외하고는 패키지 경로까지 기술해야 함. 
    여기서는 "Test" 클래스를 확인.*/
    String name = args[0];
    try {
      Class < ? > type = Class.forName(name);
      Field[] fields = type.getFields();
      Constructor[] constructors = type.getConstructors();
      Method[] methods = type.getMethods(); //field 출력            
      printList("Fields", fields); //constructor 출력            
      printList("Constructors", constructors); //method 출력            
      printList("Methods", methods);
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    }
  }
  static void printList(String s, Object[] o) {
    System.out.println("*** " + s + " ***");
    for (int i = 0; i < o.length; i++)
      System.out.println(o[i].toString());
  }
} // Test class. 코드는 분리. 
public class Test {
  public int f1;
  public int f2;
  public String f3;

  public Test(int f1, int f2) {
    this.f1 = f1;
    this.f2 = f2;
  }
  public Test() {
    f1 = 0;
    f2 = 0;

  }
  public Test(String str) {
    this.f3 = str;

  }
  public int getSum() {
    return f1 + f2;
  }
  public String getString() {
    return this.f3;
  }
}

입력

java Main Test

결과

*** Fields ***

public int Test.f1

public int Test.f2

public java.lang.String Test.f3

*** Constructors ***

public Test(java.lang.String)

public Test()

public Test(int,int)

*** Methods ***

public int Test.getSum()

public java.lang.String Test.getString()

public final void java.lang.Object.wait() throws java.lang.InterruptedException

public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException

public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException

public boolean java.lang.Object.equals(java.lang.Object)

public java.lang.String java.lang.Object.toString()

public native int java.lang.Object.hashCode()

public final native java.lang.Class java.lang.Object.getClass()

public final native void java.lang.Object.notify()

public final native void java.lang.Object.notifyAll()

 

특정 parameter type을 만족하는 메서드를 가져와서 실행하기

import java.lang.reflect.Method; 
/**  * Get a given method, and invoke it.  *  * @author Ian F. Darwin, http://www.darwinsys.com/  */
public class GetAndInvokeMethod {
  /**      * This class is just here to give us something to work on,      * with a println() call that will prove we got into it.      */
  
  static class X {
    public void work(int i, String s) {
      System.out.printf("Called: i=%d, s=%s%n", i, s);
    } // The main code does not use this overload.         
    public void work(int i) {
      System.out.println("Unexpected call!");
    }
  }
  
  public static void main(String[] argv) {
    try {
      Class < ? > clX = X.class; // or Class.forName("X");             
      
      // To find a method we need the array of matching Class types.
      Class < ? > [] argTypes = {
        int.class,
        String.class
      };
      
      // Now find a Method object for the given method.             
      Method worker = clX.getMethod("work", argTypes);
      
      // To INVOKE the method, we need the invocation arguments, as an Object array.             
      Object[] theData = { 42, "Chocolate Chips"}; 
      // The obvious last step: invoke the method.             
      // First arg is an instance, null if 
      static method worker.invoke(new X(), theData);
    } catch (Exception e) {
      System.err.println("Invoke() failed: " + e);
    }
  }
}

결과

Called: i=42, s=Chocolate Chips

 

* 만약 parameter로 primitive type을 사용할 때는 래퍼 클래스의 .TYPE 상수를 사용한다. 예를들면 int를 parameter로 받고자 할때, int.class 혹은 Integer.TYPE을 argType으로 정해야 한다.

'programming > java' 카테고리의 다른 글

[java]JVM 구조  (1) 2017.12.02
JIT(just-in-time) 컴파일  (0) 2017.12.01
[java] 정규 표현식  (0) 2017.06.01
[java] StringBuilder, StringBuffer의 차이  (0) 2017.06.01
[java]thread  (0) 2017.06.01