요약

리플렉션은 복잡한 특수 시스템을 개발할 때 필요한 강력한 기능이지만, 단점이 많기에 신중하게 사용해야 한다.

컴파일타임에는 알 수 없는 클래스를 사용하는 프로그램을 작성할 때는 리플렉션 사용을 권장한다.

하지만 되도록이면 객체 생성에만 사용하는 것이 좋고, 인스턴스 생성 후에는 적절한 인터페이스나 컴파일타임에 알 수 있는 상위 클래스로 형변환 후 사용을 권장한다.

 

→ 런타임에 리플렉션을 사용해야하는 경우가 있고, 리플렉션으로 생성한 객체는 인터페이스로 받아서(컴파일타입 체크가능) 사용하자!(리플렉션 장점 + 인터페이스 장점)

 

객체 생성은 리플렉션으로, 객체 사용은 인터페이스로

 

리플렉션

리플렉션 기능(java.lang.reflect)을 이용하면 다음 기능을 사용할 수 있다.

  1. 임의의 클래스에 접근이 가능하다
  2. 클래스의 생성자/메서드/필드의 인스턴스를 가져올 수 있다
  3. 인스턴스를 이용해 각각에 연결된 실제 생성자/메서드/필드 조작이 가능하다
    1. 클래스 인스턴스 생성 or 메서드 호출 or 필드 접근이 가능하다는 것을 의미한다
  4. 주의해야할 점은 리플렉션은 아주 제한된 형태로만 사용해야 그 단점을 피하고 이점만 취할 수 있다
    1. 리플렉션은 인스턴스 생성에만 사용하고,
    2. 생성한 인스턴스는 인터페이스나 상위클래스로 참조해 사용하는 것을 권장한다

 

리플렉션

장점

  1. 복잡한 애플리케이션에서 사용 가능
    1. 코드 분석 도구
    2. 의존관계 주입 프레임워크
  2. 런타임에 존재하지 않을 수도 있는 다른 클래스, 메서드, 필드와의 의존성을 관리할 때 적합하다
    1. 예시로, 버전이 여러 개인 외부 패키지를 다룰 때 유용
    2. 주로 가장 오래된 버전만을 지원하도록 컴파일한 후, 이후 버전의 클래스와 메서드 등은 리플렉션으로 접근하는 방식
    3. 이러기 위해선 접근하려는 새로운 클래스나 메서드가 런타임에 존재하지 않을 수 있다는 사실을 반드시 감안해야 한다

단점

리플렉션의 단점은 다음과 같다.

  1. 컴파일 타입 검사가 주는 이점을 하나도 누릴 수 없다
  2. 코드가 지저분하고 장황해진다
  3. 성능이 떨어진다
    1. 리플렉션을 통한 메서드 호출은 일반 메서드 호출보다 훨씬 느리다.

예시

아래 코드는 다음과 같은 형식으로 진행된다.

  • 리플렉션으로 Set<String> 인터페이스의 인스턴스를 생성한다
  • 정확한 클래스는 명령줄의 첫 번째 인수로 확정한다
  • 그 다음에는 생성한 집합에 두 번째 이후의 인수들을 추가후 화면에 출력한다
    • 출력은 인수 중복 제거 후 진행된다
    • 첫번째 인수로 지정한 클래스가 무엇이냐에 따라 인수 출력 순서가 달라진다
    • HashSet인 경우 무작위로 출력되고, TreeSet인 경우 알파벳 순서로 출력된다
// 리플렉션으로 인스턴스를 생성하고 인터페이스로 참조해 활용한다.
public static void main(String[] args) {
    
    // 클래스 이름을 Class 객체로 변환
    Class<? extends Set<String>> cl = null;
    try {
        cl = (Class<? extends Set<String>>) Class.forName(args[0]);
    } catch (ClassNotFoundException e) {
        fatalError("클래스를 찾을 수 없습니다.");
    }
    
    // 생성자를 얻는다.
    Constructor<? extends Set<String>> cons = null;
    try {
        cons = cl.getDeclaredConstructor();
    } catch (NoSuchMethodException e) {
        fatalError("매개변수 없는 생성자를 칮을 수 없습니다.");
    }
    
    // 집합의 인스턴스를 만든다.
    Set<String> s = null;
    try {
        s = cons.newInstance();
    } catch (IllegalAccessException e) {
        fatalError("생성자에 접근할 수 없습니다.");
    } catch (InstantiationException e) {
        fatalError("클래스를 인스턴스화할 수 없습니다.");
    } catch (InvocationTargetException e) {
        fatalError("생성자가 예외를 던졌습니다 : " + e.getCause());
    } catch (ClassCastException e) {
        fatalError("Set을 구현하지 않은 클래스입니다.");
    }
    
    // 생성한 집합을 사용한다
    s.addAll(Arrays.asList(args).subList(1, args.length));
    System.out.println(s);
}

    private static void fatalError(String msg) {
        System.err.println(msg);
        System.exit(1);
    }

위의 예시에서 리플렉션의 단점이 2가지 있다.

  1. 총 6개의 런타임 예외 발생가능
  2. 쓸데없이 너무 긴 코드
    1. 클래스 이름으로 인스턴스를 생성하기 위해 총 25줄의 코드를 작성해야한다
    2. 리플렉션을 사용하지 않는다면 생성자 호출 1줄 코드로 완료할 수 있다

+ Recent posts