티스토리 뷰

Java

자바 리플렉션(Reflection)

J-Mandu 2022. 3. 31. 09:00

리플렉션(Reflection)이란?

리플렉션은 구체적인 클래스 타입을 알지 못하여도, 해당 클래스의 메소드, 타입, 필드(변수)들을

접근할 수 있도록 해주는 자바 API 입니다.

https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html

 

먼저 리플렉션을 실습할 프로젝트를 생성해보겠습니다.

package me.whiteship;

public class Book {

    private String a = "a";

    private static String B = "BOOK";

    private static final  String C = "BOOK";

    public String d = "d";

    protected String e = "e";

    public Book() {

    }

    public Book(String a, String d, String e) {
        this.a = a;
        this.d = d;
        this.e = e;
    }

    private void f() {
        System.out.println("f");
    }

    public void g() {
        System.out.println("g");
    }

    public int h(){
        return 100;
    }
}
package me.whiteship;

public interface MyInterface {
}
package me.whiteship;

public class MyBook extends Book implements MyInterface{
    public String extendA = "extend";
}

기본적인 실습 환경을 해보았습니다.

이제부터 리플렉션에 대해서 살펴보겠습니다.

package me.whiteship;

import java.util.Arrays;


public class App 
{
    public static void main( String[] args ) throws ClassNotFoundException {
        /* 클래스 인스턴스 접근방법*/
        Class<Book> bookClass = Book.class;

        Book book = new Book();
        Class<? extends Book> bookClass2 = book.getClass();

        Class<?> bookClass3 = Class.forName("me.whiteship.Book");
    }
}

리플렉션을 사용하려면  Class<T> 타입을 써야 합니다.

Class<T>에 접근하는 방법은 다음과 같이 3가지 방법이 있습니다.

 

1. 모든 클래스를 로딩 한 다음 Class<T>의 인스턴스가 생기므로, '타입. class'로 접근할 수 있습니다.

ex) Class<Book> bookClass = Book.class 

 

2. 모든 인스턴스는 getClass() 메소드를 가지고 있습니다.

'인스턴스.getClass()'로 접근할 수 있습니다.

ex) Book book = new Book();

      Class<? extends Book> bookClass2 = book.getClass();

 

3. 클래스를 문자열로 읽어오는 방법이 있습니다.

Class.forName("FQCN") // FQCN : 풀 패키지 클래스 경로

클래스 경로에 해당 클래스가 없다면 ClassNotFoundExcetion이 발생합니다.

ex) Class<?> bookClass3 = Class.forName("me.whiteship.Book");

 

package me.whiteship;

import java.lang.reflect.Modifier;
import java.util.Arrays;


public class App 
{
    public static void main( String[] args ) throws ClassNotFoundException {
        // Field(변수) 가져오기
        Class<Book> bookClass = Book.class;
        Arrays.stream(bookClass.getFields()).forEach(System.out::println);

        Book book = new Book();
        Arrays.stream(book.getClass().getDeclaredFields()).forEach(field -> {
            try {
                field.setAccessible(true);
                System.out.printf("%s : %s \n",field, field.get(book));
                
                int modifier = field.getModifiers();
                System.out.println(Modifier.isPublic(modifier));
                
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }

        });


        // Method(메소드) 가져오기
        Class<Book> bookClass2 = Book.class;
        Arrays.stream(bookClass2.getMethods()).forEach(System.out::println);


        //Constructor(생성자) 가져오기
        Class<Book> bookClass3 = Book.class;
        Arrays.stream(bookClass3.getDeclaredConstructors()).forEach(System.out::println);


        //Super Class(상위 클래스) 가져오기
        System.out.println(MyBook.class.getSuperclass());


        //Interface(인터페이스) 가져오기
        Arrays.stream(MyBook.class.getInterfaces()).forEach(System.out::println);

    }
}

그럼 Class<T>를 통하여 할 수 있는 것이 무엇인지 위 코드와 함께 살펴보겠습니다.

1. 필드(목록) 가져오기
getField(String name) : 해당 public 변수를 가져옵니다.
getFields() : public 변수들을 가져옵니다.
getDeclaredField(String name) : 해당 변수를 가져옵니다.
getDeclaredFields() : Access Modifier(접근 제한자) 상관없이 변수들을 가져옵니다.
public 이외의 접근제한자 변수들에 접근하려면 Field 인스턴스가 setAccessible(true) 여야 합니다.

 

2. 메소드(목록) 가져오기

