리플렉션(java.lang.reflect
)을 통해 프로그램에서 임의의 클래스에 접근할 수 있다.
Class 객체가 주어지면 그 클래스의 생성자, 그 주어진 그 클래스의 생성자, 메서드, 필드에 해당하는 Constructor, Method, Filed
인스턴스를 가져올 수 있고, 이 인스턴스들로 그 클래스의 멤버 이름, 필드 타입, 메서드 시그니처 등을 가져올 수 있다.
Constructor, Method, Field
인스턴스를 이용해 실제 생성자, 메서드 필드를 조작할 수도 있다. 인스턴스를 생성하거나, 메서드를 호출하거나, 필드에 접근할 수 있다는 뜻이다.
리플렉션을 이용하면 컴파일 당시에 존재하지 않았던 클래스도 이용할 수 있지만 단점들이 있다.
컴파일 타입 검사가 주는 이점을 누를 수 없다. 예외 검사도 마찬가지고, 런타임 오류가 생길 수도 있다.
코드가 지저분하고 장황해진다.
성능이 떨어진다. 리플렉션을 통한 메서드 호출은 일반 메서드 호출보다 훨씬 느리다.
의존관계 주입 프레임워크 같은 곳에서 리플렉션을 써야하기는 하지만, 이런 도구들도 리플렉션을 줄이는 추세다.
컴파일타임에 이용할 수 없는 클래스를 사용해야만 하는 프로그램은 비록 컴파일타임이라도 적절한 인터페이스나 상위클래스를 이용할 수는 있을 것이다. 이런 경우라면 리플렉션은 인스턴스 생성에만 사용하고 생성된 인스턴스는 인터페이스나 상위 클래스로 참조하자.
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);
}
위으 코드는 리플렉션의 단점 두 가지를 보여준다.
런타임에 존재하지 않을 수도 있는 다른 클래스, 메서드, 필드와의 의존성을 관리할 때 적합하다. 버전이 여러 개 존재하는 외부 패키지를 다룰 때 유용한데, 가동할 수 있는 최소한의 환경, 즉 가장 오래된 버전을 지원하도록 컴파일하고, 이후 버전은 리플렉션으로 접근하는 방식이다.