Reflection

Park Suyong·2022년 1월 26일
1

Study

목록 보기
1/12

Reflection 이란?

객체를 통해 클래스의 정보를 분석해 내는 프로그래밍 기법

구체적인 클래스 타입을 알지 못해도 그 클래스의 메소드, 타입, 변수들에 접근할 수 있다.

즉, 컴파일 시간이 아니라 런타임에 동적으로 특정 클래스의 정보를 객체화하여 분석 및 추출해낼 수 있는 프로그래밍 기법이다.

좀 더 이해하기 위해 리플렉션을 왜 사용하는지, 언제 사용하는지 먼저 알아보도록 한다.

Reflection을 왜 사용하는 걸까?

자바 및 코틀린은 다형성을 활용하여 아래와 같은 객체 생성이 가능하다.

Object obj = new Car(0);
obj.move();
val obj: Any = Car(0)
obj.move()

obj 객체가 Car 클래스의 move 메서드를 사용할 수 있을까? 안 된다. 컴파일 에러가 발생한다.

(물론 Any 혹은 Object에 move 메소드가 구현되어 있고 Car 클래스가 move 메소드를 오버라이딩 했다면 위와 같은 경우는 가능하다.)

자바와 코틀린은 정적 언어로, 컴파일 타임에 타입이 결정된다. obj 객체는 Object / Any 타입으로 결정됐으므로 Object / Any 클래스의 메서드와 인스턴스 변수만을 사용할 수 있다.

생성된 obj 라는 객체는 Object / Any 클래스 타입만 알고 있을 뿐이다. Car 클래스라는 구체적인 타입은 알 수 없다.

이말은 즉슨, 구체적인 클래스를 모르면 해당 클래스의 정보에 접근할 수 없다는 것이다.

이러한 정적 언어로의 한계점을 보완하여 구체적인 클래스를 알지 못해도 런타임 도중 해당 클래스의 정보에 접근할 수 있도록 해주는 것이 Reflection 이다.

리플렉션은 런타임에 다른 클래스를 동적으로 로딩하여 접근할 때, 클래스와 메서드 및 멤버 필드에 대한 정보를 얻어야 할 때 사용하게 될 것이다. 다만, 코드를 작성할 때는 구체적인 클래스를 모를 일이 거의 없기에 실제로 리플렉션을 활용하는 경우는 거의 없다.

하지만 프레임워크나 라이브러리에서는 많이 사용된다. 프레임워크나 라이브러리에서는 사용자가 어떤 클래스를 만들지 예측할 수 없다. 프레임워크에서 구체적이지 않은 객체를 받아 동적으로 해결해 주기 위해 Reflection을 사용하는 것이다.

Reflection을 어디서 사용하고 있을까?

  1. Private Test
    • private method에 대해 unit test를 진행하고자 할 때, private을 유지하면서 테스트 코드를 작성하기 위해서는 리플렉션을 사용해야 한다.
  2. Injection
    • Dependency Injection
    • Dagger1의 경우, 리플렉션을 사용했다고 한다. Dagger2에서는 Reflection을 사용하지 않는다.
  3. Serializable
    • 객체나 데이터를 외부의 자바 시스템에서 이용할 수 있도록 byte 형태로 변환하거나, byte로 변환된 데이터를 다시 객체로 변환하는 기술
    • 직렬화된 객체를 읽기 위해 java.io.ObjectInputStream 클래스의 readObject() 메서드를 이용하는데, readObject()는 내부적으로 리플렉션을 이용해 직렬화된 객체의 readObject() 메서드를 호출한다.

Reflection을 적용했을 때 장단점은 무엇일까?

리플렉션은 다음과 같은 장점을 갖는다.

  1. 유연성(Flexibility)
    • 확장 가능한 유연한 코드를 작성할 수 있다.
    • 리플렉션의 Constructor 객체를 통해 클래스 생성자에 접근하여 대상 객체를 생성할 수 있다.
    • 가령, if / else 구문으로 객체를 구분 및 생성하는 함수를 작성했다. 클래스 이름이 매개 변수로 전달된다. 이 경우, 클래스가 추가되면 if / else 문 또한 변경되어야 한다. 리플렉션을 적용한다면, 클래스 이름을 넘겼을 때 if / else 문 없이 Constructor 객체를 사용함으로써 객체를 생성할 수 있다.
    • 참고 자료