상속받은 모든 메소드를 가져옵니다.

getMethod(String name) : 해당 public 메소드를 가져옵니다.

getMethods() : public Method 들을 가져옵니다.

getDeclaredMethod(String name) : 해당 메소드를 가져옵니다.

getDeclaredMethods() : Access Modifier(접근 제한자) 상관없이 메소드들을 가져옵니다.

public 이외의 접근 제한자 메소드들을 접근하려면 Method 인스턴스가 setAccessible(true) 여야 합니다.

 

3. 생성자 가져오기

getConstructor(String name) : 해당 public Constructor를 가져옵니다.

getConstructors() : public Constructor 들을 가져옵니다.

getDeclaredConstructor(String name) : 해당 Constructor를 가져옵니다.

getDeclaredConstructors() : Access Modifier(접근 제한자) 상관없이 Constructor를 모두 가져옵니다.

public 이외의 접근 제한자 생성자들을 접근하려면 Constructor 인스턴스가 setAccessible(true) 여야 합니다.

 

4. 상위 클래스 가져오기

getSuperclass() : 해당 클래스의 상위(부모) 클래스를 가져옵니다.

 

5. 인터페이스 가져오기

getInterfaces() : 해당 클래스의 상속받은 인터페이스를 가져옵니다.

 

애노테이션(annotation), 생성자 등 그 외 여러 가지를 가져올 수 있습니다.

 

package me.whiteship;

import java.util.ArrayList;

class Book {
    public static String A = "A";

    private String B = "B";

    public Book() {

    }

    public Book(String b) {
        B = b;
    }

    private void c () {
        System.out.println("C");
    }

    public int sum (int left, int right){
        return left + right;
    }
}

리플렉션 API를 이용하여 클래스 정보를 수정 및 실행하는 실습을 하기 전에 Book 클래스를 수정해 보겠습니다.

package me.whiteship;


import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;


public class App 
{
    public static void main( String[] args ) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        Class<?> bookClass = Class.forName("me.whiteship.Book");
        Constructor<?> constructor = bookClass.getConstructor(String.class);
        Book book = (Book) constructor.newInstance("myBook");
        System.out.println(book);

    
        //Field(변수) 
        Field a = Book.class.getDeclaredField("A");
        System.out.println(a.get(null));
        a.set(null, "AAAAA");
        System.out.println(a.get(null));

        Field b = Book.class.getDeclaredField("B");
        b.setAccessible(true);
        System.out.println(b.get(book));
        b.set(book, "BBBBB");
        System.out.println(b.get(book));


        // Method(메소드) 
        Method c = Book.class.getDeclaredMethod("c");
        c.setAccessible(true); // Access Modifier가 private
        c.invoke(book);        

        Method d = Book.class.getDeclaredMethod("sum", int.class, int.class);
        int invoke = (int) d.invoke(book, 10, 10);
        System.out.println(invoke);

    }
}

먼저 Book클래스 인스턴스를 생성해보겠습니다.

위 코드와 같이 클래스를 문자열로 읽고서 생성자 변수를 따로 만들어 인스턴스를 생성할 수도 있습니다.

또한 bookClass.getConstructor(String.class).newInstance("myBook")으로 생성할 수도 있습니다.

인스턴스는 생성자를 통하여 생성할 수 있습니다.

ex) getConsturcor().newInstance();

a.get(Object obj), a.set(Object obj, Object value)에 첫 번째 parameter(매개변수)인 obj는 해당 Instance를 의미합니다.

a변수는 Book의 static변수인 A를 의미하여 Instance 없이 값을 가져올 수 있기 때문에 null 값이 매개변수로 들어가는 것입니다.

그리고 b변수는 Book의 static 변수가 아니기 때문에 Instance를 생성해야 변수가 선언 및 초기화가 되기 때문에

Book 클래스의 Instance를 생성한 book변수가 매개변수로 들어가는 것입니다.

 

Method 실행은 Method 클래스의 invoke 메소드를 사용하면 됩니다.

invoke(Object obj, Object... args)에 첫 번째 매개변수는 마찬가지로 Instance를 의미합니다.

d변수는 Book의 sum 메소드를 의미하기 때문에

d.invoke(book, 10, 10) 코드를 실행하면 반환 값으로 20이 나옵니다. 

 

이로써 공부한 내용을 간략히 정리해보았습니다. 

감사합니다.

 


출처

https://www.inflearn.com/course/the-java-code-manipulation