정보 공학 에서 리플렉션 ( reflection )는 프로그램 의 실행 과정에서 프로그램 자체의 구조를 읽고 수정하거나하는 기술이다. 일반적 반사라고하면 동적 (런타임) 리플렉션을 가리 키지 만, 정적 (컴파일시) 리플렉션을 지원하는 프로그래밍 언어 도있다. 리플렉션은 Smalltalk , Java , . NET Framework 와 같은 가상 기계 나 인터프리터 에서 실행되는 것을 상정 한 언어에서 지원되는 경우가 많으며, C 언어 같은 기계어 로 출력되는 것을 상정 한 언어 에서 지원되는 것은 적다.

일반적으로 반사와 객체 가 자신의 구조와 계산의 의미를 얻을 수 있도록하는 것이다. 리플렉션의 프로그래밍 패러다임 을 리후 레크 티브 프로그래밍 ( reflective programming )이라고한다.

일반적으로 프로그램의 소스 코드 가 컴파일 되면 프로그램 구조 등의 정보는 저수준 코드 ( 어셈블리 언어 )로 변환되는 과정에서 손실되어 버린다. 리플렉션을 지원하는 경우, 이러한 정보는 생성 된 코드에 메타 데이터 로 저장된다.

LISP 와 Forth 같은 런타임 및 컴파일시를 구분하지 않는 언어에서는 코드의 해석과 반성 사이에 차이는 없다.

목차

  [ 숨기기 ] 

예 편집 ]

Java 편집 ]

다음 코드는 java.lang.reflect 패키지를 사용한 Java 6 이후의 예이다.

/ / 리플렉션없이 
Foo foo =  New Foo ( ) ; 
foo. hello ( ) ;
/ / 리플렉션 
Class CL =  Class . forName ( "Foo" ) ; 
Method Method = cl. getMethod ( "hello" ) ; 
method. invoke ( cl. newInstance ( ) ) ;

어느 코드에서도 Foo 클래스 의 인스턴스 를 만들고 인스턴스의 hello () 메소드 를 부르고있다. 전자의 예는 클래스와 메소드 이름이 하드 코딩되어 있기 때문에 실행시 다른 클래스로 변경하는 것은 불가능하다. 리플렉션을 이용한 후자의 예는 그들을 실행할 때 쉽게 변경할 수있다. 그러나 반면에, 후자는 어지럽고 또한 컴파일 시 검사의 혜택도 얻을 수 없다. 즉, 만약 Foo 클래스가 존재하지 않았다고하면 전자의 코드는 컴파일시에 오류가되지만, 후자의 코드가 실행될 때까지 오류가 발생하지 않는다.

Perl 편집 ]

다음 코드는 같은 예를 Perl 로 쓴 것이다.

# 리플렉션없이 
Foo -> new -> hello ( ) ;
# 리플렉션 
my  $ class  =  "Foo" ; 
my  $ method  =  $ class -> can ( "hello" ) ; 
$ class -> new -> $ method ( ) ;

Objective-C 편집 ]

다음 코드는 같은 예를 Objective-C 로 쓴 것이다.

/ / 리플렉션없이 
[ [ [ Foo alloc ] init ] hello ] ;
/ / 리플렉션 
ID AClass = objc_getClass ( "Foo" ) ;
 SEL aSelector = NSSelectorFromString ( @ "hello" ) ;
objc_msgSend ( [ [ aClass alloc ] init ] , aSelector, nil ) ;

ActionScript 편집 ]

다음은 같은 예제를 ActionScript 로 쓴 것이다.

/ / 리플렉션없이 
var foo : Foo = New Foo ( ) ;
foo. hello ( ) ;
/ / 리플렉션 
var ClassReference : Class = flash. utils . getDefinitionByName ( "Foo" ) as Class ;
 var instance : Object = New ClassReference ( ) ;
instance. hello ( ) ;

JavaScript 편집 ]

다음은 같은 예제를 JavaScript 로 쓴 것이다.

/ / 리플렉션없이 
var foo =  New Foo ( ) ; 
foo. hello ( ) ;
/ / 리플렉션 
var foo =  this [ 'Foo' ] ; 
var methodName =  'hello' ; 
( New foo ( ) ) [ methodName ] ( ) ;

C # 편집 ]

다음 예제는 C # 의 예에서보다 진전 된 반성의 용법을 보여주고있다. 프로그램은 명령 줄 에서 어셈블리 이름을 입력으로 취한다. 어셈블리는 클래스 라이브러리 와 같은 것이다. 어셈블리가로드되면 어셈블리에 정의 된 방법을 찾기 위해 리플렉션을 사용한다. 발견 된 각 메소드에 대해 최근 변경이 있었는지 여부를 리플렉션을 사용하여 조사하고있다. 만약 변경이 있어야하며 인수를 취하지 않는 방법이면 메서드 이름과 반환 형식을 표시한다.

최근 변경되었는지 여부를 확인하기 위해 개발자는 사용자 정의 속성 을 사용할 필요가있다.

using  System ; 
using  System.Collections.Generic ; 
using  System.Text ; 
using  System.Reflection ; 
using  Recent ;
 
namespace Reflect
 { 
    class Program
     { 
        private Assembly A ; 
        Program ( String assemblyName ) 
        { 
            a = Assembly . Load ( New AssemblyName ( assemblyName ) ) ; 
            / / 지정된 어셈블리에 SupportsRecentlyModified 특성이 적용되어 있는지 확인. 
            Attribute c = Attribute . GetCustomAttribute ( a, typeof ( SupportsRecentlyModifiedAttribute ) ) ; 
            if  ( c ==  null ) 
            { 
                 / / "SupportsRecentlyModified"속성 가져 오지 못했습니다. 
                 / / 즉, 어셈블리는 "RecentlyModified"속성을 지원하지 않는다. 
                 throw  New Exception ( "어셈블리"  + assemblyName +  "필요한 속성을 지원하지 않습니다." ) ; 
            } 
            Console . WriteLine ( "읽기 완료 :"  + a . FullName ) ; 
        }
 
        public  void FindNewMethodsWithNoArgs ( ) 
        { 
            / / 어셈블리에 정의 된 형식을 모두 취득하는 
            Type [ ] t = a . GetTypes ( ) ; 
            foreach  ( Type type in T ) 
            { 
                / /이 형식이 클래스이어야 건너 뜀. 
                if  ( ! type . IsClass ) 
                    continue ; 
                Console . WriteLine ( "클래스 이름 :"  + type . FullName ) ; 
                MethodInfo [ ] methods = type . GetMethods ( ) ; 
                foreach  ( MethodInfo Method in Methods ) 
                { 
                    object [ ] ab = method . GetCustomAttributes ( typeof ( RecentlyModifiedAttribute ) , true ) ; 
                    / / 속성이 하나를 얻을 수없는 경우에,이 메소드는 이전하는 것이다. 
                    if  ( ab . Length  ! =  0 ) 
                    { 
                        / / 그렇지 않으면 하나의 속성 만 검색된다. 
                        / / 이는 "RecentlyModified"속성은 다른 속성과 함께 수 없기 때문이다.
 
                        Console . Write ( " \ t 업데이트 된 메소드 : "  + method . Name ) ; 
                        if  ( method . GetParameters ( ) . Length  >  0 ) 
                            break ;
 
                        / / 개발자가 지정한 코멘트를 얻기 위해, 
                        / / "RecentlyModifiedAttribute"속성의 인스턴스를 사용한다. 
                        Console . WriteLine ( " \ t "  +  ( ab [ 0 ]  as RecentlyModifiedAttribute ) . comment ) ; 
                        Console . WriteLine ( " \ t \ t 반환 값 : "  + method . ReturnType . Name ) ; 
                    } 
                } 
            } 
        }
 
        static  void Main ( string [ ] args ) 
        { 
            try 
            { 
                Program reflector =  New Program ( "UseAttributes" ) ; 
                reflector . FindNewMethodsWithNoArgs ( ) ; 
            } 
            catch  ( Exception e ) 
            { 
                Console . Error . WriteLine ( e . Message ) ; 
            } 
        } 
    } 
}

