의존관계 프레임워크처럼 리플렉션을 사용해야 하는 경우에도 리플렉션의 사용을 줄이고 있다.
아마도 우리 애플리케이션에서는 리플렉션이 필요가 없을 것이다.
이런 경우에는 리플렉션은 인스턴스 생성에만 사용하고, 이렇게 만든 인스턴스는 인터페이스나 상위 클래스로 참조해 사용하자
아래 예시는 명령줄의 첫 번째 인수로 정확한 클래스를 확정하고,
전달받은 클래스의 생성자를 얻어, 인스턴스를 생성하여 사용하는 과정이다.
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 (IllegalArgumentException 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) {
Sytstem.err.println(msg);
System.exit(1);
}
이 경우 리플렉션의 단점 2가지를 알 수 있다.
1. 런타임에 총 여섯 가지나 되는 예외를 던질 수 있다.
이 예외들은 인스턴스를 리플렉션 없이 생성했다면 컴파일 타임에 잡아낼 수 있었을 예외들이다.
2. 클래스 이름만으로 인스턴스를 생성해야 하기에 많은 양의 코드를 작성해야 한다.
만약, 리플렉션이 아니라면 생성자 호출 한 줄로 끝났을 것이다.
참고로, 리플렉션 예외 각각을 잡는 대신 모든 리플렉션 예외의 상위 클래스인 ReflectiveOperationException을 잡도록 할 수도 있다.
이러한 리플렉션의 단점은 인스턴스를 생성하는 부분에만 국한된다.
이후에는 여타의 인스턴스를 사용할 때와 같다.
런타임에 존재하지 않을 수도 있는 다른 클래스, 메서드, 필드와의 의존성을 관리할 때 적합하다.
ex) 버전이 여러 개 존재하는 외부 패키지를 다룰 때,
가장 오래된 버전만을 지원하도록 컴파일한 후, 이후 버전의 클래스와 메서드 등은 리플렉션으로 접근한다.
이 경우 접근하려는 새로운 클래스나 메서드가 런타임에 존재하지 않을 수 있다는 사실을 감안해야 한다.
컴파일타임에는 알 수 없는 클래스를 사용하는 프로그램을 작성한다면, 리플렉션을 사용하자.
단, 되도록 객체 생성 시에만 사용하고, 생성한 객체를 이용할 때는 적절한 인터페이스나 상위 클래스로 형 변환해 사용하자