Reflection

동동주·2024년 5월 9일

CS스터디_자바

목록 보기
17/19

Reflection이란?

  • 구체적인 Class Type을 알지 못하더라도 해당 Class의 method, type, variable들에 접근할 수 있도록 해주는 자바 API이며, 컴파일된 바이트 코드를 통해 Runtime에 동적으로 특정 Class의 정보를 추출할 수 있는 프로그래밍 기법이다.
Class class = Class.forName("클래스 이름");

<정적 바인딩 vs 동적 바인딩>

  • 바인딩이란 프로그램에 사용된 구성 요소의 실제 값 또는 프로퍼티를 결정짓는 행위를 의미한다. 즉, 프로그램에서 사용되는 변수나 메서드 등 모든 것들이 결정되도록 연결해주는 것을 뜻한다.

  • 바인딩을 결정 짓는 시점에 따라 정적 바인딩, 동적 바인딩으로 나뉘게 된다.

    • 동적 바인딩을 대표하는 예제로는 오버라이딩(Overriding)을 들 수 있다. 상속을 이용한 부모-자식 관계일 경우 부모에서 정의한 메서드를 자식에서 Overriding 했다 가정했을 때, 해당 메서드를 이용할 경우 Runtime에 어떤 메서드를 호출할지가 정해진다.
    • 반대로 정적 바인딩을 쉽게 대표하는 예제로 오버로딩(overloading)을 들 수 있다. Compile 과정에서 어떤 메서드를 호출할지 결정하기 때문에 코드 작성 단계에서 어떤 메서드를 사용할지 구분할 수 있고 잘못 사용 했을 경우 Compile error를 발생시킨다.
  • 이렇게 동적 바인딩은 실행 시간에 Binding 되다보니 정적 바인딩보다는 성능상 오버헤드가 있지만, 동적 바인딩을 통해 상속과 다형성 등 다양한 기능을 사용할 수 있는 장점이 있다.

Reflection은 언제 사용할까?

앞서 설명했던 것을 토대로 생각해보면, Reflection은 Runtime에 Class Type을 모르는채로 객체를 생성하고 이용하기 때문에 동적 바인딩을 제공한다.

  • 동적으로 Class를 사용 해야할 경우
    - 코드 작성 시점에서는 어떠한 Class를 사용해야할지 모르지만 Runtime에 Class를 가져와서 실행해야하는 경우 (Spring Annotation)

  • Test Code 작성
    - private 변수를 변경하고 싶거나 private method를 테스트할 경우

  • 자동 Mapping 기능 구현
    - IDE 사용 시 Da 입력만해도 이와 관련된 Class 혹은 Method 목록들을 IDE가 먼저 확인하고 사용자에게 제공한다.

  • Jackson, GSON 등의 JSON Serialization Library
    - Reflection을 사용하여 객체 필드의 변수명/어노테이션명을 Json key와 mapping 해주고 있다.

  • 정적 분석 tool

지금 말한 Reflection의 활용 주제 중, 가장 기대 효과가 높은 부분은 바로 동적으로 Class를 사용해야 하는 경우이다.

Reflection 사용 방법

Reflection은 아래와 같은 정보를 가져올 수 있으며

  • Class/Interface
  • Constructor
  • Method
  • Field
    해당 정보들을 통해 (1) 객체 생성 (2) 메서드 호출 (3) 변수 값을 변경할 수 있다.
  1. Class / Interface
public static void main(String[] args) throws Exception {
	// 1. class를 알고 있을 경우
    Class car = Car.class;
    
    // 2. class 이름만 알고 있을 경우
    Class car = Class.forName("com.reflection.test.Car");
    // class.getName() -> com.reflection.test.Car
 
    // 3. Default 생성자를 이용한 객체 생성
    Car realCar = car.newInstance();
    
    // 4. class에 구현된 interface 확인
    Class[] interfaces = car.getInterfaces();
}
  1. Constructor
public static void main(String[] args) throws Exception {
    Class car = Class.forName("com.reflection.test.Car");
    
    // 1. 인자가 없는 생성자 가져오기
    Constructor constructor = car.getDeclaredConstructor();
    
    // 2. String 인자를 가진 생성자 가져오기
    Constructor constructor = car.getDeclaredConstructor(String.class);
    
    // 3. 모든 생성자 가져오기
    Constructor constructors[] = car.getDeclaredConstructors();
    
    // 4. public 생성자만 가져오기
    Constructor constructors[] = car.getConstructors();
    // public com.reflection.test.Car()
	// public com.reflection.test.Car(java.lang.String)
    
    // 5. 생성자를 이용한 객체 생성
    Car realCar = constructor.newInstance();
}
  1. Method