에서 사용하는 사용자 지정 특성의 구현을 다음.

using  System ; 
using  System.Collections.Generic ; 
using  System.Text ;
 
namespace Recent
 { 
    / /이 특성은 메서드에만 적용 할 수 없으며 다른 특성과 함께 할 수 없다. 
    [ AttributeUsage ( AttributeTargets . Method , AllowMultiple = false , Inherited = true ) ] 
    public  class RecentlyModifiedAttribute : Attribute
     { 
        / /이 특성은 메서드에 적용된다. 
        / / 인수없이 (RecentlyModified), 
        / / 또는 코멘트와 함께 (RecentlyModified (comment = "<someComment>")) 적용 할 수있다.
 
        private  String Comment =  "이 방법은 최근에 변경되었습니다." ;
 
        public RecentlyModifiedAttribute ( ) 
        { 
            / / 속성의 인스턴스를위한 빈 생성자. 
            / / 필수 인수는 없기 때문에 생성자는 하늘. 
        }
 
        / / 선택적 인수 "comment"의 정의. 속성이 사용되는 때 명명 된 인수로 지정된다. 
        public  String comment
         { 
            get 
            { 
                return Comment ; 
            } 
            set 
            { 
                Comment = comment ; 
            } 
        } 
    }
 
    [ AttributeUsage ( AttributeTargets . Assembly , AllowMultiple = false ) ] 
    public  class SupportsRecentlyModifiedAttribute : Attribute
     { 
        / /이 특성은 인수없이 어셈블리에 적용된다. 
        / / as in [SupportsRecentlyModified] 
        public SupportsRecentlyModifiedAttribute ( ) 
        { 
            / / 필수 인수는 없기 때문에 생성자는 하늘. 
        } 
    } 
}

또한 사용자 지정 특성을 사용하여 클래스 정의 예를 들면.

using  System ; 
using  System.Collections.Generic ; 
using  System.Text ; 
using  Recent ;
 
/ / 어셈블리가 "RecentlyModified"속성을 지원하는 것을 보여주기 위해 
/ / "SupportsRecentlyModified"속성을 적용한다. 
[ assembly : SupportsRecentlyModified ]
 
namespace Reflect
 { 
    class UseAttributes
     { 
        private  Object info ;
 
        public UseAttributes ( ) 
        { 
            info =  ( object )  "Hello World" ; 
        }
 
        public  void OldMethodWithNoArgs ( ) 
        { 
            Console . WriteLine ( "이것은 인수를 취하지 않는 오래된 방법이다." ) ; 
        }
 
        / / 메소드가 최근 변경되었다는 것을 나타내는 "RecentlyModified"속성을 적용한다. 
        [ RecentlyModified ] 
        public  void NewMethodWithNoArgs ( ) 
        { 
            Console . WriteLine ( "이것은 인수를 취하지 않는 새로운 방법이다." ) ; 
        }
 
        public  void OldMethodWithOneArg ( object something ) 
        { 
            info = something ; 
            Console . WriteLine ( "이것은 인수를 1 개 취하는 오래된 방법이다." ) ; 
        }
 
        [ RecentlyModified ] 
        public  void NewMethodWithOneArg ( object something ) 
        { 
            info = something ; 
            Console . WriteLine ( "이것은 인수를 1 개 취하는 새로운 방법이다." ) ; 
        }  
    } 
}


===================================================================================


컴퓨터 과학에서, 반영(Reflection)은 컴퓨터 프로그램에서 런타임 시점에 사용되는 자신의 구조와 행위를 관리(type introspection)하고 수정할 수 있는 프로세스를 의미한다. “type introspection”은 객체 지향 프로그램언어에서 런타임에 객체의 형(type)을 결정할 수 있는 능력을 의미한다.

많은 컴퓨터 아키텍처에서, 프로그램 명령은 데이터와 같이 메모리에 적재되므로, 명령과 데이터 사이의 구분은 단지 컴퓨터와 프로그램 언어에 의하여 어떻게 정보가 처리되느냐의 문제이다. 일반적으로, 명령은 실행되고, 데이터는 (명령의 자료로서) 처리된다. 그렇지만, 어떤 언어에서는, 프로그램은 명령 역시 데이터와 같은 방식으로 처리 할 수 있기 때문에, Reflective 수정이 가능하게 된다. 가장 일반적으로 반영은 스몰토크, 스크립트 언어와 같이 높은 수준의 가상 머신 프로그램 언어에서 주로 많이 사용되고, 자바C 언어와 같이 선언적이거나 정적 형식의 프로그램 언어에서는 드물게 사용된다.

[편집]이용

반영은 런타임에 프로그램의 수행을 수정하고, 관찰하기 위하여 사용할 수 있다. 반영 지향적인 프로그램 구성 요소는 내부 코드의 실행을 감시할 수 있고, 구성 요소 자신의 궁극적인 목표에 맞도록 내부를 수정 할 수 있다. 이는 전형적으로 런타임에 프로그램 코드를 동적으로 할당하여 이루어진다.

반영은 프로그램을 서로 다른 상황에서 동적으로 사용할 수 있게 한다. 예를 들어, 비슷한 동작을 수행하는 서로 다른 클래스 ‘X’와 클래스 ‘Y’를 교체하여 사용하는 응용 프로그램을 고려해보자. 반영 지향적이 아닌 프로그램 개발에서는, 응용 프로그램은 클래스 ‘X’와 클래스 ‘Y’의 함수를 호출하기 위하여, 함수(이름)을 직접 코드로 작성해야 한다. 반면, 반영 지향적인 프로그램 패러다임에서, 응용 프로그램은 함수(이름)을 직접 코드로 작성하지 않고, 클래스 ‘X’와 클래스 ‘Y’의 함수를 호출하기 위하여, 반영을 이용하여 설계되고 작성될 수 있다. 반영 지향적인 프로그램은 보다 보편적인(Generic) 코드의 수행의 기능을 사용하기 위하여, 거의 언제나 추가적인 정보, 프레임워크, 연관 사상, 객체 관계 등을 필요로 한다. 반영 지향적인 프로그램 기술을 확장하면, 하드 코딩을 방지할 수 있다.

[편집]기능

