개발 언어/Java

리플렉션

피어나는 열정 2021. 11. 21. 18:29

리플렉션 Reflection

리플렉션Reflection이란 개념은 C, C++과 같은 다른 언어에서는 볼 수 없는 기능입니다.

자바는 정적인 언어로 부족한 부분이 많지만, 이 동적인 문제를 해결하기 위해 리플렉션을 사용합니다.

● 정적 언어: 컴파일 시점에 타입을 결정 - Java, C, C++ 등

● 동적 언어: 런타임 시점에 타입을 결정 - JavaScript, Python, Ruby 등등...

간단하게 리플렉션의 정의를 내려보겠습니다.

What? 리플렉션(Reflection) 이란?

'구체적인 클래스 타입을 알지 못해도, 클래스의 메소드, 타입, 변수들을 접근할 수 있도록 가능하게 해주는 자바 API'

'JVM에서 실행되는 애플리케이션의 런타임 동작을 검사하거나 수정할 수 있는 기능이 필요한 프로그램에서 사용됩니다.'

-> 클래스의 구조를 개발자가 확인할 수 있고, 메소드, 변수 등을 호출하는데 사용됩니다.

리플렉션(Reflection)
컴파일 시간(Compile Time)이 아닌 실행 시간(Run Time)에 동적으로
특정 클래스의 정보를 추출해낼 수 있는 프로그래밍 기법입니다.

Why? 그럼 이러한 리플렉션은 언제 사용될까요?

동적으로 클래스를 사용해야 할 때 필요합니다. 다시말해, 작성 시점에는 어떠한 클래스를 사용해야 할지 모르지만, 런타임 시점에서 클래스를 가져와서 실행해야 할 경우에 필요합니다. 대표적으로 IntelliJ의 자동완성, Spring 프레임워크의 어노테이션 같은 기능들이 리플렉션을 이용해 프로그램 실행 중 동적으로 클래스 정보를 가져와서 사용합니다.

//정적언어 :자바>> 컴파일 단계에서 타입 지정
//동적언어: 런타임에서 타입 지정
public class Main{
    public static void main(String[] args){
        Object car = new Car();
        car.drive(); // 컴파일 에러
        Person per = new Person();
        per.hello();
    }
}

위와 같이 Java에서 모든 클래스의 조상 클래스인 Object 타입으로 Car 클래스를 담을 수는 있지만,

기존 Car 클래스에 존재하는drive() 이라는 메소드는 사용할 수 없고, Object 클래스의 메소드만 사용이 가능합니다.

위와 같은 방식으로 구체적인 Class 타입을 모른다면, 해당 Class의 메소드와 변수들을 사용할 수 없습니다.

즉, 위와 같이 구체적인 타입의 클래스를 모를 때 사용하는게 리플렉션 입니다.

 

"내가 만드는 프로그램인데, 내가 사용할 클래스의 이름과 타입을 모르는 경우가 있을까?"

위와 같은 상황의 대표적인 예가 JDBC 입니다. 자바 가상머신이 동작을 시작하고, 코드가 실행되기 전까지는 어떠한 JDBC의 드라이버가 사용이 될지 알 수 없습니다. JAVA에서 DB를 연동할때 다음과 같은 코드가 존재합니다.

Class.forName("oracle.jdbc.driver.OracleDriver");

위 코드를 사용하여 런타임(Runtime) 시에 동적으로 클래스(OracleDriver)를 로딩하게 합니다.

Class 클래스의 forName() 메소드는 매개변수의 클래스나 인터페이스와 연관된 Class의 객체를 리턴합니다.

위와 같은 방식이 가능한 이유는, 자바 클래스 파일은 바이트 코드로 컴파일되어 Static 영역에 위치하게 됩니다. 이러한 이유로 클래스 이름만 알고 있다면, 언제든지 이 Static 영역을 뒤져서 클래스에 대한 정보를 가져올 수 있습니다.

 

예제

 

Object.getClass()를 통해 클래스의 정보를 로드합니다.

//임의의 클래스를 가져오는 방법
Class c = "Car".getClass();
System.out.println(c);      //class java.lang.String

//Array는 객체이므로 Array 인스턴스에서 클래스 정보를 로드할 수 있습니다.
byte[] b = new byte[1024];
Class c1 = b.getClass();
System.out.println(c1);     //class [B

Set<String> s = new HashSet<>();
Class c2 = s.getClass();
System.out.println(c2);     //class java.util.HashSet

 

.class 문법을 사용합니다.

//.class 문법
boolean bl;
Class c3 = bl.getClass();   //컴파일 에러 발생
Class c4 = boolean.class;

이처럼 boolean형은 원시 유형이기 때문에, b1.getClass()를 사용하면 컴파일 에러가 발생합니다.

