ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JAVA] 자바 리플렉션(Java Reflection)
    프로그래밍/JAVA 2020. 7. 31. 19:05

    리플렉션(Reflection)이란?

    구체적인 클래스 타입을 알지 못해도 해당 클래스의 생성자, 메소드, 타입, 변수들에 접근할 수 있도록 도와주는 기본적으로 Java에서 제공하는 API

     

    리플렉션의 필요성?

    동적으로 클래스를 사용해야할 때 필요.

    작성 시점에는 어떤 클래스를 사용해야할 지 모르는 경우, 런타임 시점에서 클래스를 가져와서 실행해야 하는 경우에 사용

    ex) IntelliJ의 자동완성, 스프링 프레임워크의 어노테이션 등

     

    ※ 런타임 : 프로그래밍 언어가 구동되는 환경

     

    리플렉션의 원리

    Java에서는 모든 .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 에서 접근할 수 있게 도와준다.

     

    https://www.holaxprogramming.com/2013/07/16/java-jvm-runtime-data-area/

     

    JVM의 Runtime Data Area

    안녕 프로그래밍

    www.holaxprogramming.com

    java.lang.reflect의 기능

    이 패키지에는 생성자, 메소드, 필드같은 클래스들이 있고, 생성자를 통해 새로운 객체를 생성할 수 있다.

     

    - getter, setter 메소드를 통해 필드의 값을 읽거나 수정할 수 있음

    - invoke 메소드를 통해 메소드를 호출

    - parameter, return type, modifer 등 클래스의 관련된 모든 정보를 알고 조작할 수 있음

    - setAccessible을 통해 private 접근, 조작 가능

     

    사용 예제

    /* Parent.java */
    
    package test;
    
    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 test;
    
    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 test;
    
    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
        {
    
        }
    }

     

    Class 찾기

    Class 객체는 클래스 또는 인터페이스를 가리킨다. java.lang.Class이며 import하지 않고 사용할 수 있다.

     

    다음 코드와 같이 Chilld.class 처럼 클래스 정보를 할당할 수 있다. Class 객체는 여러 메소드를 제공하며 getName() 메소드는 클래스의 이름을 리턴한다.

     

    Class clazz = Child.class;
    System.out.println("Class name : " + clazz.getName());
    
    // Class name : test.Child

     

    위의 예제는 IDE에서 클래스를 알고 있다는 전제에 사용할 수 있다. 만약 클래스를 참조할 수 없고 이름만 알고 있다면 어떻게 클래스 정보를 가져올까?

     

     Class clazz2 = Class.forName("test.Child");
     System.out.println("Class name : " + clazz2.getName());
            
     // Class name : test.Child      

     

    위의 코드는 클래스 이름만으로 클래스 정보를 가져온다. Class.forName() 메소드에 클래스 이름을 인자로 전달하여 클래스 정보를 가져올 수 있다. (패키지 이름이 포함된 클래스 이름을 작성)

     

    Constructor 찾기

    다음 코드는 클래스로부터 생성자를 가져오는 코드이다. getDeclaredConstructor() 메소드는 인자 없는 생성자를 가져오고 안에 인자를 넣으면 그 타입과 일치하는 생성자를 찾는다.

     

    Constructor constructor = clazz.getDeclaredConstructor();
    System.out.println("Constructor: " + constructor.getName());
    
    // Constructor: test.Child
    
    Constructor constructor2 = clazz.getDeclaredConstructor(String.class);
    System.out.println("Constructor(String): " + constructor2.getName());
    
    // Constructor(String): test.Child

     

    그리고 다음 코드는 모든 생성자를 가져온다. getDeclaredConstructors() 메소드를 통해 클래스의 private, public 등의 모든 생성자를 리턴한다.

     

    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 생성자만 리턴해준다.

     

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

     

    Method 찾기

    다음 코드는 이름으로 메소드를 찾는 코드로, getDeclaredMethod()의 인자로 파라미터 정보를 넘겨주면 일치하는 것을 찾아준다.

     

    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)

     

    만약 인자가 두개라면 아래처럼 클래스 배열을 만들어서 인자를 넣어준다.

     

    Class partypes[] = new Class[1];
    partypes[0] = int.class;
    Method method = clazz.getDeclaredMethod("method4", partypes);

     

    모든 메소드를 찾으려면 다음과 같이 getDeclaredMethods를 사용한다. 공통적으로 Delcared가 들어가면 Super 클래스의 정보는 가져오지 않는다.

     

    Method methods[] = clazz.getDeclaredMethods();
    for (Method method : methods) {
        System.out.println("Get methods in Child: " + method);
    }
    
    // Get methods in Child: public int test.Child.method4(int)
    // Get methods in Child: private int test.Child.method5(int)

     

    getMethods()는 public 메소드를 리턴해주며, 상속받은 메소드들도 모두 찾아준다.

     

     Method methods2[] = clazz.getMethods();
     for (Method method : methods2) {
     	System.out.println("Get public methods in both Parent and Child: " + method);
     }
    
    // Get public methods in both Parent and Child: public int test.Child.method4(int)
    // Get public methods in both Parent and Child: public void test.Parent.method2(int)
    // Get public methods in both Parent and Child: public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
    // Get public methods in both Parent and Child: public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
    // Get public methods in both Parent and Child: public final void java.lang.Object.wait() throws java.lang.InterruptedException
    // Get public methods in both Parent and Child: public boolean java.lang.Object.equals(java.lang.Object)
    // Get public methods in both Parent and Child: public java.lang.String java.lang.Object.toString()
    // Get public methods in both Parent and Child: public native int java.lang.Object.hashCode()
    // Get public methods in both Parent and Child: public final native java.lang.Class java.lang.Object.getClass()
    // Get public methods in both Parent and Child: public final native void java.lang.Object.notify()
    // Get public methods in both Parent and Child: public final native void java.lang.Object.notifyAll()

     

    Method 호출

    클래스로부터 메소드 정보를 가져와, 객체의 메소드를 호출할 수 있다.

     

    메소드 객체를 생성했으면, Method.invoke() 메소드로 호출할 수 있다. 첫번째 인자는 호출하려는 객체, 두번째 인자는 전달할 파라미터 값이다.

     

    Method method = clazz.getDeclaredMethod("method4", int.class);
    int returnValue = (int) method.invoke(new Child(), 10);
    System.out.println("return value: " + returnValue);
    
    // method4: 10
    // return value: 10

     

    다음 코드는 Parent의 method1() 메소드를 호출하는 코드다. 이 메소드는 인자가 없기 때문에, getDeclaredMethod() 에 인자를 입력하지 않아도된다. 그리고 getDeclaredMethod는 상속받은 클래스의 정보를 가져오지 않기 때문에 Parent에 대한 클래스 정보를 가져와야 한다.

     

    그리고 method1() 의 접근 지정자가 private 이므로 이에 접근하기 위해서 setAccessible(true) 로 설정해준다.

     

     Class clazz2 = Class.forName("test.Parent");
     Method method2 = clazz2.getDeclaredMethod("method1");
     method2.setAccessible(true);
     method2.invoke(new Child());
    
    // method1

     

    Field 변경

    클래스로부터 변수 정보를 가져와 객체의 변수를 변경할 수 있다.

     

    다음 코드는 cstr1 변수를 가져와서 값을 출력하고 변경한 뒤 다시 출력하는 코드이다.

     

     Child child = new Child();
     Field fld = clazz.getField("cstr1");
     System.out.println("child.cstr1: " + fld.get(child));
    
    fld.set(child, "cstr1");
    System.out.println("child.cstr1: " + fld.get(child));
    
    // child.cstr1: 1
    // child.cstr1: cstr1

     

    private 변수를 수정하려면 다음과 같이 수정해준다.

     

    Field fld2 = clazz.getDeclaredField("cstr2");
    fld.setAccessible(true);
    fld.set(child, "cstr2");
    System.out.println("child.cstr2: " + fld.get(child));
    
    // child.cstr2: cstr2

     

     

    출처

    http://libqa.com/wiki/88

     

    Social Q&A LibQA™

    libqa 는 글라이더 오픈소스팀이 만든 소셜 기반의 Q&A 지식공유 Wiki 플랫폼입니다.

    libqa.com

    https://codechacha.com/ko/reflection/

     

    Java - Reflection 쉽고 빠르게 이해하기

    자바의 리플렉션(Reflection)은 클래스, 인터페이스, 메소드들을 찾을 수 있고, 객체를 생성하거나 변수를 변경할 수 있고 메소드를 호출할 수도 있습니다. Reflection은 Class, Constructor, Method, Field와 같

    codechacha.com

    https://brunch.co.kr/@kd4/8#comment

     

    자바의 리플렉션

    java reflection를 이해하시나요? | Java Reflection API를 이해하시나요? 리플렉션이란 개념은 초보 자바 개발자에게는 생소한 개념이고, 이해하기 힘든 개념인 것 같습니다. 일단 저에겐 그랬습니다. 그�

    brunch.co.kr

    '프로그래밍 > JAVA' 카테고리의 다른 글

    [JAVA] final  (0) 2020.08.10
    [JAVA] static 멤버, static 메소드  (0) 2020.08.05
    [JAVA] 상속 예제  (0) 2020.07.26
    [JAVA] 접근지정자(Access Specfier)  (0) 2020.07.26
    [JAVA] 가비지 컬렉터(Garbage Collector)  (0) 2020.07.26

    댓글

Designed by Tistory.