반영이 지원되는 언어는, 낮은 수준의 언어의 처리와는 다른, 런타임에 사용할 수 있는 매우 모호한 몇 가지 특성을 제공한다. 이런 특성들은 다음과 같다.

  • 런타임에 First-Class 객체로서 소스 코드의 생성자(코드 블록, 메소드, 프로토콜, ……)를 인식하고 수정할 수 있다.
  • 참조 및, 호출되는 클래스 또는 함수와 일치하는 문자열을 클래스 또는 함수의 기호 이름으로 변환할 수 있다.
  • 런타임에 소스 코드 구문으로 구성된 문자열을 평가할 수 있다.
  • 프로그래밍 구성에 새로운 의미 또는 의도를 부여하여, 프로그램 언어의 바이트 코드에 대한 새로운 번역기를 생성할 수 있다.

컴파일 언어들은 소스 코드에 대한 정보를 제공하기 위하여, 자신들의 런타임 시스템에 의존한다. 예를 들어, 컴파일 된 오브젝티브 C 실행 모듈은, 프로그램 컴파일 과정을 통하여 소스 코드와 메소드가 상응하는 테이블을 제공하여, 실행 모듈의 블록 안에 모든 메소드의 이름을 기록한다. 커먼 리스프(Common Lisp) 언어와 같이, 함수의 런타임 생성을 지원하는 컴파일 언어는 런타임 환경은 컴파일 또는 인터프리터를 포함해야 한다.

반영은 자동화된 소스 코드 변환 체계를 정의한 프로그램 변환 시스템을 사용하여, 반영 기법이 내장되지 않은 언어에서 구현될 수 있다.

[편집]예제

다음은 여러 언어에서 구현된 반영의 사용 예를 표시한다.

C#:

Int32 i = 1234;
Console.WriteLine(String.Format("{0}", i.ToString("F")));
 
Type t = typeof(Int32);
// Reflection 1
Object res1
    = t.InvokeMember("ToString", 
        System.Reflection.BindingFlags.InvokeMethod, 
        null, 
        i, 
        new Object[]{"C", CultureInfo.CreateSpecificCulture("ko-KR")});
Console.WriteLine(String.Format("{0}", res1));
 
// Reflection 2
System.Reflection.MethodInfo method 
    = t.GetMethod("ToString", new Type[] { typeof(String) });
Object res2 = method.Invoke(i, new Object[] {"X"});
Console.WriteLine(String.Format("{0}", res2));

Objective-C

NSString* str = [[NSString alloc] initWithString:@"1234"];
NSLog(@"%d", (int)[str length]);
 
// Reflection
Class class = NSClassFromString(@"NSString");
id idOfClass = [[class alloc] initWithString:@"Reflection"];
SEL selector = NSSelectorFromString(@"length");
NSLog(@"%d", (int)[idOfClass performSelector:selector]);
 
[str release];
[idOfClass release];

Python:

>>> class MyClass:
...     def Hello(self, sText):
...             return 'Hello ' + sText
...
>>> MyClass().Hello('Peter')
'Hello Peter'
>>> 
>>> getattr(globals()['MyClass'](), 'Hello')('Peter')
'Hello Peter'
>>>


===================================================================================


RTTI(Run-Time Type Information)을 이용하면 실행중에 타입 정보를 알아내고 사용할 수 있다. 이는 매우 까다롭고 복잡한 문제점들을 쉽고 빠르게 해결 할 수 있고, 굉장히 강력한 프로그램을 작성 할 수 있게 도와준다. 하지만 이는 객체 지향적인 프로그래밍 방법을 위배하고, 코드의 가독성을 떨어트린다는 의견도 있다. 그렇지만 이런 강력한 기능의 유혹을 벗어나긴 힘들다. 그리고 실제 여러 Framework에서 RTTI를 잘 활용 하고 있다. (특히나 Spring Framework)



자바에서는 모든 .class 파일 하나당 java.lang.Class 객체 하나씩 생성된다. Class는 모든 .class들의 정보를 가지고 있으며 .class파일에 같이 저장된다. 모든 .class들은 이 클래스를 최초로 사용하는 시점에서 동적으로 ClassLoader을 통해 JVM에 로드된다. 


최초로 사용하는 시점이라면 해당 .class에서 static을 최초로 사용할때를 말한다. (생성자도 static 메소드이다.  그렇기 때문에 new를 하게 되면 로드된다고 보면 된다. 이를 동적 로딩이라 한다.) 


이렇게 .class의 정보와 Class객체는 JVM에 Run Time Data Area의 Method Area에 저장된다.


그리고 이러한 정보들을 java.lang.reflect 에서 접근할 수 있게 도와 준다.  이 패키지에서는 Field, Method, Constructor같은 class들이 있고,  Constructor를 통해 새로운 객체를 생성하고, getter method와 setter method를 통해 Field의 값을 읽거나 수정할 수 있다. 또 invoke method를 통해 method를 호출한다. 그 외에도 parameter, return type, modifier 등 class의 관련된 모든 정보를 알아올 수 있고 조작할 수 있다. 심지어 private으로 Encapsulation된 immutable한 것들 까지도 setAccessible을 통해 접근, 조작 할 수 있다. 


출처 - http://blog.naver.com/PostView.nhn?blogId=cracker542&logNo=40160053694


===================================================================================


Java 란 언어는 참 보면 볼 수록 재미난 언어다. 기반 라이브러리가 참 충실하다보니 이것 저것 생각나는걸 해볼 수 있다는게 참 맘에 든다. 하기사... 다른 언어들도 그렇지만 암튼 그렇다 치자. 
  오늘은 얼마전에 개발을 시작한 모 프로그램에 들어가는 기능에 삘받아 간단한 리플렉션 예제를 실어 볼까한다. 리플렉션을 쓰다보면 참 노가다도 이런 상노가다가 없다. 물론 인터페이스를 사용할 수 없을 때 얘기다. 이게 뭔고 하니 아래의 코드로 비교를 해보자. 일단은 인터페이스를 쓰지않은 리플렉션 코드다.

우선 리플렉션을 통해 실행할 클래스를 소개한다.


public class HandlerA{
  private String msg;
  
  public void setMessage(String msg){
    this.msg = msg;
  }
  
  public void handle(){
    System.out.println(msg);
  }
}

아~ 주 심플하다. 메시지를 설정하고 표준출력으로 뱉는 단순한 기능을 하는 클래스다. 

그러면 저 녀석을 로드해서 실행하는 클래스를 보자.


import java.lang.reflect.Method;

public class DynaLoad{
  public static void main(String[] args) throws Exception{
    if(args.length < 1){
      System.out.println("DynaLoad classname");
    }else{
      loadAndExecute(args[0]);
    }
  }
  
  public static void loadAndExecute(String className) throws Exception{
    Class c = null;
    c = Class.forName(className);
    Object target = c.newInstance();
    Method m = c.getDeclaredMethod("setMessage", String.class);
    m.invoke(target, "Hello");
    m = c.getDeclaredMethod("handle");
    m.invoke(target);
  }
}



뭐시가 아주 길다. java.lang.reflect.Method 라는 놈도 임포트 되어 있다. 요녀석이 실행되는 과정은 다음과 같다. 

