leelee.log

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

Book/이팩티브 자바

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

leeleelee3264 2020. 5. 18. 23:10

Effective Java 3e 아이템 65를 요약한 내용입니다.

리플랙션(java.lang.reflect)를 이용하면 컴파일 타임에 알 수 없는 임의의 클래스까지 접근을 할 수 있다. 생성자메서드를 가져올 수 있으며 맴버 이름, 필드 타입도 가져올 수 있다.
가져오는 것 뿐만 아니라 생성자, 메서드, 필드를 실제로 이용할 수 있다. 예를 들어 Method.invoke()를 이용해서 블러들인 메소드를 사용할 수 있다.

//ex1
 TestClass testClass = new TestClass(); // TestClass 안에 testClassMethod가 있다고 가정 
    Class reClass = testClass.getClass();
    Method[] m = reClass.getDeclaredMethods();
    for (int i = 0 ; i < m.length ; i++) {
        if (m.getName().compareTo("testClassMethod") == 0 &&
            m.getParameterTypes().length == 1) {
            Object[] param = new Object[] {"인자값" };
            m.invoke(testClass, param); //메소드를 호출할 객체,호출할 메소드에 전달할 파라메터
        }
    }

//ex2
  Method[] m = TestClass.class.getDeclaredMethods() // ex1과 동일한 결과가 나온다.

편리한 리플렉션의 단점

  • 컴파일타임 타입 검사가 주는 이점을 하나도 누릴 수 없다.
        존재하지 않는 클래스, 메소드를 호출해도 컴파일타임 검사에서는 발견하지 못해 런타임 오류가 발생한다.
  • 리플렉션을 이용하면 코드가 지저분하고 장황해진다
  • 성능이 떨어진다
        리플렉션을 이용한 메소드 호출은 일반 메소드 호출보다 훨씬 느리다. 매개변수가 없이 int를 반환하는 메소드도 리플렉션을 이용하면 11배나 느려졌다.
리플렉션을 써야하는 복잡한 어플리케이션 (ex 코드 분석 도구, 의존관계 주입 프레임워크)에서 리플렉션이 쓰이기는 하나  
점차 사용을 줄요가는 추세이다. 리플렉션은 아주 제한된 형태로만 사용해야 그 단점을 피하고 이점만 취할 수 있다.

리플렉션은 인스턴스 생성에만 사용하고, 만든 인스턴스는 인터페이스나 상위 클래스로 참조해 사용하라.

public static void main(String[] args) {


    // 클래스 이름을 Class 객체로 변환
    Class<? extends Set<String>> cl = null;
    try {
      cl = (Class<? extends Set<String>>)  // 비검사 형변환, @SuppressWarnings을 쓰고 주석을 달아야 한다.item27
          Class.forName("java.util.HashSet");
    } catch (ClassNotFoundException e) {
      fatalError("클래스를 찾을 수 없습니다.");
    }

    // 생성자를 얻는다.
    Constructor<? extends Set<String>> cons = null;
    try {
      cons = cl.getDeclaredConstructor(); // java.lang.reflect 사용
    } catch (NoSuchMethodException e) {
      fatalError("매개변수 없는 생성자를 찾을 수 없습니다.");
    }

    // 집합의 인스턴스를 만든다.
    Set<String> s = null;
    try {
      s = cons.newInstance();  // java.lang.reflect 사용
    } catch (IllegalAccessException e) {
      fatalError("생성자에 접근할 수 없습니다.");
    } catch (InstantiationException e) {
      fatalError("클래스를 인스턴스화할 수 없습니다.");
    } catch (InvocationTargetException e) {
      fatalError("생성자가 예외를 던졌습니다: " + e.getCause());
    } catch (ClassCastException e) {
      fatalError("Set을 구현하지 않은 클래스입니다.");
    }

    // 생성한 집합을 사용한다. 상위 인터페이스인 Set의 메서드를 사용
    s.addAll(Arrays.asList("a","b","c").subList(1, 3));
    System.out.println(s);
  }

  private static void fatalError(String msg) {
    System.err.println(msg);
    System.exit(1);
  }
}
  1. forName()에 String 형태의 클래스 파일명을 넣어주면 해당하는 클래스를 반환해준다. 단, 실제 클래스가 만들어진 것은 아니라서 메모리에 올라오지 않았다.
  2. newInstance() 를 사용해서 클래스의 인스턴스를 만들어준다.
  3. java.io.reflect가 제공해준 생성자로 인스턴스를 생성한 이후 부터는 reflect의 기능을 쓰지 않고 인터페이스나 상위 클래스를 참조해서 사용한다.

리플렉션의 두 가지 단점

리플렉션을 사용하지 않았다면 컴파일 타임에 잡을 수 있는 오류들을 6가지나 던진다.
클래스 이름만 사용하여 인스턴스를 생성해내기 위해 25줄이나 되는 코드를 작성했다. 생성자 호출로는 한줄이면 된다.

코드를 줄이기 위해 java.io.reflect의 최상위 오류인 ReflectiveOperationException 을 사용해도 된다.

image
ReflectiveOperationException와 subclass exception

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

  • 버젼이 여러 개 존재하는 외부 패키지를 다룰 때 유용하다.
  • 가장 오래된 버젼만 지원하도록 컴파일 한 후, 이후 버젼의 클래스와 메서드는 리플렉션으로 접근하는 방식이다.
  • 이런 경우 접근하려는 새로운 클래스나 메서드가 런타임에 존재하지 않을 수 있다는 사실을 감안해서 대체 수단을 만들어 둬야한다.
핵심정리  
리플렉션은 복잡한 시스템을 개발할 때 필요한 강력한 기능이지만 단점이 많기 때문에 리플렉션은 되도록 객체 생성에만 사용하고, 
생성한 객체를 이용할 때는 적절한 인터페이스나 컴파일 타임에 알 수 있는 상위 클래스로 형변환해서 사용해야 한다.