JAVA 리플렉션(Reflection)이란?

wujin·2023년 3월 13일
0

리플렉션(Reflection)

  • 구체적인 클래스 타입을 알지 못하더라도 그 클래스의 메서드, 타입, 변수들에 접근할 수 있도록 해주는 자바 API
  • 컴파일 시간이 아닌 실행 시간에 동적으로 특정 클래스의 정보를 추출할 수 있는 프로그래밍 기법

리플렉션은 언제 사용되는가?

  • 동적으로 클래스를 사용해야할 때 사용한다.
  • 다시 말해 작성 시점에는 어떠한 클래스를 사용해야 할지 모르지만 런타임 시점에서 클래스를 가져와서 실행해야하는 경우 필요하다.
  • 대표적으로 Spring Framework의 Annotation 같은 기능들이 리플렉션을 이용하여 프로그램 실행 도중 동적으로 클래스의 정보를 가져와서 사용한다.

리플렉션은 어떤 정보를 가져올 수 있나?

아래와 같은 정보들을 가져올 수 있으며 해당 정보들을 가져와서 객체를 생성하거나 메서드를 호출하거나 변수의 값을 변경할 수 있다.

  • Class
  • Constructor
  • Method
  • Field

예제

package test;

public class Animal {

    public String name = "myName ?";
    private String city = "mycity ?";

    public Animal() {
    }

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

    private void eat(String x) {
        System.out.println("eat: " + x);
    }

    private void walk() {
        System.out.println("walk");
    }
}
package test;

import test.Animal;

public class Dog extends Animal {
    private String myName = "뽀삐";
    public String myCity = "서울";

    public Dog() {
    }

    private Dog(String myName) {
        this.myName = myName;
    }

    private void myName(String name) {
        System.out.println("myName : " + name);
    }

    private void myCity(String city) {
        System.out.println("myCity : " + city);
    }

    private void hello() {
        System.out.println("hello~");
    }
}
package test;

public class Main {
    public static void main(String[] args) {
    
    }
}

Class 찾기

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

  • Case01 - class를 알고 있다는 전제

    • 아래 코드를 보면 Dog.class 처럼 클래스 정보를 할당할 수 있다. Class객체는 여러 메서드를 제공하고 있으며 getName()은 클래스의 이름을 리턴한다.
public class Main {
    public static void main(String[] args) {
        Class cls = Dog.class;

        System.out.println("Class Name : " + cls.getName());
        // Class Name : test.Dog
    }
}
  • Case02 - class를 참조할 수 없고 이름만 알고 있는 상황

    • 아래 예제는 클래스 이름만으로 클래스의 정보를 가져온다.
    • Class, forName() 메서드에 클래스 이름을 인자로 전달하여 클래스 정보를 가져올 수 있다.
    • 패키지 네임이 포함된 클래스 이름으로 작성해야 한다.
public class Main {
    public static void main(String[] args) throws Exception {
        Class cls = Class.forName("test.Dog");

        System.out.println("Class Name : " + cls.getName());
        // Class Name : test.Dog
    }
}

Constructor 찾기

  • Case01 - 인자가 없는 생성자 가져오기

    • getDeclaredConstructor() 메서드에 아무런 내용을 작성하지 않으면 인자가 없는 기본 생성자를 가져올 수 있다.
    • 기본 생성자가 없고 오버로딩된 생상자만 있다면 java.lang.NoSuchMethodException 예외를 발생시킨다.
import java.lang.reflect.Constructor;

public class Main {
    public static void main(String[] args) throws Exception {
        Class cls = Class.forName("test.Dog");
        Constructor constructor = cls.getDeclaredConstructor();

        System.out.println("Constructor : " + constructor.getName());
        // Constructor : test.Dog
    }
}
  • Case02 - 인자가 있는 생성자 가져오기

    • getDeclaredConstructor(Param)에 인자를 넣으면 해당 타입과 일치하는 생성자를 찾는다.
import java.lang.reflect.Constructor;

public class Main {
    public static void main(String[] args) throws Exception {
        Class cls = Class.forName("test.Dog");
        Constructor constructor = cls.getDeclaredConstructor(String.class);

        System.out.println("Constructor : " + constructor.getName());
        // Constructor : test.Dog
    }
}
  • Case03 - 모든 생성자 가져오기

    • getDeclaredConstructors() 메서드를 사용하면 클래스의 private, public 등의 모든 생성자를 리턴해준다.
import java.lang.reflect.Constructor;

public class Main {
    public static void main(String[] args) throws Exception {
        Class cls = Class.forName("test.Dog");
        Constructor constructors[] = cls.getDeclaredConstructors();

        for (Constructor item : constructors){
            System.out.println("Get constructors : " + item);
        }
        // Get constructors : public test.Dog()
		// Get constructors : private test.Dog(java.lang.String)
    }
}
  • Case04 - public 생성자만 가져오기

    • getConstructor() 메서드를 사용하면 클래스의 public 생성자를 리턴할 수 있다.
import java.lang.reflect.Constructor;