Class c5 = java.io.PrintStream.class;
System.out.println(c5);     //class java.io.PrintStreamClass c6 = int[][].class;
System.out.println(c6);     //class [[I

변수 c5는 java.io 유형에 해당하는 PrintStream이 됩니다. 다차원 배열 또한 .class 구문이 사용가능합니다.

Class.forName() 문법을 사용합니다.

//아래와 같이 패키지 명으로 클래스를 로드할 수 있습니다.
Class c7 = Class.forName("ko.maeng.reflection.ReflectionApplication");
Class doubleArray = Class.forName("[D");    //class [D
Class stringArray = Class.forName("[[Ljava.lang.String;");  //class [[Ljava.lang.String;

변수 doubleArray는 double형 배열의 클래스를 로드한 것과 같고, 변수 stringArray는 2차원 문자열 배열의 클래스를 로드한 것과 같습니다.

 

 

TYPE Field를 통한 원시형 클래스 반환 방법.

Class c8 = Double.TYPE;     //double
Class c9 = Void.TYPE;       //void

 

클래스의 정보를 로드해 봤으니, c가 A의 인스턴스인지 확인합니다.

class A{}

public class Main {
    public static void main(String[] args) {
        try{
            Class c = Class.forName("A");

            boolean b = c.isInstance(new Integer(22));
            System.out.println(b);  //false

            boolean b1 = c.isInstance(new A());
            System.out.println(b1); //true
        } catch (ClassNotFoundException e){
            e.printStackTrace();
        }
    }
}

Method를 활용한 클래스 반환 방법.

  • class.getSuperClass() : 슈퍼 클래스를 반환합니다.
  • class.getClass() : 상속된 클래스를 포함하여 모든 공용 클래스, 인터페이스 및 열거형을 반환합니다.
  • class.getDeclaredClass() : 명시적으로 선언된 모든 클래스 및 인터페이스, 열거형을 반환합니다.
  • class.getDeclaringClass() : 클래스에 구성된 클래스(명시적으로 선언된)를 반환합니다.
  • class.getEnclosingClass() : 클래스의 즉시 동봉된 클래스를 반환합니다.
import java.lang.reflect.Method;

public class DemoReflection2 {
    public int sum(int a, int b) throws NoSuchFieldException{
        return a+b;
    }

    public static void main(String[] args) {
        try{
            Class c = Class.forName("DemoReflection2");

            Method[] m = c.getDeclaredMethods();
            for(int i=0; i<m.length; i++){
                Method m1 = m[i];
                System.out.println("name: " + m1.getName());
                System.out.println("declare Class: " + m1.getDeclaringClass());

                Class[] gpt = m1.getParameterTypes();

                for(int j=0; j<gpt.length; j++){
                    System.out.println("Param: " + gpt[i]);
                }

                Class[] ept = m1.getExceptionTypes();

                for(int z=0; z<ept.length; z++){
                    System.out.println("Exception: " + ept[z]);
                }

                System.out.println("ReturnType: " + m1.getReturnType());
            }
        } catch (ClassNotFoundException e){
            e.printStackTrace();
        }
    }
}

출력

 

name: main
declare Class: class DemoReflection2
Param: class [Ljava.lang.String;
ReturnType: void
name: sum
declare Class: class DemoReflection2
Param: int
Param: int
Exception: class java.lang.NoSuchFieldException
ReturnType: int

 

리플렉션  외

ASM을 사용하여 바이트 코드를 동적으로 생성하여 Java 반사 오버헤드를 방지합니다. 결과는 Java 리플렉션 프레임워크를 얼마나 남용하느냐에 따라 Java 리플렉션을 직접 사용하는 것보다 1.6배에서 20배 더 빠릅니다.

(ASM is an all purpose Java bytecode manipulation and analysis framework. )

 

자바 Reflection이란?. 많은 입문용 자바 서적에서 잘 다루지 않는 Reflection이라는… | by Maeng Sol | msolo021015 | Medium

 

자바 Reflection이란?

많은 입문용 자바 서적에서 잘 다루지 않는 Reflection이라는 개념에 대해서 알아보려고 합니다.

medium.com

[Java/자바] - 리플렉션(Reflection) : 네이버 블로그 (naver.com)

 

[Java/자바] - 리플렉션(Reflection)

리플렉션 Reflection 리플렉션Reflection이란 개념은 저에게는 낯선 용어였습니다. 자바 기본서 2권이 있...

blog.naver.com

 

'개발 언어 > Java' 카테고리의 다른 글

Generic  (0) 2021.11.24
grabage collector  (0) 2021.11.10
자바 실행 과정  (0) 2021.11.03
[java] JVM 이란?  (0) 2021.10.27