리플렉션(java.lang.reflect)를 이용하면 프로그램에서 임의의 클래스에 접근할 수 있다.
- 클래스의 생성자, 메서드, 필드에 해당하는 constructor, method, field 인스턴스를 가져올 수 있다.
- 클래스의 멤버 이름, 필드 타입, 메서드 시그니처 등을 가져올 수 있다.
- 위의 것들을 활용해서 실제로 인스턴스를 생성하거나, 메서드를 호출하거나, 필드에 접근할 수도 있다.
- 컴파일 당시에 존재하지 않던 클래스도 이용할 수 있다.
KimDoubleB/JavaStartingFromBottom
Class.forName
getDeclaredConstructors()
getDeclaredMethods()
getDeclaredFields()
setAccessible(boolean)
newInstance(parameter)
invoke(class, parameter)
public, private 상관없이 다 접근이 가능하고 생성, 사용 및 변경까지 가능하다.
- 컴파일타임 타입 검사가 주는 이점을 하나도 누릴 수 없다.
- 리플렉션을 이용하면 코드가 지저분해지고 장황해진다.
- 각종 오류 처리로 인해 로직을 보기 힘들며, 코드를 읽기가 힘들어진다.
- 성능이 떨어진다.
- 훨씬 느리고, 책에 의하면 int 반환 메서드의 경우 일반 메서드에 비해 11배나 느렸다고 한다.
⇒ 코드 분석 도구나 의존관계 주입 프레임워크 같이 리플렉션을 사용해야하는 경우가 있지만, 만약 리플렉션이 필요한지 확신할 수 없다면 아마 필요 없을 것이다. (사용하지 마라...) - 이런 도구들마저도 위 단점들 때문에 리플렉션 사용을 점차 줄이고 있다고 함.
리플렉션은 아주 제한된 형태로만 사용해야 그 단점을 피하고 이점만 취할 수 있다. (그래도 해결하지 못하는 단점들은 존재)
- 인터페이스 및 상위클래스로 참조해 사용하자. 즉, 리플렉션은 인스턴스 생성에만 쓰고, 이렇게 만든 인스턴스는 인터페이스나 상위클래스로 참조해 사용하자.
SomeInterface instance = InstanceMakerUsingReflection()
아래 예제에서는 Set<String>
인터페이스를 사용한 예이다.
- args를 통해 첫 번째 인수로 지정한 클래스가 무엇이냐에 따라 저장되는 순서(구조)가 달라진다.
- 즉, 컴파일타임에는 어떤 클래스인지 알 수 없다.
- HashSet을 지정하면 무작위 순서가 될 것이고, TreeSet을 지정하면 알파벳 순서가 될 것이다.
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) {
fatalError("클래스를 찾을 수 없습니다.");
}
// 생성자를 얻어오기
Constructor<? extends Set<String>> cons = null;
try{
cons = cl.getDeclaredConstructor();
} catch (NoSuchMethodException e) {
fatalError("매개변수 없는 생성자를 찾을 수 없습니다.");
}
// 위에서 만든 클래스를 통해 집합 인스턴스 생성
Set<String> s = null;
try{
s = cons.newInstance();
} catch (IllegalAccessException 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){
System.out.println(msg);
System.exit(1);
}
위 예를 통해 리플렉션의 단점을 직접 확인할 수 있다.
- 런타임 중에 총 여섯가지나 되는 예외를 던질 수 있다.
- 이 예외들이 인스턴스를 리플렉션 없이 생성했다면 컴파일 타임에 잡아낼 수 있었을 예외들
- 클래스 이름만으로 인스턴스를 생성해내기 위해 무려 25줄 이상의 코드를 작성했다.
- 리플렉션이 아니라면 생성자를 호출하는 한 줄로 끝났을 일
그래도 위 과정으로 인스턴스를 만들어내면, 이후 사용하는 코드는 기존 인스턴스를 사용할 때와 똑같다.
위에서 말했듯 코드 분석 도구, 의존관계 주입 프레임워크에서 많이 사용된다.
예를 들어
- Intellij - 자동완성 등
- Spring framework, Jackson, Hibernate ...
- Spring에선 관련 Utils를 제공
보통 일반? 개발자가 활용할 땐, 어노테이션과 리플렉션 조합으로 원하는 로직을 만드는 경우가 많은 것 같다.
Spring Annotation 과 Reflection 을 활용해서 Entity의 여러 필드 한번에 수정하기
Java Custom Annotations Example - Mkyong.com
Java Annotation과 Reflection이란?
Annotation과 Reflection을 이용한 챗봇 컨트롤러 만들기