본문 바로가기

JAVA

[java] 리플렉션 기법


http://blog.naver.com/oskmkr/60034858718

자바빈에 있어서 리플렉션의 구체적인 쓰임중 하나는 빌더 툴을 통해서 비쥬얼적으로 소프트웨어 컴퍼넌트를 조작할수 있따는것이다. 툴들은 리플렉션을 통해서 자바컴퍼넌트(클래스)의 프로퍼티를 알아내고, 동적으로 로딩한다.

A Simple Example

To see how reflection works, consider this simple example:

   import java.lang.reflect.*;
 
   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);
         }
      }
   }


For an invocation of:

 java DumpMethods java.util.Stack 

the output is:

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 는 하나의 메소드를 표현한다.

Setting Up to Use Reflection

Method와 같은 리플렉션 클래스는 in java.lang.reflect 팩키지에서 찾아볼수 있다. 이 클래스를 사용하기위해서는 다음과 같은 과정이 필요하다. 첫번째 과정으로는 조작하고자 하는 클래스의 java.lang.Class 형의 오브젝트를 취득한다. java.lang.Class 은 자바프로그램이 실행될때, 클래스와 인터페이스를 대표한다.

One way of obtaining a Class object is to say:

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

to get the Class object for String. Another approach is to use:

 Class c = int.class; 

or

 Class c = Integer.TYPE; to obtain Class information on fundamental types. 
The latter approach accesses the predefined TYPE field of the wrapper (such as Integer) for the fundamental type. 

두번째 과정은 클래스에 선언된 메소드 리스트를 얻기위해서 getDeclaredMethods()같은 메소드를 호출하는것이다.

세번째 과정은 리플렉션 API를 이용하여, 클래스를 조작한다.

For example, the sequence:

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

위의 프로그램은 String클래스에 선언되어있는 메소드들을 출력하는것이다.


Simulating the instanceof Operator

클래스에 관한 정보를 취득했다면, 다음 단계로 자주 사용하는것이, 클래스에 대한 instanceof 동작과 같은 동작을 하는Class.isInstance 메소드를 통한 조작이다.

   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에 관한 클래스 오브젝트가, A의 인스턴스인지 IInteger(37) 의 인스턴스인지 알아보는 샘플소스이다.

Finding Out About Methods of a Class

리플렉션을 사용하는데있어서 가장 가치있고 기본적인 사용은, 어떤 메소드가 클래스에 정의되어 있는지 알아내는것이다.

   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);
         }
      }
   }


이프로그램은 우선 method1에 대한 클래스 정의를 취득하고, getDeclaredMethods ()을 호출하여, 클래스에 정의된 각각의 메소들에 관한 Method 오브젝트의리스트를 얻는다. 메소드들은 public, protected, package, and private 를 포함한다. getDeclaredMethods()메소드 대신 getMethods()를 사용하게 되면, 상속된 메소드의 정보까지 얻을수있다.

Method 오브젝트리스트를 얻게 되면, 인수타입이나, 예외의 타입, 반환값등의 출력은 간단할것이다. The output of the program is:

  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
   -----

Obtaining Information About 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);
          }
      }
   }

이 예제에서는 반환값에 대한 정보는 없다. 그도 그럴것이 생성자에는 반환값이 없으니...

When this program is run, the output is:

   name = constructor1
   decl class = class constructor1
   -----
   name = constructor1
   decl class = class constructor1
   param #0 int
   param #1 double
   -----

Finding Out About Class Fields

클래스에 정의된 필드에(전역변수) 관한 정보를 얻는것도 물론 가능하다.

To do this, the following code can be used:

   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.toString을 사용하여, 문자열로써  "official" 선언부를 표현한다.

The output of the program is:

  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 =
   ----- 

메소드를 취득할때처럼,getDeclaredFields를 이용하여, 클래스에 선언된 멤버변수 취득도 가능하지만. getFields를 이용하여 부모클래스의 멤버변수까지 취득할수 있다.



Invoking Methods by Name

지금까지는 클래스의 정보를 얻고 표현하는 여러가지 예를 살펴봤다. 이제부터는 리플렉션을 이용하여 메소드를 실행하는 방법에 대해 알아보자. .

To see how this works, consider the following example:

   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);
         }
      }
   }

프로그램이 추가메소드를 실행하고싶지만, 실행하기전까지는 어떤메소드인지 알수 없다고 가정해보자. 메소드 이름마저 실행중에 특정적으로 변할수 있다고 하자. getMethod는 두개의 인티저 인수를 가지고 있고 적당한 메소드이름을 가지고 메소드정보를 알아내곤한다. 이메소드가 Method 오브젝트를 획득하면, 메소드를 실햏아기위해서 인수의 리스트가 생성되어야 하며, 37과 47이 인티저 오브젝트로써 래퍼되어야 한다. 리턴값인 87 역시 인티저 클래스 형태가 되어야 한다.

Creating New Objects

생성자를 실행한다는것은 새로운 오브젝트를 생성한다(메모리 할당과 오브젝트 생성을 포함하는 정확한 생성)는 측면에서 생성자에 대한 메소드 실행은 의미가 없다.

   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);
         }
      }
   }


특정한 인수타입을 조정하고 실행시키는 생성자를 찾아내는것은 오브젝트의 새로운 인스턴스를 생성하는것이다. 이러한 접근은 생성자가 컴파일타임보다 실행타임에서 실행된다는 의미에서 동적인 가치를 지니고 있다.

무슨소리인지.ㅡㅡ;

Changing Values of Fields

리플렉션의 다른 사용법은 오브젝트의 멤버변수의 값을 바꾸는 일이다.멤버변수가 실행타임에서 이름으로 찾아내어지고(?) 값이 변했을때, 이 값은 리플렉션의 동적인 성질에 유래한다.(점점 알수없는) This is illustrated by the following example:

   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);
         }
      }
   }

In this example, the d field has its value set to 12.34.

Using Arrays

마지막으로 등장하는 리플렉션의 최종 쓰임은 배열을 생성하고, 조작하는것이다.자바에 있어서 배열은 클래스의 특별한 타입이고, 배열참조는 오브젝트 참조를 할당할수가 있다.

To see how arrays work, consider the following example:

   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);
         }
      }
   }

이 예제는 문자열의 10개의 배열을 만들고, 5번째 위치에 문자열값을 세팅하는것이다.

A more complex manipulation of arrays is illustrated by the following code:

   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]);
      }
   }

이예제는 5 x 10 x 15 형태의 배열을ㅇ 생성하고,  [3][5][10] 위치에 37을 세팅하는것이다. 여기서 주목해야 할점은, 다중배열을 사실 배열의 배열이라는 점이다.따라서, 첫배열 Array.get 후에 arrobj 의 결과는 10 x 15 배열의 결과이다. 이것은 다시한번 길이 15의 배열로 분해하여 10번째 위치에 Array.setInt.을 사용하여 숫자를 세팅하는것이다.

또한 배열타입이 동적으로 생성되며, 컴파일타임에는 알수 없다는것에주목해야 할것이다. 

[출처] Java reflection|작성자 핫핫핫