ex) java DynaLoad HandlerA 라고 실행을 하면
녀석이 인자를 하나 받아서 해당 인자를 클래스 명으로 간주하고 로드를 한다. 그리고 나서 해당 Class 를 통해 객체를 하나 생성한다. 근데 그 객체가 뭔 타입인지 미리 알 수가 없으니 Object 로 일단 놓고 아까 로드한 클래스를 통해 우리가 호출할 객체의 메서드를 얻는다. Object 에는 setMessage 는 물론이거니와 handle 이라는 메서드도 없으니 클래스를 통해 호출할때 쓸 메서드를 얻어야 한다. 우선 setMessage 를 호출해야 하니까. setMessage 라는 메서드명과 setMessage 의 인자의 타입을 통해 해당 메서드를 얻는다. 그리고 나서 얻어낸 메서드에 생성한 객체와 인자를 넣고 invoke 를 호출해 주면 해당 객체에대해 setMessage 를 호출한 것과 동일한 효과를 볼 수있다. 뭐 이런식으로 해서 handle 도 호출하면 Hello 라고 출력되는걸 볼 수 있다. 아~ 뭔가 길고, 지루하지 않은가 게다가 메서드를 호출할 때마다 이짓을 해야하니 정말 죽을 맛이다. 그러면 저기다가 인터페이스를 이용하면 어떨까? 

일단 우리가 로드할 클래스는 어떠한 인터페이스를 구현한 클래스라고 가정하자. 이래야 뭔가 문제가 쉬워진다. 그 인터페이스는 아래와 같다.


public interface HandlerInterface {
  public void handle();
  public void setMessage(String msg);
}

간단하다. 호출할 메서드를 다 넣어놨다. 그러면 이녀석을 구현한 클래스는 어떨까? 아래를 보자

public class HandlerB implements HandlerInterface{
  private String msg;
  public void setMessage(String msg){
    this.msg = msg;
  }
  public void handle(){
    System.out.println(msg);
  }
}

implements 만 붙었을 뿐 HandlerA 와 똑같다! 그러면 위에서 썻던 DynaLoad 클래스는 어떻게 바뀔까?

import java.lang.reflect.Method;
public class DynaLoad{
  public static void main(String[] args) throws Exception{
    if(args.length < 1){
      System.out.println("DynaLoad classname");
    }else{
      loadAndExecute(args[0]);
    }
  }
  
  public static void loadAndExecute(String className) throws Exception{
    Class c = null;
    c = Class.forName(className);
    HandlerInterface target = (HandlerInterface)c.newInstance();
    target.setMessage("Hello");
    target.handle();
  }
}



main 메서드는 그대로고 loadAndExecute 라는 메서드는 좀 짧아졌다. 지금은 로드해서 실행할 클래스가 간단해서 그렇지 메서드가 수십개씩 되는 클래스라고 생각해 보라. 코드가 얼마나 줄겠는가?!! 위의 코드는 딱히 설명할 것도 없다. 인터페이스를 이용함 으로써 로드된 클래스의 모든 기능을 맘편히 사용할 수 있다.


출처 - http://lemonfish.egloos.com/4087285


===================================================================================


(1) 리플렉션(Reflection)

 

자바에서 리플렉션은 유연성을 제공하기위해 필수적인 기법이다물론 리플렉션이 없더라도 훌륭한 코드를
작성할 수 있다하지만 리플렉션을 사용하면 좀더 유연한 프로그램을 작성할 수 있다자바에서 리플렉션을 
이해하기 위해서자바 클래스 파일은 바이트 코드로 컴파일 되며 실행시간에 이 바이트 코드가해석되어
 
실행된다는 것을 아는 것이 첫 출발점이 된다이 바이트 코드에는 클래스에 대한 모든 정보를포함하고 있다

클래스 파일이 있는 위치와 이 클래스 파일의 이름을 알수 있다면 언제든지 바이트 코드를 뒤져서 클래스에 대한
 
정보를 얻어낼 수 있다이제부터 리플렉션을 통해 어떻게,어떤 정보를 얻을 수 있는지 알아보자.




1. 
리플렉션(Reflection)

리플렉션은 구체적인 클래스 타입을알지 못해도컴파일된 바이트 코드를 통해 역으로 클래스에 정보를 알아내어
클래스를 사용할 수 있는 기법을 의미한다마치 거울에 비친 모습과 유사하여 리플렉션이란이름을 붙힌 것 같다

2. 
리플렉션을사용하는 이유

리플렉션은 조합(Composition)과 함께 사용되어 다형성을 구현하는 강력한 도구이다조합을사용하여 교체할 수 있는 
위임 클래스를 리플렉션을 통해 동적/정적으로 생성하고 교체하는 방식으로 사용된다프레임워크에서 유연성이 있는 동작을

위해 자주 사용되는 방식이기도 하다.



3. 
리플렉션을 통해 얻을 수 있는 정보

리플렉션을 통해 얻을 수 있는 정보에대해서 알아보자.

 

ClassName

 

Class Modifiers (public, private, synchronized )

 

Package Info

 

Superclass

 

Implemented Interfaces

 

Constructors

 

Methods

 

Fields

 

Annotations

 



이외에도 더 많은 클래스 정보를 얻을 수 있다.java.lang.Class 클래스에 대한 JavaDoc 문서를 참고하자

3.1 Class Object

클래스 정보를 얻기 위해 가장 먼저해야할 일은 정보를 담고 있는 java.lang.Class 객체를 획득하는 것이다.
프리미티 타입과 배열 타입을 포함하여 자바의 모든 타입들은 연관된 Class 객체를 가지고있으며컴파일 타임에
 
클래스의 이름을 알수 있다면다음과 같이 Class 객체를얻을 수 있다.

 ClassmyObjectClass = MyObject.class

 

컴파일 타임에 이름을 알수 없다면런타임에 문자열로 된 이름으로부터 클래스 객체를 아래와 같이 얻을 수 있다.

String className =  ... // 클래스 풀네임

 

Class myObjectClass = Class.forName(className);

 

이때 문자열로 된 클래스 이름은 패키지경로까지 포함한 풀네임이여야 하며해당 패키지에 클래스가 존재하지 않으면
Class.forName 
메소드는 ClassNotFoundException 예외를 던지게된다.



3.1 Class Name

Class객체로부터 2가지버전의 클래스 이름을 얻을 수 있다. getName() 메소드를 사용하면 패키지까지 포함한 풀네임을얻을 수
있고, getSimpleName() 을 사용하여 패키지가 포함되지 않은 클래스 이름을 얻을수 있다.

// 클래스 풀네임

 

Class aClass = ... // 이전에 얻은 클래스객체

 

String className = aClass.getName();

 

 

// 클래스 심플 네임

 

Class  aClass =... // 이전에 얻은 클래스 객체

 

String simpleClassName = aClass.getSimpleName();

 



3.2 Modifier

Class객체로부터 변경자에 접근할 수 있다클래스 변경자는 public, private, static 과 같은키워드를 의미한다.

클래스에 대한 플래그 비트가 설정된 int 값을 얻을 수 있으며 java.lang.reflect.Modifier 클래스에있는 메소드를 
통해 해당 플래그가 켜져있는지 확인할 수 있다.

// 변경자 얻기

 

Class  aClass =... // 이전에 얻은 클래스 객체

 

int modifiers = aClass.getModifiers();

 

 

// 변경자 플래그 확인 메소드들

 

Modifier.isAbstract(int modifiers)

 

Modifier.isFinal(int modifiers)

 

Modifier.isInterface(int modifiers)

 

Modifier.isNative(int modifiers)

 

Modifier.isPrivate(int modifiers)

 

Modifier.isProtected(int modifiers)

 

Modifier.isPublic(int modifiers)

 

Modifier.isStatic(int modifiers)

 

Modifier.isStrict(int modifiers)

 

