에몽이

Java Reflection 개념 및 사용법 본문

java

Java Reflection 개념 및 사용법

ian_hodge 2017. 7. 25. 19:03

Java Reflection 정의


리플렉션이란 객체를 통해 클래스의 정보를 분석해 내는 프로그램 기법을 말한다. 투영, 반사 라는 사전적인 의미를 지니고 있다.


스프링을 공부하다가 보면 BeanFactory 라는 Spring Container 개념을 학습하게 된다.

이 BeanFactory는 어플리케이션이 실행한 후 객체가 호출 될 당시 객체의 인스턴스를 생성하게 되는데 
그 때 필요한 기술이 Reflection이다.
자바는 스크립트 언어가 아닌 컴파일 언어이다. 물론 .java -> .class -> 실행이라는 2단계의 메커니즘을 가지고 있지만 컴파일 언어로 분리하는 게 옳다. 원래 자바에서는 동적으로 객체를 생성하는 기술이 없었다. 그리고 동적으로 인스턴스를 생성하는 Reflection으로 그 역활을 대신하게 된다.

리플렉션이란 객체를 통해 클래스의 정보를 분석해 내는 프로그램 기법을 말한다. 투영, 반사 라는 사전적인 의미를 지니고 있다

가정을 해 보자. 만약 객체의 메모리만을 알고 있고, 그리고 객체의 형에 대해서는 모른다고 생각하보자, 

리플렉션으로 형은 알고 있지만 형변환을 할 수 없는 상태에서 객체의 메서드를 호출할 수 있다.



Class c = Data.class;
//Class c = Class.forName("클래스이름");

Method[] m = c.getMethods();                     

Field[] f = c.getFields();
Constructor[] cs = c.getConstructors();
Class[] inter = c.getInterfaces();
Class superClass = c.getSuperclass();



Java Reflection 의 사용

 

 

reflection 은 자바의 특징이다. 실행중인 자바프로그램 내부를 검사하고 내부의 속성을 수정할 수 있도록 한다. 예를 들어, 어떤 자바클래스가 가진 모든 멤버의 이름을 얻거나 보여줄 수 있다.
자바에서 클래스가 그 자신을 조사하고 수정하는 것이  많다고 할수는 없으나 다른 언어에서는 볼수 없는 특징이다.

reflection 이 구체적인 쓰임중에 하나가 빌더툴을 이용해서 소프트웨어 콤포넌트를 만드는 곳에서 이다. 툴은 reflection 을 사용해서 동적으로 로딩되는 자바 콤포넌트(클래스)의 속성을 얻을 수 있다.

 

예제

import java.lang.reflect.Method;


public class DumpMethods {

public static void main(String args[]) {

try {

Class c = Class.forName(args[0]);

Method m[] = c.getDeclaredMethods();

for (int i = 0; i < m.length; i++)

System.out.println(m[i].toString());

} catch (Throwable e) {

System.err.println(e);

}

}

}

 

명령

java DumpMethods java.util.Stack

 

실행 결과

  public java.lang.Object java.util.Stack.push(
    java.lang.Object)
   public synchronized 
     java.lang.Object java.util.Stack.pop()
   public synchronized
      java.lang.Object java.util.Stack.peek()
   public boolean java.util.Stack.empty()
   public synchronized 
     int java.util.Stack.search(java.lang.Object)
 
이 프로그램은 java.util.Stack 의 속성과 반환값에 따라 메소드리스트를 출력한다.
이 프로그램은 Class.forName 을 통해서 클래스를 로딩하고 getDeclaredMethods 을 통해서 클래스에서 정의한 메소드리스트를 얻는다. java.lang.reflect.Method  는 단일 메쏘드를 나타내는 클래스이다.

 

Reflection 을 사용한 Set up
Method 와 같은 reflection class 는 java.lang.reflect 에 있다. 이 클래스를 사용하기 위해서는 세가지 스텝을 밟아야 한다.
첫번째는 수정하기를 원하는 클래스의 java.lang.Class 객체를 얻어야 한다. java.lang.Class 는 클래스를 표현하고, 실행중인 자바 프로그램과 인터페이스한다.


자바기본형에 대한 클래스 정보를 얻는 방법.
방법 1 : 
   Class c = Class.forName("java.lang.String");


방법 2 : 
   Class c = int.class;