다만, 리플렉션은 아래와 같은 단점을 갖고 있다.

  1. 성능 오버헤드

    • 리플렉션을 사용함으로써 컴파일 타임이 아닌 런타임에 동적으로 타입을 분석하여 정보를 가져오게 되므로, JVM 최적화를 수행할 수 없다.

    • 런타임에 동적으로 타입을 분석하기 때문에 예기치 못한 문제가 런타임 도중 발생할 수 있다.

    • 다만, 참고 자료에 의하면 아래 내용을 기술하고 있다.

      Reflection의 사용에 관한 사실들은 사실 사실이 아니다. 성능, 디버깅, 그리고 복잡성과 관련된 내용들은 잘못 사용된 예에서 파생한 오해들이다.

      Reflection은 염려할 만큼의 성능저하를 가져오지 않는다. 대량의 if/else문이나 switch문 대신, 잘 설계된 Reflection은 객체지향 철학을 어기지 않으면서도 더 좋은 성능을 발휘할 수도 있다. (참고 자료의 Reflection에 따른 성능 저하 표 참고)

      이 결과를 통해 알 수 있는 사실은 “Reflection에 따른 성능 저하”가 아니라 “성능 측정 결과, Reflection을 사용한 지금 이 경우에는 성능이 저하되는 것을 검증했다”라는 것이다. 최적화 또는 성능 개선(Optimization)시 유의해야 할 점은 반드시 최적화 이전과 이후의 성능을 측정하여, 성능개선이 가시적으로 보일 때에만 적용해야 한다는 것이다.

  2. 추상화 위반

    • Field.setAccessible()Class.getDeclaredField(String name), Class.getDeclaredFields()를 사용하면 직접 접근할 수 없는 private 인스턴스 변수, 메서드에 접근하여 조작할 수 있다. 따라서, 내부를 노출시키게 되어 추상화가 깨지게 된다.
  3. 디버깅

    • 리플렉션은 디버깅을 어렵게 한다고 한다. 다만, 이는 오해에 가깝다.
    • 컴파일 단계에 처리할 수 있는 오류들이 런타임에 발생한다는 것은 단점이라고 볼 수 있을 것이다. 하지만, 리플렉션을 사용했을 때 런타임 에러 메시지를 최대한 상세하게, 친절하게 표현하도록 Exception 처리 전략을 설계한다면 충분히 보완할 수 있다.

공부하며 겪은 착오

  1. Koin (Kotlin Injection Framework)

리플렉션 기술 조사를 진행하면서, Koin에서 의존성을 주입하는 방법 또한 리플렉션 일 수 있겠다는 생각이 들어 찾아 봤으나, 아니었다.

Koin은 다음과 같이 소개하기도 한다.

A pragmatic lightweight dependency injection framework for Kotlin developers: no proxy, no code generation, no reflection.

Koin은 get, inject 등이 reified로 구현되어 있었다.

reified

reified는 inline func와 조합해서야만 사용할 수 있다. reified type과 함께 인라인 함수가 호출되면 컴파일러는 type argument로 사용된 실제 타입을 알고 만들어진 바이트 코드를 직접 클래스에 대응되도록 바꿔 준다. 따라서, myVar is T는 런타임과 바이트 코드에서 myVar is String이 된다.

  1. Type Introspection

리플렉션에 대한 기술 조사를 하던 와중, type introspection 을 자주 볼 수 있었다.

Type Introspection은 런타임에 객체의 타입이나 속성을 검사하는 것이라고 설명한다. 객체의 클래스, 메소드, 필드 등의 객체 정보를 조사하는 것이다.

여기까지 보았을 때, Reflection와 개념이 유사하다 생각되어 헷갈리는 부분이 있었다. (위키백과에서도 헷갈려서는 안 된다고 말하고 있다.)

type introspection을 런타임 시점에 사용되는 자신의 구조와 행위를 관리하는 것이라 한다. (수정할 수 있는 프로세스까지 포함하면 Reflection이라 한다.)

객체의 타입과 시스템의 코드를 확인하는 것은 Reflection이 아니라, Type Introspection 이며, 런타임에 수정할 수 있는 능력까지 하여 Reflection으로 정의한다고 한다.

결론적으로, Reflection은 Type Introspection의 과정에 수정까지 포함된 것이라 할 수 있다.

References

profile
Android Developer

0개의 댓글