Modifier.isSynchronized(int modifiers)

 

Modifier.isTransient(int modifiers)

 

Modifier.isVolatile(int modifiers)

 



3.3 Package Info
다음과 같이 Class 객체로부터 패키지에 대한 정보를얻는다.

// 패키지 정보 얻기

 

Class  aClass =... // 이전에 얻은 클래스 객체

 

Package package = aClass.getPackage();

 

 

Package객체로부터 패지지 이름과 같은 정보에 접근할 수 있다또한 패키지가 위치한 classpath에 있는jar 파일의 
Manifest 
파일에서도 이 패키지에 대한 특정한 정보를 얻을 수 있다

(
예를 들면 Manifest 파일에 지정된 패지키 버전 번호 같은...)



3.4 Superclass
아래와 같이 수퍼클래스의 class 객체를 얻을 수 있다.

// 수퍼 클래스의 class 객체 얻기

 

Class superclass = aClass.getSuperclass();

 



3.5 Implemented Interfaces
클래스 객체에 의해 구현된 인터페이스의 목록을 얻어보자.

// 구현한 인터페이스 목록 얻기

 

Class  aClass =... // 이전에 얻은 클래스 객체

 

Class[] interfaces = aClass.getInterfaces();

 

 

수퍼클래스가 구현한 인터페이스지만자식클래스가 특별히 해당 인터페이스를 구현한다고 명시하지 않으면
해당 인터페이스는 목록에 포함되지 않는 것에 주의하자구현하는 완전한 인터페이스의 목록을얻기 위해서는
 
자신의 수퍼클래스의 구현 인터페이스 목록을 재귀적으로 확인해야 한다.



3.6 Constructors
다음과 같이 클래스의 생성자 목록에 접근한다.

// 클래스 생성자 목록 얻기

 

Constructor[] constructors =aClass.getConstructors();

 




3.7 Methods 
다음과 같이 클래스의 메소드들에 접근한다.

// 메소드 목록 얻기

 

Method[] methods = aClass.getMethods();

 




3.8 Fields
다음과 같이 클래스의 멤버 변수들에 접근한다.

// 필드 목록 얻기

 

Field[] fields = aClass.getFields();

 


3.9 Annotations
다음과 같이 클래스의 어노테이션에 접근한다.

// 어노테이션 목록 얻기

 

Annotation[] annotations = aClass.getAnnotations();

 


※ 
리플렉션을 사용하여 Annotation을 처리하는 것은아래 포스트를 참고!!
Java-어노테이션(Annotation)
Java-어노테이션 사용하기


4. 
리플렉션 사용 예

게시물 정보를 갖는 Board 클래스에 대한 정보를 리플렉션을 사용하여 출력하는 예를 보자
Board 
클래스에 대한 정의는 아래와 같다.

package com.tistory.hiddenviewer.reflection;

 

import java.awt.event.ActionEvent;

 

import java.awt.event.ActionListener;

 

import java.util.ArrayList;

 

public class Board implements ActionListener{

 

         publicfinal static String boardName = "MyBoard";

 

         publicArrayList boardList;

 

        

 

         publicint seq;

 

         protectedString title;

 

         privateString contents;

 

        

 

         publicBoard() {

 

                 this(10);

 

         }

 

        

 

         publicBoard(int count) {

 

                 this.boardList= new ArrayList(count);

 

         }

 

        

 

        

 

         publicint getSeq() {

 

                 returnseq;

 

         }

 

         publicvoid setSeq(int seq) {

 

                 this.seq= seq;

 

         }

 

         public StringgetTitle() {

 

                 returntitle;

 

         }

 

         publicvoid setTitle(String title) {

 

                 this.title= title;

 

         }

 

         publicString getContents() {

 

                 returncontents;

 

         }

 

         publicvoid setContents(String contents) {

 

                 this.contents= contents;

 

         }

 

         @Override

 

         publicvoid actionPerformed(ActionEvent e) {

 

                 //TODO Auto-generated method stub

 

                

 

         }

 

}

 



Board 
클래스에 대한 정보를 리플렉션을 사용하여 출력하였다.

package com.tistory.hiddenviewer.reflection.executor;

 

import java.io.BufferedReader;

 

import java.io.IOException;

 

import java.io.InputStreamReader;

 

import java.lang.annotation.Annotation;

 

import java.lang.reflect.Constructor;

 

import java.lang.reflect.Field;

 

import java.lang.reflect.Method;

 

import java.lang.reflect.Modifier;

 

public class BoardReflectionExecutor {

 

         publicstatic void main(String[] args) throws IOException, ClassNotFoundException {

 

                

 

                 BufferedReaderbr = new BufferedReader(new InputStreamReader(System.in));

 

                

 

                 System.out.print("생성할 클래스 이름을 입력하세요(패키지 포함): ");

 

                 StringclassName = br.readLine();

 

                

 

                 Classcls = Class.forName(className);

 

                

 

                 //클래스 이름얻기

 

                 StringclassFullName = cls.getName();

 

                 StringclassSimpleName = cls.getSimpleName();

 

                

 

                 System.out.println("classfull name: " + classFullName);

 

                 System.out.println("classsimple name: " + classSimpleName);

 

                

 

                 //변경자 얻기

 

                 intmodifiers = cls.getModifiers();

 

                

 

                 if(Modifier.isPublic(modifiers)) {

 

                          System.out.println("classis public class");

 

                 }

 

                 if(Modifier.isFinal(modifiers)) {

 

                          System.out.println("classis final class");

 

                 }

 

                 //패키지 얻기

 

                 Packagepkg = cls.getPackage();

 

                 System.out.println("packagename: " + pkg.getName());

 

                

 

                

 

                 //수퍼클래스 얻기

 

                 ClasssuperCls = cls.getSuperclass();

 

                 System.out.println("superclass name :" + superCls.getName());

 

                

 

                

 

                 //구현 인터페이스 목록 얻기

 

                 Class[]interfaces = cls.getInterfaces();

 

                 for(Class cs : interfaces) {

 

                          System.out.println("thisclass implements " + cs.getName() + " interface");

 

                 }

 

                

 

                 //생성자 목록 얻기

 

                 Constructor[]conturctors = cls.getConstructors();

 

                 for(Constructor constructor : conturctors) {

 

 

                          System.out.println("Constructor:" + constructor.getName());

 

                 }

 

                //메서드 목록 얻기

                 Method[]methods = cls.getMethods();

 

                 for(Method method : methods) {

                          System.out.println(method.getReturnType()+ " " + method.getName() + "(...)");

                 }

 

                 //프로퍼티 목록 얻기

 

                 Field[]fields = cls.getFields();

 

                 for(Field field : fields) {

 

                          System.out.println(field.getType()+ " " + field.getName());

 

                 }

 

                 //어노테이션 얻기

 

                 Annotation[]annotations = cls.getAnnotations();

 

                 for(Annotation annotation : annotations) {

 

                          System.out.println(annotation.toString());

 

                 }

 

         }

 

}

 



출력결과

 

///////////////////////////////////////////////////////////////////////////////////////////


(2) 리플렉션(Reflection) 사용하기

 

지난 포스팅에 이어 리플렉션을 사용하여객체를 생성하고, private 필드에 접근하는 방법 그리고 메서드를 호출하는 
방법에 대해 알아보자.



1. 리플렉션을 사용하여 객체생성