방법 3 : 기본형의 경우 (Integer 와 같은) Wrapper 에 기정의된 TYPE을 사용한다. 
  Class c = Integer.TYPE;


두번째 스텝은 getDeclaredMethods와 같은 메소드를 Call 해서, 클래스에 정의된 모든 메소드의 리스트를 얻는다.

세번째 스텝은 정보 수정을 위해 Reflection API 를 이용한다.

   Class c = Class.forName("java.lang.String");
   Method m[] = c.getDeclaredMethods();
   System.out.println(m[0].toString());

String 에 선언된 첫번째 메소트를 보여준다.


 

Simulating the instanceof Operator
클래스 정보가 있으면, 다음 스텝은 클래스 객체에 대해서 기본적인 질의다. Class.isInstance 메쏘드는 instanceof 를 통해서 구현될 수 있다.


class A {}


public class instance1 {

public static void main(String args[]) {

try {

Class cls = Class.forName("A");

boolean b1 = cls.isInstance(new Integer(37));

System.out.println(b1);

boolean b2 = cls.isInstance(new A());

System.out.println(b2);

} catch (Throwable e) {

System.err.println(e);

}

}

}

 
A의 클래스객체가 만들어진다. 그리고 class instance objects 가 A 의 인스턴스인지 체크한다. 
Integer(37) 은 아니지만, new A() 는 True 이다.


클래스 메쏘드 찾기
reflection 의 가장 기초적인 쓰임은 클래스에서 정의한 메소드가 무엇인지 찾아내는 것이다. 이를 위해서 다음 코드가 사용될 수 있다.

import java.lang.reflect.*;


public class method1 {

private int f1(Object p, int x) throws NullPointerException {

if (p == null)

throw new NullPointerException();

return x;

}


public static void main(String args[]) {

try {

Class cls = Class.forName("method1");


Method methlist[] = cls.getDeclaredMethods();

for (int i = 0; i < methlist.length; i++) {

Method m = methlist[i];

System.out.println("name  = " + m.getName());

System.out.println("decl class = " + m.getDeclaringClass());

Class pvec[] = m.getParameterTypes();

for (int j = 0; j < pvec.length; j++)

System.out.println(" param #" + j + " " + pvec[j]);

Class evec[] = m.getExceptionTypes();

for (int j = 0; j < evec.length; j++)

System.out.println("exc #" + j + " " + evec[j]);

System.out.println("return type = " + m.getReturnType());

System.out.println("-----");

}

} catch (Throwable e) {

System.err.println(e);

}

}

}


getDeclaredMethods 을 통해서 메쏘드 리스트를 조회한다. getDeclaredMethods 대신에 getMethods 를 사용하면 상속된 메쏘드에 대한 정보를 얻을 수 있다.

실행결과
   name = f1
   decl class = class method1
   param #0 class java.lang.Object
   param #1 int
   exc #0 class java.lang.NullPointerException
   return type = int
   -----
   name = main
   decl class = class method1
   param #0 class [Ljava.lang.String;
   return type = void
   -----
 
Constructors 에 대한 정보얻기


import java.lang.reflect.*;


public class constructor1 {

public constructor1() {

}


protected constructor1(int i, double d) {

}


public static void main(String args[])

      {

         try {

           Class cls = Class.forName("constructor1");

        

           Constructor ctorlist[] = cls.getDeclaredConstructors();

           for (int i = 0; i < ctorlist.length; i++) {

               Constructor ct = ctorlist[i];

               System.out.println("name = " + ct.getName());

               System.out.println("decl class = " + ct.getDeclaringClass());

               Class pvec[] = ct.getParameterTypes();

               

               for (int j = 0; j < pvec.length; j++)

                  System.out.println("param #" + j + " " + pvec[j]);

               

               Class evec[] = ct.getExceptionTypes();

               

               for (int j = 0; j < evec.length; j++)

                  System.out.println("exc #" + j + " " + evec[j]);

               

               System.out.println("-----");

            }

          }

          catch (Throwable e) {

             System.err.println(e);

          }

      }

}

이 프로그램에서는 리턴타입정보가 없는데... 생성자는 리턴타입을 갖지 않기 때문이다.
실행결과
   name = constructor1
   decl class = class constructor1
   -----
   name = constructor1
   decl class = class constructor1
   param #0 int
   param #1 double
   -----
 
Class Fields 찾기

   import java.lang.reflect.*;


public class field1 {

private double d;

public static final int i = 37;

String s = "testing";


public static void main(String args[])

