65. 리플렉션보다는 인터페이스를 사용하라

주제: 리플렉션보다는 인터페이스를 사용하라

  • (핵심) java.lang.reflect 패키지를 통해 런타임에 클래스 정보를 얻고 조작할 수 있지만, 단점이 많아 매우 제한적으로 사용해야 하며, 가능하다면 인터페이스 기반 접근을 우선해야 한다.


리플렉션이란?

  • 프로그램 실행 중(런타임)에 임의의 클래스에 접근 가능.

  • Class, Constructor, Method, Field 인스턴스.

  • 가능한 작업

    • 클래스/멤버 정보 조회

    • 실제 멤버 조작:

      • 인스턴스 생성 (Constructor.newInstance()).

      • 메서드 호출 (Method.invoke()).

      • 필드 값 접근/수정 (Field.get(), Field.set()).

  • 컴파일 시점에는 존재하지 않거나 알 수 없었던 클래스도 런타임에 로드하여 사용 가능.


리플렉션의 단점

  1. 컴파일타임 검사 이점 상실 (Loss of Compile-time Checking Benefits)

    • 타입 안전성 X

      • 잘못된 타입의 객체나 메서드 사용 시 런타임 오류 발생.

    • 예외 검사 X

      • 리플렉션 API는 확인된 예외(Checked Exception)를 제대로 검사하지 못함.

      • 존재하지 않거나 접근 불가한 멤버 호출 시 RuntimeException 또는 관련 리플렉션 예외 발생.

  2. 지저분하고 장황한 코드

    • 단순한 생성/호출에도 많은 리플렉션 API 호출 및 광범위한 try-catch 블록 필요. 가독성 저하.

  3. 성능 저하

    • 일반적인 메서드 호출이나 필드 접근보다 '훨씬' 느림. 분석 및 해석 오버헤드 발생.

    • (참고) 실제 테스트 시 메서드 호출 속도 11배 차이.


권장 사용 패턴

  • 아주 제한적 사용

  • 핵심

    • 생성은 리플렉션, 사용은 인터페이스/상위 클래스

    • 컴파일 시점에는 구체 클래스를 알 수 없지만, 사용할 인터페이스나 상위 클래스는 아는 경우.

  • 과정

    1. 리플렉션 API를 사용해 인스턴스를 생성.

      • Class.forName(), getDeclaredConstructor(), newInstance()

    2. 생성된 인스턴스를 컴파일 시점에 아는 인터페이스나 상위 클래스 타입으로 형변환하여 변수에 할당.

    3. 이후 코드에서는 해당 인터페이스/상위 클래스에 정의된 메서드만 사용하여 객체 처리. (리플렉션 코드와 분리)


예시 분석 (Code 65-1: 명령줄 인수로 Set 구현체 동적 생성)

  • 목표 : 프로그램 실행 시 주어진 클래스 이름으로 Set<String> 인스턴스를 동적으로 생성하고 사용하는 것.

    • 예: java.util.HashSet, java.util.LinkedHashSet

  • 리플렉션 과정

    1. Class.forName(args[0]): 문자열 이름으로 Class 객체 로드.

    2. cl.getDeclaredConstructor(): 기본 생성자(Constructor) 객체 얻기.

    3. cons.newInstance(): 생성자를 호출하여 실제 Set 인스턴스 생성.

    4. 이 과정에서 ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, ClassCastException다양한 예외 처리 필요.

  • 인터페이스 사용

    • Set<String> s = ...: 생성된 객체를 Set 인터페이스 타입 변수 s로 받음.

    • s.addAll(...), System.out.println(s): 이후 s 변수를 통해 Set 인터페이스의 표준 메서드만 사용.

      • 이 부분은 리플렉션과 무관하게 타입 안전하고 명확함

  • 예시가 보여주는 단점

    • 6가지 잠재적 런타임 예외 발생 가능성 (컴파일 시점 확인 불가).

    • 단 한 줄(new HashSet<>())이면 될 인스턴스 생성을 위해 25줄의 복잡한 코드 필요.


고급/특수 사용 사례 (Advanced/Special Use Case)

  • 다중 버전 외부 라이브러리 지원 (Supporting Multi-version External Libraries)

    • 호환성을 위해 가장 오래된 버전(최소 요구사항)의 라이브러리로 컴파일.

    • 런타임에 더 최신 버전의 라이브러리가 존재할 경우, 해당 버전에만 있는 추가 클래스나 메서드를 리플렉션으로 감지하고 접근 시도.

    • (필수 조건) 해당 최신 기능이 런타임에 없을 경우를 반드시 대비해야 함

      • 예: 대체 로직 실행, 기능 비활성화


최종 요약 (Key Takeaway)

"리플렉션은 특정 문제(컴파일 시점에 알 수 없는 클래스 사용 등) 해결에 강력하지만, 코드 가독성/타입 안전성/성능 저하라는 명확한 단점이 있다.

필요하다면 사용하되, 가급적 인스턴스 생성에만 국한하고, 생성된 객체는 반드시 인터페이스나 상위 클래스로 형변환하여 참조하고 사용함으로써 리플렉션의 단점을 코드 전체로 확산시키지 않도록 격리하라."

Last updated