리플렉션을 사용하여 런타임에 클래스에생성자들을 검사하고생성자 객체를 통해 객체를 생성하는 과정을 알아보자
아래와 같이 3가지 단계를 거치게 되며 가장 먼저 클래스의 생성자 객체 java.lang.reflect.Consturctor 를 얻어야 한다.

 

1.1 Constructor 객체 획득하기
다음과 같이 Class 객체로부터 Constructor 클래스를 얻는다.

// 생성자 목록 얻기

 

Class aClass = ... // 이전에 얻은 클래스객체

 

Constructor[] constructors = aClass.getConstructors();

 



Constructor[] 
배열은 클래스에 선언된 모든public 생성자의 Constructor 인스턴스를 가집니다특정한 파라미터를 갖는 
특정한 생성자는 다음과 같이 얻을 수 있다.

// 특정 파라미터를 갖는 생성자 얻기

 

Class aClass = ... // 이전에 얻은 클래스객체

 

Constructor constructor =

 

aClass.getConstructor(new Class[]{String.class});

 



위 예는 하나의 String 타입 파라미터를 갖는 생성자를반환하는데일치하는 파라미터를 갖는 생성자가 없으면 
NoSuchMethodException 
예외가 발생한다.

1.2 Constructor 파라미터 얻기 
다음과 같이 생성자에 포함된 파라미터 타입 목록을 얻을수 있다.

// 생성자의 파라미터 타입목록 얻기

 

Class aClass = ... // 이전에 얻은 클래스객체

 

Class[] parameterTypes =constructor.getParameterTypes();

 



1.3 Constructor 객체를 사용하여 객체 생성하기
다음과 같이 생성자 객체로부터 객체를 생성한다.

// 하나의 String 파라미터를 갖는 생성자를 얻는다.

 

Constructor constructor =MyObject.class.getConstructor(String.class);

 

// 생성

 

MyObject myObject = (MyObject)constructor.newInstance("constructor-arg1");

 


Constructor.newInstance() 
메서드는 선택적인 개수의 파라미터를취한다하지만 반드시 해당 생성자에 맞는 개수와 타입의

파라미터를 제공해야 한다는 것에 주의하자.





2. 리플렉션을 사용하여 Field 에 접근하기

리플렉션을 사용하여 클래스의 모든멤버 변수를 검사할 수 있으며런타임에 값을 얻어오거나 설정할 수 있다.

이때 하나의 프로퍼티 당 하나의 java.lang.reflect.Field 클래스 객체를 사용하게 된다.

 

2.1 Field 객체 얻기

다음과 같이 Field 객체를 얻는다.

// 클래스에 선언된 public 속성의 Field 객체얻기

 

Class aClass = ... // 이전에 얻은 클래스객체

 

Field[] fields = aClass.getFields();

 

 

Field[]배열은 클래스에 선언된 각 public field 당 하나의 Field 객체를 갖는다.(public field 만을 갖는다는 것에 주의)
접근하려는 필드의 이름을 안다면다음과 같이 접근할 수 있다.

// Field에 접근하기

 

Class  aClass =MyObject.class

 

Field field = aClass.getField("someField");

 

 

위 예제는 아래 MyObject 에 선언된 someField 에 대응하는 Field 인스턴스를 반환합니다.

public class MyObject{

 

public String someField = null;

 

}

 


getField() 
메서드의 파라미터에 해당하는 이름의 필드가 클래스에 존재하지않으면 NoSuchFieldException 예외가발생한다.

2.2 Field 이름 얻기

Field인스턴스를 획득하면다음과같이 Field.getName()을 사용하여 이름을 얻을 수 있다.

// Field 이름 얻기

 

Field field = ... // Field 객체를 얻는다.

 

String fieldName = field.getName();

 

 

2.3 Field 타입 얻기

Field.getType()메서드를 사용하여 필드의 타입을 얻을 수 있다.

// Field 타입 얻기

 

Field field = aClass.getField("someField");

 

Object fieldType = field.getType();

 

 

2.4 Field 값 조회하고 설정하기 
Field 
에 대한 참조를 얻게되면, Field.get(), Field.set() 메소드를 사용하여 값을 얻거나 설정할수 있다.

// Field 값 설정하고 조회하기

 

Class  aClass =MyObject.class

 

Field field = aClass.getField("someField");

 

MyObject objectInstance = new MyObject();

 

Object value = field.get(objectInstance);

 

field.set(objetInstance, value);

 


field.get(), set() 
메서드에는 해당 필드를 소유하는 객체가 인자로전달되야 하며 만일 static 필드라면 null을전달한다.

2.5 Private Field 에 접근하기

클래스의 Private 필드는 외부클래스에서 접근 할 수 없지만리플렉션을사용하면 접근이 가능하다캡슐화를 깨는 동작일 
수 있지만단위테스트와 하이버네이트와 같은 프레임워크에서 유용하게 사용되기도 한다
private 
필드에 접근하기 위해서는 Class.getDeclaredField(Stringname) Class.getDeclaredFields() 메서드를 사용한다. (Class.getField(String name) Class.getFields() 메서드는 public 필드만을 반환한다.)


다음은 privatefield에 접근하는 예이다.

 

Board board = new Board();

 

board.setContents("test contents...");

 

                

 

Field field = cls.getDeclaredField("contents");

 

field.setAccessible(true);

 

String contents = (String) field.get(board);

 

                

 

System.out.println("Private Contents Field:" + contents);

 


Field 
객체의 setAccessible(true)를 호출하지 않고, private 필드값을 조회하려고 하면 IllegalAccessException 예외가 발생한다




3. 리플렉션을 사용하여 Method 호출하기

java.lang.reflect.Method클래스를 사용하여 메서드를 검사하고 호출 하는 방법을 알아보자.



3.1 Method 객체 얻기

Method클래스를 다음과 같이 획득한다.

// Method 객체목록 얻기

 

Class aClass = ... // 이전에 얻은 클래스객체

 

Method[] methods = aClass.getMethods();

 

 

역시 클래스에 선언된 public 메서드당 하나의 Method 인스턴스를 갖으며메서드의 구체적인 파라미터 타입들을 알고 있으면 
해당 메서드의 인스턴스를 얻을 수 있다다음은String 파라미터 하나를 갖는 doSomething 메소드의 Method 객체를 얻는다.

// 파라미터를 갖는 메소드의 Method 객체 얻기

 

Class aClass = ... // 이전에 얻은 클래스객체

 

Method method = aClass.getMethod("doSomething",new Class[]{String.class});

 

 

만일 일치하는 메서드가 없으면NoSuchMethodException이 발생한다파라미터가없는 메서드를 얻기 위해서는 파라미터 
배열 대신 null을 전달한다.

// 파라미터가 없는 메서드의 Method 객체 얻기

 

Class aClass = ... // 이전에 얻은 클래스객체

 

Method method =aClass.getMethod("doSomething", null);

 



3.2 Method 파라미터와 반환값 얻기

다음과 같이 해당 메서드의 파라미터들을얻을 수 있다.

// 메서드의 파라미터 타입목록 얻기

 

Method method = ... //

 

Class[] parameterTypes = method.getParameterTypes();

 


메서드의 반환타입은 다음과 같이 접근한다.

// 메서드의 반환값 타입 얻기

 

Method method = ... //

 

Class returnType = method.getReturnType();

 