public static void main(String[] args) throws Exception {
    Class car = Class.forName("com.reflection.test.Car");
  
    // 1. 인자가 없는 method 가져오기
    Method method = car.getDeclaredMethod("move");
    
    // 2. String 인자를 가진 method 가져오기
    Method method = car.getDeclaredMethod("move", String.class);
    
    // 3. 모든 method 가져오기
    Method methods[] = car.getDeclaredMethods();
    
    // 4. 상속받은 method와 public method 가져오기
    Method methods[] = car.getMethods();
	// public void com.reflection.test.Car.move()
	// public void com.reflection.test.Car.move(java.lang.String)
    
    // 5. method 호출
    Class realCar = car.newInstance();
    method.invoke(realCar, /*인자*/);
    
    // 6. 접근 제한자를 무시한 method 호출.
    method.setAccessible(true);
    method.invoke(realCar, /*인자*/);
}
  1. Field
public static void main(String[] args) throws Exception {
    Class car = Class.forName("com.reflection.test.Car");
    
    // 1. car 객체에서 name 에 해당하는 field 가져오기
    Field field = car.getDeclaredField("name");
    
    // 2. car + car super 객체를 포함하여 name에 해당하는 field 가져오기
    Field field = car.getField("name");
    
    // 3. car 객체에 선언된 모든 field 가져오기
    Field[] fields = car.getDeclaredFields();
    // private java.lang.String com.reflection.test.Car.name
	// public java.lang.Integer com.reflection.test.Car.type
    
    // 4. car + car super 객체의 모든 public field 가져오기
    Field[] fields = car.getFields();
	// public java.lang.Integer com.reflection.test.Car.age
}
  1. Field 값 변경
public static void main(String[] args) throws Exception {
    Class class = Class.forName("com.reflection.test.Car");
    Constructor constructor = class.getConstructor()
    Car car = constructor.newInstance()
        
    Field field = car.getField("name");
    
    // 1. public field 일 경우
    field.set(car, "아반떼");
    
    // 2. private field 일 경우
    field.setAccessible(true);
    field.set(car, "아반떼");
}

Reflection의 장점과 단점

장점

  1. 확장성
    대량의 if/else문을 사용하는 것보다 Reflection을 이용하여 재사용 가능한 컴포넌트로 만들 수 있다.

  2. Class 브라우저 및 시각적 개발 환경을 제공
    Reflection을 통해 Method, Property, Constructor를 미리 파악함으로써 사용할 정보를 열거하여 시각적 개발 환경을 구축할 수 있다.

  3. 디버거 및 테스트 도구
    디버거는 개인 Property, Method, Constructor를 검사할 수 있어야 한다. 테스트 장치는 Reflection을 사용하여 클래스에 정의된 발견 가능한 세트 API를 체계적으로 호출하여 테스트에서 높은 수준의 코드 커버리지를 보장할 수 있다.

  4. 라이브러리 파악
    Java에서 지원하는 라이브러리가 아닌 특정 기업의 라이브러리를 사용하는 경우 해당 라이브러리에 존재하는 클래스 및 메서드를 분석할 수 있다.

단점

  1. 컴파일 시점에 가는한 타입 확인이 불가능하여 캄파일 시에 타입 확인이나 예외 검사를 할 수 없다.

  2. 클래스,메서드,필드를 접근하여 직접 이용하기 때문에 객체 지향 프로그래밍의 특징인 추상화를 위반한다.

  3. 컴파일 에러가 아닌 런타임시에 에러가 발생하기 때문에 코드 운용에 위험이 있다.

    📑 출처: https://velog.io/@alsgus92/Java-Reflection%EC%9D%80-%EB%AC%B4%EC%97%87%EC%9D%B4%EA%B3%A0-%EC%96%B8%EC%A0%9C%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-%EA%B2%83%EC%9D%B4-%EC%A2%8B%EC%9D%84%EA%B9%8C

0개의 댓글