본문 바로가기

오늘 이거 알았네요/Java

[Java] Reflection; 리플렉션이 뭔데

728x90

육각형 개발자를 읽는 중 이 단어가 나와 잉? 이게 뭐지? 

책에서 본 거 같긴 한데... 정확히 이게 뭔데! 해서 찾아봤다. 

 

두둥

 

Reflection


사전적 정의

리플렉션 - 실체가 아닌 반사 반영된 이미지를 통해 어떤 행위를 하는 것 

 

자바 세상에서의 무엇이, 어디에 반사되어 있는 걸까? 

실체 - 클래스 / 거울 - JVM 메모리 영역 

 

- 자바 

컴파일러가 자바 코드를 바이트 코드로 바꿔줌 

클래스 로더는 자바 바이트코드를 읽어 JVM 메모리에 저장 

리플렉션은 JVM 메모리 영역에 저장된 클래스 정보를 꺼내와 필요한 정보(생성자, 필드, 메서드) 가져와 사용하는 기술 

 

 - 리플렉션이 제공하는 기능

클래스 : 실행 중인 자바 애플리케이션의 클래스와 인터페이스의 정보를 가진 클래스 

그래서 조회 할 수 있는 것? 

1. 클래스에 붙은 어노테이션

2. 클래스 생성자 조회

3. 클래스 필드 조회

4. 클래스 메서드 조회

5. 부모 클래스, 인터페이스 조회

etc ..

 

Class type은 public 생성자가 존재하지 않는다.

JVM에 의해 자동으로 객체가 생성된다. 

 

Class의 메서드 사용 

// {클래스타입}.class
Class<?> clazz = Deer.class;

// {인스턴스}.getClass()
Deer 뿔이없는사슴 = new Deer("뿔이없는사슴");
Class<?> clazz = 뿔이없는사슴.class();

// Class.forName("{전체 도메인 네임}")
Class<?> clazz = Class.forName("org.example.Deer");

 

- 주의점이 있다면?

getMethods vs getDeclaredMethods

 

-- getMethods

상위 클래스와 상위 인터페이스에서 상속한 메서드를 포함하여 public인 메서드들을 모두 가져온다

-- getDeclaredMethods

접근 제어자와 관계없이 상속한 메서드를 제외하고 직접 클래스에서 선언한 메서드들을 모두 가져온다

 

   getXXX와 getDeclaredXXX를 잘 구분하여 사용

 

 

import java.lang.reflect.Constructor;

public class Deer {
    private static final String CATEGORY = "동물";

    private String name;
    public int age;

    private Deer() {
        this.name = "밤비";
        this.age = 0;
    }

    public Deer(String name) {
        this.name = name;
    }

    public Deer(String name, int age) {
        this.name = name;
        this.age = age;
    }

 

 

    
// 생성자를 통한 객체 생성
// 파라미터로 구분하여 클래스에 선언된 생성자를 가져올 수 있다.

Class<?> clazz = Class.forName("example.Deer");

    Constructor<?> constructor1 = clazz.getDeclaredConstructor();
    Constructor<?> constructor2 = clazz.getDeclaredConstructor(String.class);
    Constructor<?> constructor3 = clazz.getDeclaredConstructor(String.class, int.class);

 

Object deer1 = construct1.newInstance();

// Error 
// 왜? 생성자의 접근제어자가 private이라 접근 불가

 

이때 Reflection을 이용하면 private 생성자에도 접근할 수 있다.

 

어떻게?

setAccessible 메서드 이용 

constructor1.setAccessible(true);
    Object deer1 = constructor1.newInstance();
    Object deer2 = constructor2.newInstance("고라니");
    Object deer3 = constructor3.newInstance("고라니", 3);

 

 

디버깅해 보면 잘 생성되었다 

 

 

필드 정보 조회

getDeclaredFields()를 사용하여 클래스에 정의된 필드정보들을 Field 타입 객체로 받아올 수 있다.

private field가 존재할 수 있기 때문에 접근을 위해 setAccessible(true)로 설정해 줌 

Object deer = constructor.newInstance("백두산사슴", 300);

        Field[] fields = clazz.getDeclaredFields();

        for(Field field : fields){
            field.setAccessible(true);
            System.out.println(field);
            System.out.println("value : " + field.get(deer)); // 원하는 인스턴스의 정보
            System.out.println("----------------------------------");

 

 

+ private 필드의 값도 변경할 수 있다. 😱

(private...을? 뭔가 조심해서 써야 함을 감지... )

Field field = clazz.getDeclaredField("name");

            field.setAccessible(true);
            System.out.println("기존 : " + field.get(deer));
            field.set(deer, "순록");
            System.out.println("변경 : " + field.get(deer));

 

 

 

메서드 관련 기능

 

public class Deer {
    
    ... // 생략
    
    
    private void speak(final String sound, final int count){
        System.out.println(sound.repeat(count));
    }

    public void eats(){
        System.out.println("풀을 뜯어 먹다.");
    }

    public int getAge() {
        return age;
    }

 

메서드 타입 객체로 받아올 수 있다.

 

+ private 메서드도 호출할 수 있다.

 

Method method = clazz.getDeclaredMethod("speak", String.class, int.class);
        method.setAccessible(true);
        method.invoke(deer, "먀아아아아아~ ", 5);

 

speak() 호출하다 .. ㅎ

 

 

 

아니 그래서 리플렉션 어디에 사용하니 

 

--  리플렉션은 왜 써?

 

프레임워크나 라이브러리에 많이 사용 

사용자가 생성한 객체가 어떤 타입인지 컴파일 시점까지 알 수 없다

이러한 문제를 동적으로 해결하기 위해 사용 

e.g  JPA, Mockito, Junit ... 그리고 우리의 빛.... 자동완성 기능..... 🥰, 어노테이션 

 

 

-- 어노테이션은 어떻게 동작할까? 

 

1. 리플렉션을 통해 클래스나 메서드, 파라미터 정보를 가져온다 

2. 리플렉션의  getAnnotation(s), getDeclaredAnnotation(s)등의 메서드를 통해 원하는 어노테이션이 붙어있는지 확인

3. 어노테이션이 붙어 있다면 원하는 로직을 수행

 

오 .... 리플렉션은 생각보다 우리 가까이에 있다 

 

 

 

리플렉션의 단점 

 

1. Reflection API는 컴파일 시점이 아니라 런타임 시점에서 클래스를 분석한다
→ JVM을 최적화할 수 없기 때문에 성능저하 발생

2. 런타임 시점에 클래스 정보를 알게 된다. 이는 컴파일 시점에 제공하는 타입 체크 기능 사용 불가 
→ 존재하지 않는 클래스에 접근해 메서드를 호출하면 ClassNotFoundException 에러 발생 

3. 코드가 복잡해짐 
→ getConstructor() 어쩌고 ~ setAccessible(true) 저쩌고 ~ 

4. 내부를 노출해서 추상화를 파괴한다. 
→ private 필드나 메서드에 접근할 수 있고 모든 클래스의 정보를 알게 된다. >>>  불변성 또한 지킬 수 없다. 

 

따라서 클래스 정보를 컴파일 시점에 알아야 하는 특수한 경우가 아니라면 리플렉션 사용을 지양하는 것이 좋다.

 

^^.. 

 

 

 

[테코톡] 파랑, 아키의 리플렉션을 정리한 내용입니다. 

728x90