3.3 Method 객체를 사용하여 메서드 호출하기(Invoking)

다음과 같이 메서드를 호출할 수 있다.

// 메서드 호출

 

Method method =MyObject.class.getMethod("doSomething", String.class);

 

Object returnValue = method.invoke(null,"parameter-value1");

 

 

invoke()메소드에는 호출하기를 원하는 객체를 전달하며, static 메서드이면 null을 대신 전달한다
Method.invoke(Object target, Object...parameters) 
메서드는 선택적인 개수의 파라미터를 취하지만메서드가 필요로 하는 
정확한 개수의 파라미터를 전달해야 한다

3.4 Private Method 에 접근하기 
Private Field 
에 접근하는 것과 유사하게 Class.getDeclaredMethod(String name) Class.getDeclaredMethods()메서드를 사용한다
(Class.getField(String name) 
 Class.getFields() 메서드는 public 필드만을 반환한다.)


/////////////////////////////////////////////////////////////////////////////////////////////////


리플렉션으로 Getter 와 Setter 검사하기

 

리플렉션을 사용하여 해당 클래스가 어떤 getter setter를 갖는지 검사하는 예를 알아보자.



클래스에 gettersetter 메서드만을 검사할 수는 없으며모든 메서드들을 스캔하여 getter인지 setter인지를 확인해야 한다.

getter setter 메서드가나타내는 특성은 다음과 같다.

 

Getter  “get” 이라는 이름으로 시작하며파라미터가 없고 하나의 값을 반환한다.

Setter  “set” 이라는 이름으로 시작하며하나의 파라미터를 취한다.


몇몇 setter는 값을 반환하지 않을 수도 있고 또 어떤 것은집합값을 반환하거나 어떤 것들은 메서드 chaining을 위해 
값을 반환할 수 있기 때문에 setter의 반환타입에 대한 가정을 해서는 안된다.
다음은 getter setter를 찾아 출력하는예이다.

package com.tistory.hiddenviewer.reflection.executor;

 

import java.lang.reflect.Method;

 

import com.tistory.hiddenviewer.reflection.Board;

 

public class GetterSetterPrinter {

 

        publicstatic void main(String[] args) {

 

               printGettersSetters(Board.class);

 

        }

 

        publicstatic void printGettersSetters(Class aClass){

 

               Method[]methods = aClass.getMethods();

 

               for(Methodmethod : methods){

 

                       if(isGetter(method)){

 

                               System.out.println("getter:" + method);

 

                       }

 

                       if(isSetter(method)){

 

                               System.out.println("setter:" + method);

 

                       }

 

               } //for

 

        }

 

       

 

        publicstatic boolean isGetter(Method method){

 

               //get 으로 시작하지 않으면 반환

 

               if(!method.getName().startsWith("get")){

 

                       returnfalse;

 

               }

 

               // 파라미터가 있으면 반환

 

               if(method.getParameterTypes().length!= 0) {

 

                       returnfalse;

 

               }

 

               // 반환값이 없으면 반환

 

               if(void.class.equals(method.getReturnType())){

 

                       returnfalse;

 

               }

 

               returntrue;

 

        }

 

        publicstatic boolean isSetter(Method method){

 

               //set 으로 시작하지 않으면 반환

 

               if(!method.getName().startsWith("set")){

 

                       returnfalse;

 

               }

 

               // 파라미터가 개수가 1이 아니면 반환

 

               if(method.getParameterTypes().length!= 1) {

 

                       returnfalse;

 

               }

 

               returntrue;

 

        }

 

}

 



출력결과



출처 - http://blog.naver.com/PostView.nhn?blogId=kimgas2000&logNo=90133009397







제공 : 한빛 네트워크
저자 : Russ Olsen
역자 : 백기선
원문 : Reflections on Java Reflection 

일상에서의 리플렉션(reflection)이란 거울 속에 비친 자신의 모습입니다. 프로그래밍 세상에서의 리플렉션은 프로그램이 자신의 모습을 보고 심지어 수정하기 까지 하는 것을 말합니다. Java reflection API는 바로 그런 기능을 언어의 기본 요소인 클래스, 필드, 메소드를 들여다 볼 수 있는 평범한 Java API를 통해 제공합니다. 리플렉션을 이해하는 것은 여러분이 자주 사용하는 툴을 이해하는데 도움이 됩니다. Eclipse가 어떻게 자동완성으로 메소드 이름을 만들어 줄까? Tomcat은 web.xml파일에 있는 클래스 이름을 가지고 웹의 요청을 처리할 서블릿을 실행하는 걸까? Spring은 어떻게 마술 같은 dependency injection을 하는 것일까? 여러분의 프로그램에서도 리플렉션을 사용하여 보다 동적이고 유연한 코드를 작성하실 수 있습니다. 리플렉션을 사용하면 이전에 본적 없는 클래스들을 매우 멋지게 처리할 수 있습니다. 

클래스 만들기 

이미 말했듯이 리플렉션의 기본 아이디어는 프로그램이 동작하는 내부에 집어 넣을 API를 제공하는 것입니다. Java에서 가장 기본이 되는 사상이 바로 클래스기 때문에(클래스 없이 자바 프로그램을 만들어 보세요) Class 클래스부터 살펴보는 것이 좋겠습니다. 이것의 객체는 Class 타입일 것입니다. 일단 Class객체를 가지게 되면 그것으로부터 클래스에 관련된 모든 정보를 뽑아낼 수 있습니다. 클래스의 이름, 그것이 public 인지 abstract 인지 final 인지 그리고 심지어 상위 클래스까지 말이죠. 

이 정도면 이론은 충분합니다. 자, 이제 리플렉션 현미경을 가지고 아래에 있는 매우 간단한 Employee 클래스를 살펴봅시다.

package com.russolsen.reflect;

public class Employee
{
   public String _firstName;
   public String _lastName;
   private int _salary;

      public Employee()
   {
      this( "John", "Smith", 50000);
   }
 
   public Employee(String fn, String ln, int salary)
   {
      _firstName = fn;
      _lastName = ln;
      _salary = salary;
   }
   
      public int getSalary()
   {
      return _salary;
   }
   
   public void setSalary(int salary)
   {
      _salary = salary;
   }
   
   public String toString() 
   {
      return "Employee: " + _firstName +  " "
             + _lastName + " " + _salary;
   }

}

Class 객체를 만드는 가장 쉬운 방법은 해당 클래스 객체의 getClass 메소드를 호출하는 것입니다. 아래에 있는 코드는 Employee 객체를 만들고 그것의 Class 객체를 만들어서 클래스에 대한 다양한 정보들을 출력합니다.

package com.russolsen.reflect;

import java.lang.reflect.Modifier;

public class GetClassExample
{
   public static void main(String[] args)
   {
 
      Employee employee = new Employee();
      
      Class klass = employee.getClass();
      
      System.out.println( "Class name: " + klass.getName());
      System.out.println( 
            "Class super class: " + klass.getSuperclass());
      
      int mods = klass.getModifiers();
      System.out.println( 
            "Class is public: " + Modifier.isPublic(mods));
      System.out.println( 
            "Class is final: " +  Modifier.isFinal(mods));
      System.out.println( 
            "Class is abstract: " + Modifier.isAbstract(mods)); 

   }

}

코드를 실행하면 다음과 같은 결과를 확인할 수 있습니다.