public class Main {
    public static void main(String[] args) throws Exception {
        Class cls = Class.forName("test.Dog");
        Constructor constructors[] = cls.getConstructors();

        for (Constructor item : constructors){
            System.out.println("Get public constructors : " + item);
        }
        // Get public constructors : public test.Dog()
    }
}

Method 찾기

  • getDeclaredMethod() 메서드를 사용하여 인자로 메서드의 파라미터 정보를 넘겨주면 일치하는 것을 찾을 수 있습니다.

  • Case01 - 인자가 있는 메서드 가져오는 방법

import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        Class cls = Class.forName("test.Dog");
        Method method = cls.getDeclaredMethod("myName", String.class);

        System.out.println("Method : " + method);
        // Method : private void test.Dog.myName(java.lang.String)
    }
}
  • Case02 - 인자가 없는 메서드 가져오는 방법
import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        Class cls = Class.forName("test.Dog");
        Method method = cls.getDeclaredMethod("hello", null);

        System.out.println("Method : " + method);
        // Method : private void test.Dog.hello()
    }
}
  • Case03 - 모든 메서드를 가져오는 방법
import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        Class cls = Class.forName("test.Dog");
        Method methods[] = cls.getDeclaredMethods();

        for (Method item : methods) {
            System.out.println("Method : " + item);
            // Method : private void test.Dog.myName(java.lang.String)
            // Method : private void test.Dog.myCity(java.lang.String)
            // Method : private void test.Dog.hello()
        }
    }
}
  • Case 04 - 상속받은 메서드와 public 메서드만 가져오는 방법
import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        Class cls = Class.forName("test.Dog");
        Method methods[] = cls.getMethods();

        for (Method item : methods) {
            System.out.println("Method : " + item);
        }
        /*
         * Method : public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
         * Method : public final void java.lang.Object.wait() throws java.lang.InterruptedException
         * Method : public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
         * Method : public boolean java.lang.Object.equals(java.lang.Object)
         * Method : public java.lang.String java.lang.Object.toString()
         * Method : public native int java.lang.Object.hashCode()
         * Method : public final native java.lang.Class java.lang.Object.getClass()
         * Method : public final native void java.lang.Object.notify()
         * Method : public final native void java.lang.Object.notifyAll()
         */
    }
}

Field 찾기

  • Case01 - getDeclaredField() 메서드를 사용하여 전달된 이름과 일치하는 필드를 찾아줍니다.
import java.lang.reflect.Field;

public class Main {
    public static void main(String[] args) throws Exception {
        Class cls = Class.forName("test.Dog");
        Field field = cls.getDeclaredField("myName");

        System.out.println(field);
        // private java.lang.String test.Dog.myName
    }
}
  • Case 02 - getDeclaredFields() 메서드를 사용하여 객체에 선언된 모든 필드를 찾아줍니다. 단 상속받은 객체의 정보는 찾아주지 않습니다.
import java.lang.reflect.Field;

public class Main {
    public static void main(String[] args) throws Exception {
        Class cls = Class.forName("test.Dog");
        Field fields[] = cls.getDeclaredFields();

        for (Field item : fields) {
            System.out.println(item);
        }
        // private java.lang.String test.Dog.myName
        // public java.lang.String test.Dog.myCity
    }
}
  • Case 03 - getFields() 메서드를 사용하면 상속받은 객체의 public 필드까지 찾아줍니다.
import java.lang.reflect.Field;

public class Main {
    public static void main(String[] args) throws Exception {
        Class cls = Class.forName("test.Dog");
        Field fields[] = cls.getFields();

        for (Field item : fields) {
            System.out.println(item);
        }
        // public java.lang.String test.Dog.myCity
        // public java.lang.String test.Animal.name
    }
}

Field 변경

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

  • Case01 - getField() 메서드를 사용하면 객체의 public 필드를 찾아서 값을 변경할 수 있다.

import java.lang.reflect.Field;

public class Main {
    public static void main(String[] args) throws Exception {
        Dog dog = new Dog();
        Class cls = Class.forName("test.Dog");
        Field field = cls.getField("myCity");

        System.out.println("default field : " + field.get(dog));
        // default field : 서울

        field.set(dog, "제주도");
        System.out.println("update field : " + field.get(dog));
        // update field : 제주도
    }
}
  • Case 02 - setAccessible() 메서드를 사용하면 private로 선언한 필드에 접근

    • setAccessible() 메서드를 사용하면 private 인스턴스 변수나 메서드는 해당 클래스의 외부에서는 접근할 수 없는데 setAccessible(true)를 사용하면 문제없이 접근을 할 수 있다.
import java.lang.reflect.Field;

public class Main {
    public static void main(String[] args) throws Exception {
        Dog dog = new Dog();
        Class cls = Class.forName("test.Dog");
        Field field = cls.getDeclaredField("myName");
        field.setAccessible(true);

        System.out.println("default field : " + field.get(dog));
        // default field : 뽀삐

        field.set(dog, "펩시");
        System.out.println("update field : " + field.get(dog));
        // update field : 펩시
    }
}

0개의 댓글