Effective Java : 아이템 65. 리플렉션보다는 인터페이스를 사용하라.

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

    • 리플렉션은 Class 객체를 이용해 특정 클래스의 생성자, 메서드, 필드 등을 가져와서 이런저런 작업을 실행해 볼 수 있음. 
    • 리플렉션이 필요한지 확신할 수 없다면, 대부분 사용할 필요가 없는 것이다.
    • 리플렉션의 단점
      • 컴파일타임의 타입 검사가 주는 이점을 누릴 수 없음. 
      • 코드가 장황해 짐
      • 성능이 떨어짐. 
    • 리플렉션의 사용 시점
      • 최대한 제한된 형태로 단순히 사용해야 함. 
      • 리플렉션은 객체를 생성하는데만 사용하고, 생성한 객체는 가장 최상위 인터페이스로 받아라. (예를 들면 Set)

    리플렉션이란? 

    Class 객체 (Clazz<?>) 가 주어지면, 그 클래스의 생성자, 메서드, 필드에 해당하는 Constructor, Method, Field 인스턴스를 가져올 수 있다. 이런 기술을 리플렉션이라고 한다. 가져온 인스턴스를 invoke()하여 실제로 실행해 볼 수도 있다. 리플렉션을 이용하면 컴파일 당시에 존재하지 않았던 클래스도 이용할 수 있는데, 단점이 존재한다. 

    • 컴파일타임의 타입 검사가 주는 이점을 하나도 누릴 수 없다. 
      • 런타임에 이것저것 가져와서 사용하는 것이기 때문에 런타임 에러가 발생할 가능성이 매우 높아짐. 
    • 리플렉션을 이용하면 코드가 장황해진다.
    • 성능이 떨어진다. 리플렉션을 통한 메서드 호출은 일반 메서드 호출보다 훨씬 느리다. 

     


    리플렉션은 언제 사용해야할까?

    만약 리플렉션이 정말로 필요한지 확인할 수 없다면, 대부분의 경우 리플렉션이 필요없다. 만약 리플렉션을 사용해야 한다면 아주 제한된 형태로만 사용해야 그 단점을 피하고 이점만 취할 수 있다. 따라서 다음과 같이 사용할 것을 권장한다. 

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

    예를 들면 아래처럼 사용하자는 것이다. 아래에서는 인스턴스를 생성할 때만 리플렉션을 사용했고, 생성된 인스턴스는 최상위 인터페이스인 Set 타입으로 선언되었다. 이렇게 사용하는 이유는 리플렉션을 사용하면서 발생할 수 있는 컴파일 에러를 최소화하고, 포괄적인 타입을 사용해 변화에도 열려있는 코드를 만들기 위함이다. 

    Class<? extends Set<String>> cl = null; 
    Set<String> s = null; 
    try { 
    	s = cl.getDeclaredConstructor().newInstance();
    }catch {
    	...
    }

     


    리플렉션을 사용했을 때 단점

    // 클래스 이름을 Class 객체로 변환
    public static void main(String[] args) {
        Class<? extends Set<String>> cl = null;
        try {
            cl = (Class<? extends Set<String>>) Class.forName(args[0]);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    
        Constructor<? extends Set<String>> cons = null;
        try {
            cons = cl.getDeclaredConstructor();
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
        
        // 집합의 인스턴스를 만든다
        Set<String> s = null;
        try {
            s = cons.newInstance();
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    
        // 생성한 집한을 사용한다
        s.addAll(Arrays.asList(args).subList(1, args.length));
        System.out.println(s);
    }

    위 코드는 리플렉션을 사용한 예시다. 코드에서는 리플렉션의 단점을 살펴볼 수 있다. 

    • 리플렉션을 사용했을 때, 총 5개의 메서드를 런타임에 잡아야 함. (컴파일 타입 검사의 이점이..)
    • 코드가 장황해졌다. 

    리플렉션을 사용하면서 총 5개의 에러가 발생했다. 만약 리플렉션을 사용하지 않았다면, 이런 에러들은 컴파일 타임에 모두 처리되었을 것이다. 왜냐하면 단순히 new HashSet() 같은 것을 하면 되었을 것이고 별다른 에러가 발생하지 않았을 것이기 때문이다. 

    두번째로는 객체 하나를 생성하기 위해 코드가 굉장히 길어졌다는 점이다. 이런 코드는 가독성을 떨어뜨린다.

    댓글

    Designed by JB FACTORY