      {

         try {

          Class cls = Class.forName("field1");

          Field fieldlist[] = cls.getDeclaredFields();

             for (int i = 0; i < fieldlist.length; i++) {

              Field fld = fieldlist[i];

                 System.out.println("name = " + fld.getName());

                 System.out.println("decl class = " + fld.getDeclaringClass());

                 System.out.println("type = " + fld.getType());

                 int mod = fld.getModifiers();

                 System.out.println("modifiers = " + Modifier.toString(mod));

                 System.out.println("-----");

            }

          }

          catch (Throwable e) {

             System.err.println(e);

          }

       }

}

이전예제와 비슷하다. 새로운 특징은 modifier 의 사용이다.
modifier는 'private int' 와 같은 필트멤버를 표현하기위한 reflection class 이다.
modifier 는 숫자로 표현된다. Modifier.toString 은 'final' 앞의 'static' 과 같은 선언순서의 문자열표현을 리턴한다.

실행결과 :
  name = d
   decl class = class field1
   type = double
   modifiers = private
   -----
   name = i
   decl class = class field1
   type = int
   modifiers = public static final
   -----
   name = s
   decl class = class field1
   type = class java.lang.String
   modifiers =
   ----- 
 
이름으로 메쏘드 실행하기.
  import java.lang.reflect.*;


public class method2 {

public int add(int a, int b) {

return a + b;

}


public static void main(String args[]) {

try {

Class cls = Class.forName("method2");

Class partypes[] = new Class[2];

partypes[0] = Integer.TYPE;

partypes[1] = Integer.TYPE;

Method meth = cls.getMethod("add", partypes);

method2 methobj = new method2();

Object arglist[] = new Object[2];

arglist[0] = new Integer(37);

arglist[1] = new Integer(47);

Object retobj = meth.invoke(methobj, arglist);

Integer retval = (Integer) retobj;

System.out.println(retval.intValue());

} catch (Throwable e) {

System.err.println(e);

}

}

}

프로그램이 add 라는 메쏘드를 실행시키는데 실행시까지 알지 못한다고 가정해보자. 메쏘드 이름은 실행시간에 알수있다. 
getMethod 는 클래스에서 두개의 숫자 파라미터와 해당 이름을 가진 메쏘드를 찾아낸다.


새로운 객체 만들기

 import java.lang.reflect.*;


public class constructor2 {

public constructor2() {

}


public constructor2(int a, int b) {

System.out.println("a = " + a + " b = " + b);

}


public static void main(String args[]) {

try {

Class cls = Class.forName("constructor2");

Class partypes[] = new Class[2];

partypes[0] = Integer.TYPE;

partypes[1] = Integer.TYPE;

Constructor ct = cls.getConstructor(partypes);

Object arglist[] = new Object[2];

arglist[0] = new Integer(37);

arglist[1] = new Integer(47);

Object retobj = ct.newInstance(arglist);

} catch (Throwable e) {

System.err.println(e);

}

}

}


필드값 바꾸기

  import java.lang.reflect.*;


public class field2 {

public double d;


public static void main(String args[]) {

try {

Class cls = Class.forName("field2");

Field fld = cls.getField("d");

field2 f2obj = new field2();

System.out.println("d = " + f2obj.d);

fld.setDouble(f2obj, 12.34);

System.out.println("d = " + f2obj.d);

} catch (Throwable e) {

System.err.println(e);

}

}

}

 
배열의 사용

import java.lang.reflect.*;


public class array1 {

public static void main(String args[]) {

try {

Class cls = Class.forName("java.lang.String");

Object arr = Array.newInstance(cls, 10);

Array.set(arr, 5, "this is a test");

String s = (String) Array.get(arr, 5);

System.out.println(s);

} catch (Throwable e) {

System.err.println(e);

}

}

}

 

 import java.lang.reflect.*;


public class array2 {

public static void main(String args[]) {

int dims[] = new int[] { 5, 10, 15 };

Object arr = Array.newInstance(Integer.TYPE, dims);


Object arrobj = Array.get(arr, 3);

Class cls = arrobj.getClass().getComponentType();

System.out.println(cls);

arrobj = Array.get(arrobj, 5);

Array.setInt(arrobj, 10, 37);


int arrcast[][][] = (int[][][]) arr;

System.out.println(arrcast[3][5][10]);

}

}

 


Comments