Class name: com.russolsen.reflect.Employee
Class super class: class java.lang.Object
Class is public: true
Class is final: false
Class is abstract: false

예제에서 보이듯이 클래스의 이름과 상위 클래스를 알아내는 것은 다른 접근 메소드들(Getters or Accessors)을 호출하는 것처럼 쉬운 일입니다. 만약 해당 클래스가 public 인지 abstract 인지 final 인지 알고 싶다면 약간 복잡해 집니다. 이 모든 정보가 getModifires에 의해 하나의 int 값으로 패키징되어 넘어오기 옵니다. 다행히 Java는 Modifier 클래스를 통해 getModifiers에서 넘어온 숫자를 가지고 여러 일을 할 수 있는 static 메소드들을 제공해 줍니다. 

객체에 getClass를 호출하는 것 만이 Class 객체를 얻을 수 있는 유일한 방법은 아닙니다. 클래스 이름을 사용하여 직접 얻을 수도 있습니다.

Class klass = Employee.class;

세 번째 방법은 좀 더 흥미로운 방법입니다. 문자열을 통해서 Class 객체를 생성할 수 있습니다. 물론 그 문자열이 클래스 이름을 포함하고 있을 때 말이죠. 다음과 같이 Class 클래스에 있는 forName 을 호출하여 얻을 수 있습니다.

      Class klass = Class.forName("com.russolsen.reflect.Employee");
      
      System.out.println( "Class name: " + klass.getName());
      System.out.println( 
            "Class super class: " + klass.getSuperclass());
      
      // Print out the rest...

forName을 사용할 때 한 가지 주의할 것은 클래스 이름 앞에 완전한 패키지 경로를 붙여줘야 한다는 것입니다. 평범하고 늙은 “Employee” 말고 “com.russolsen.reflect.Employee" 여야만 합니다. forName을 통해 리플렉션 API의 기본적인 강력함(그리고 멋진 것)을 살펴봤습니다. 클래스 이름을 포함한 문자열로 시작할 수도 있고 class로 끝낼 수도 있습니다. 

바로 인스턴스 만들기 

class 객체를 가져오고 그것에 관한 정보를 찾는 것은 그것 자체로도 흥미롭고 유용합니다. 하지만 진짜 재미있는 것은 리플렉션을 사용하여 무언가를 실제 해보는 것입니다. Class 객체를 가지고 할 수 있는 가장 눈에 띄는 작업은 그 클래스의 새로운 객체를 만드는 것입니다. newInstance 메소드를 사용하여 간단하게 만들 수 있습니다. 실제 사용하는 것을 보여 주기 위하여 아래에 있는 간단한 프로그램은 커맨드 라인 인자(command line argument)로 Class 객체를 만들고 그 클래스 타입의 객체를 만드는 프로그램을 보여줍니다.

package com.russolsen.reflect;

public class NewInstanceExample
{
   public static void main(String[] args)
      throws ClassNotFoundException,
      InstantiationException, IllegalAccessException
   {

      Class klass = Class.forName(args[0]);
      Object theNewObject = klass.newInstance();
      System.out.println("Just made: " + theNewObject);
   }
}

위 코드를 "com.russolsen.reflect.Employee" 인자와 함께 실행하면 새로운 Employee 객체를 만들게 됩니다.

Just made: Employee: John Smith 50000

Run it again, but this time feed it "java.util.Date" and you will get:

Just made: Tue Feb 27 20:25:20 EST 2007

간단한 코드 몇 줄로 얼마나 많은 유연성을 얻게 되었는지 생각해보세요. 위에 있는 프로그램은 실제 Employee 나 Date 클래스에 관해 아는 것이 하나도 없지만 각각의 새로운 객체들을 만들 수 있습니다. 이것이야 말로 Java 프로그래밍을 하는 또 다른 방법입니다. 

인자 없는 생성자 너머에 

Class.newInstance 메소드를 호출하는 것은 인자 없이 new를 사용하는 것과 동일합니다. 그러나 만약 인자가 없는 생성자 즉 default 생성자가 없는 클래스에 newInstance를 호출하면 어떻게 될까요? 좋을 일 없습니다. 별로 맘에 안 드는 InstantiationException을 받게 됩니다. 

좋은 소식이 있습니다. 생성자 인자를 필요로 하는 클래스의 객체를 동적으로 만들 수 있습니다. 하지만 약간 더 힘든 작업을 해야 합니다. 그건 바로 클래스에서 해당 생성자를 찾고 적당한 인자를 사용하여 그것을 호출하는 일입니다. 적당한 생성자를 찾는 일은 여러분이 찾고자 하는 생성자에 대한 정보를 가지고 getConstrucor 메소드를 사용하면 됩니다. 그럼 Constuctor 객체를 얻게 되고 그것을 사용하여 새로운 객체를 만들 수 있습니다.

Let's see how this all works in code:
      Class klass = Class.forName("com.russolsen.reflect.Employee");

      Class[] paramTypes = {
            String.class, 
            String.class, 
            Integer.TYPE };
      
      Constructor cons = klass.getConstructor(paramTypes);
      
      System.out.println( "Found the constructor: " + cons);

      
      Object[] args = { 
            "Fred", 
            "Fintstone", 
            new Integer(90000) };
      
      Object theObject = cons.newInstance(args);
      System.out.println( "New object: " + theObject);

생성자들 사이의 차이점은 오직 그것들이 가지고 있는 매개 변수들입니다. 따라서 getConstructor 메소드에 찾고자 하는 생성자에 들어갈 매개변수 각각의 Class들 객체의 배열을 넘겨줍니다. 위에 있는 예제에서는 두 개의 String 그리고 하나의 int를 받는 생성자를 찾게 됩니다. Constructor 객체를 얻은 뒤 새로운 객체를 생성하는 일은 간단합니다. 실제 인자로 들어갈 객체의 배열을 newInstance 메소드를 호출하면서 넘겨주면 됩니다. 

geConstructor에는 조그만 지뢰가 하나 있습니다. 파라미터의 타입으로 생성자를 식별하여 원하는 것을 찾을 때 primitive 인자와 그것의 wrapper 클래스를 잘 구별해야 합니다. 생성자가 인자로 primitive 타입인 int를 받는 것인가 아니면 그것의 삼촌 격인 java.lang.Integer 클래스의 객체를 받는 건가요? 만약 java.lang.integer 같은 wrapper 타입의 객체를 받는 생성자라면 Integer.class처럼 wrapper 클래스를 사용하면 됩니다. primitive 타입인 int를 사용하는 생성자라면 Integer.TYPE을 사용합니다. TYPE은 primitive을 위해 마련한 것입니다. 모든 wrapper 클래들은 static 타입인 TYPE 필드를 가지고 있고 이것을 사용하여 primitive 타입이라는 것을 알려줄 수 있습니다. 역자 백기선님은 AJN(http://agilejava.net)에서 자바 관련 스터디를 하고 있는 착하고 조용하며 점잖은 대학생입니다. 요즘은 특히 Spring과 Hibernate 같은 오픈소스 프레임워크를 공부하고 있습니다. 공부한 내용들은 블로그(http://whiteship.tistory.com)에 간단하게 정리하고 있으며 장래 희망은 행복한 개발자입니다.



출처 - http://www.hanb.co.kr/network/view.html?bi_id=1369







Posted by linuxism

댓글을 달아 주세요