Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ Item65 ] 리플렉션보다는 인터페이스를 사용하라 #66

Open
SooKim1110 opened this issue Jun 5, 2022 · 1 comment
Open
Assignees
Labels
9장 일반적인 프로그래밍 원칙

Comments

@SooKim1110
Copy link

SooKim1110 commented Jun 5, 2022

[ Item65 ] 리플렉션보다는 인터페이스를 사용하라

리플렉션 기능 (java.lang.reflect)

리플렉션 기능을 사용하면 프로그램에서 임의의 클래스에 접근할 수 있다.

  • Class 객체에서 생성자, 메서드, 필드 인스턴스를 가져올 수 있다
  • 이 인스턴스들을 통해 클래스의 멤버 이름, 필드 타입, 메서드 시그니처 등을 가져올 수 있고, 실제 생성자, 메서드, 필드를 조작할 수 있다
  • 컴파일 당시 존재하지 않던 클래스도 이용할 수 있다

예를 들어 Method.invoke는 어떤 클래스의 어떤 객체가 가진 어떤 메서드라도 호출할 수 있게 해준다.

하지만 다음과 같은 단점도 있다.

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

리플렉션은 제한된 형태로 사용하자

컴파일타임에 이용할 수 없는 클래스를 사용해야하는 프로그램이라도 적절한 인서페이스나 상위 클래스를 이용할 수는 있다.
그러므로, 이런 경우엔 리플렉션은 인스턴스 생성에만 쓰고, 만든 인스턴스는 인터페이스나 상위 클래스로 참조해 사용하자.

예시) 리플렉션의 단점 - Set 인터페이스의 인스턴스 생성

  • 인수들의 출력 순서는 첫번째 인수로 지정한 클래스에 따라 달라진다. (java.util.HashSet: 무작위, java.util.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 (InvocationTargetException e) {
            fatalError("생성자에 접근할 수 없습니다.");
        } catch (InstantiationException e) {
            fatalError("클래스를 인스턴스화할 수 없습니다.");
        } catch (ClassCastException e) {
            fatalError("Set을 구현하지 않은 클래스입니다.");
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }

        // 생성한 집합을 사용한다. 생성한 집합에 두번째 이후의 인수들을 추가한 다음 화면에 출력한다.
        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);
    }

예시를 통해 리플렉션의 단점을 볼 수 있다.

  1. 런타임에 6가지의 예외를 던질 수 있다.
    인스턴스를 리플렉션 없이 생성했다면 컴파일타임에 잡을 수 있는 예외들이다.

  2. 클래스 이름만으로 인스턴스를 생성하기 위해 25줄이나 되는 코드를 작성했다. 리플렉션이 아니라면 생성자 호출 한 줄로 끝났을 일이다.
    리플렉션 예외 각각을 잡는 대신 모든 리플렉션 예외의 상위 클래스인 ReflectiveOperationException을 잡도록 하여 코드 길이를 줄일 수 있다.

런타임에 존재하지 않을 수 있는 다른 클래스, 메서드, 필드와의 의존성을 관리할 때 적합

리플렉션은 버전이 여러 개 존재하는 외부 패키지를 다룰 때 유용하다.
가장 오래된 버젼(최소한의 환경)만 지원하도록 컴파일 한 후, 이후 버젼의 클래스와 메서드는 리플렉션으로 접근하는 방식이다.
이런 경우 접근하려는 클래스나 메서드가 런타임에 존재하지 않을 수 있으므로 대체 수단을 이용하거나 기능을 줄여 동작하는 등의 조치를 취해야한다.

핵심 정리

  • 리플렉션은 알 수 없는 클래스를 사용하는 프로그램을 작성할 때 등 복잡한 특수 시스템을 개발할 때 필요한 강력한 기능이지만 단점도 많다.
  • 되도록 객체 생성에만 사용하고,생성한 객체를 이용할 때에는 적절한 인터페이스나 컴파일타임에 알 수 있는 상위 클래스로 형변환해 사용해야한다.
@SooKim1110 SooKim1110 self-assigned this Jun 5, 2022
@SooKim1110 SooKim1110 added the 9장 일반적인 프로그래밍 원칙 label Jun 5, 2022
@SooKim1110
Copy link
Author

자바 Reflection을 이해하는데 좋은 글을 추천받았었는데 공유합니다~
https://www.oracle.com/technical-resources/articles/java/javareflection.html

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
9장 일반적인 프로그래밍 원칙
Projects
None yet
Development

No branches or pull requests

1 participant