티스토리 뷰
리플렉션 기능(java.lang.reflect)을 이용하면 프로그램에서 임의의 클래스에 접근할 수 있습니다.
Class 객체가 주어지면 그 클래스의 생성자, 메서드, 필드에 해당하는 Constructor, Method, Field 인스턴스를 가져올 수 있고, 이어서 이 인스턴스들로는 그 클래스의 멤버 이름, 필드 타입, 메서드 시그니처 등을 가져올 수 있습니다.
나아가 Constructor, Method, Field 인스턴스를 이용해 각각에 연결된 실제 생성자, 메서드, 필드를 조작할 수도 있습니다.
이 인스턴스들을 통해 해당 클래스의 인스턴스를 생성하거나, 메서드를 호출하거나, 필드에 접근할 수 있다는 뜻입니다.
예를 들어 Method.invoke는 어떤 클래스의 어떤 객체가 가진 어떤 메서드라도 호출할 수 있게 해줍니다.
⇒ 물론 일반적인 보안 제약사항은 준수해야 합니다.
리플렉션을 이용하면 컴파일 당시에 존재하지 않던 클래스도 이용할 수 있는데, 물론 단점도 있습니다.
리플렉션의 단점
[1]. 컴파일타임 타입 검사가 주는 이점을 하나도 누릴 수 없다.
예외 검사도 마찬가지입니다.
프로그램이 리플렉션 기능을 써서 존재하지 않는 혹은 접근할 수 없는 메서드를 호출하려 시도하면 (주의해서 대비 코드를 작성해두지 않았다면) 런타임 오류가 발생합니다.
[2]. 리플렉션을 이용하면 코드가 지저분하고 장황해진다.
지루한 일이고, 읽기도 어렵습니다.
[3]. 성능이 떨어진다.
리플렉션을 통한 메서드 호출은 일반 메서드 호출보다 훨씬 느립니다.
고려해야 하는 요소가 많아 정확한 차이는 이야기하기 어렵지만 책에서는 약 11배나 느렸다고 하네요.
코드 분석 도구나 의존 관계 주입 프레임워크처럼 리플렉션을 써야 하는 복잡한 애플리케이션이 몇 가지 있습니다.
하지만 이런 도구들마저 리플렉션 사용을 점차 줄이고 있습니다. ⇒ 단점이 명확하기 때문
리플렉션은 아주 제한된 형태로만 사용해야 그 단점을 피하고 이점만 취할 수 있습니다.
컴파일타임에 이용할 수 없는 클래스를 사용해야만 하는 프로그램은 비록 컴파일타임이라도 적절한 인터페이스나 상위 클래스를 이용할 수는 있을 것입니다. (아이템 64)
다행히 이런 경우라면 리플렉션은 인스턴스 생성에만 쓰고, 이렇게 만든 인스턴스는 인터페이스나 상위 클래스로 참조해 사용합시다.
예를 들어 다음 프로그램은 Set<String> 인터페이스의 인스턴스를 생성하는데, 정확한 클래스는 명령줄의 첫 번째 인수로 확정합니다.
그리고 생성한 집합(Set)에 두 번째 이후의 인수들을 추가한 다음 화면에 출력하빈다.
첫 번째 인수와 상관없이 이후의 인수들에서 중복은 제거한 후 출력합니다.
반면, 이 인수들이 출력되는 순서는 첫 번째 인수로 지정한 클래스가 무엇이냐에 따라 달라집니다.
java.util.HashSet을 지정하면 무작위 순서가 될 것이고, java.util.TreeSet을 지정하면 알파벳 순서로 출력될 것입니다.
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 (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.err.println(msg);
System.exit(1);
}
간단한 프로그램이지만 여기서 선보인 기법은 꽤나 강력합니다.
이 프로그램은 손쉽게 제네릭 집합 테스터로 변신할 수 있습니다.
즉, 명시한 Set 구현체를 공격적으로 조작해보며 Set 규약을 잘 지키는지 검사해볼 수 있습니다.
비슷하게, 제네릭 집합 성능 분석 도구로 활용할 수도 있습니다.
사실 이 기법은 완벽한 서비스 제공자 프레임워크(아이템1)을 구현할 수 있을 만큼 강력합니다.
대부분의 경우 리플렉션 기능은 이 정도만 사용해도 충분합니다.
위 코드에서 볼 수 있는 리플렉션의 단점
[1]. 런타임에 총 여섯 가지나 되는 예외를 던질 수 있다.
그 모두가 인스턴스를 리플렉션 없이 생성했다면 컴파일타임에 잡아낼 수 있었을 예외들입니다.
[2]. 클래스 이름만으로 인스턴스를 생성해내기 위해 무려 25줄이나 되는 코드를 작성했다.
리플렉션이 아니라면 생성자 호출 한 줄로 끝났을 일입니다.
참고로, 리플렉션 예외 각각을 잡는 대신 모든 리플렉션 예외의 상위 클래스인 ReflectiveOperationException을 잡도록 하여 코드를 줄일 수도 있습니다.
⇒ 자바 7부터 지원
두 단점 모두 객체를 생성하는 부분에만 국한됩니다.
객체가 일단 만들어지면 그 후의 코드는 여타의 Set 인스턴스를 사용할 때와 똑같습니다.
그래서 실제 프로그램에서는 이런 제약에 영향받는 코드는 일부에 지나지 않습니다.
이 프로그램을 컴파일하면 비검사 형변환 경고가 뜹니다.
하지만 Class <? extends Set<String>>으로의 형변환은 심지어 명시한 클래스가 Set을 구현하지 않았더라도 성공할 것이라, 실제 문제로 이어지지는 않습니다. 단, 그 클래스의 인스턴스를 생성하려 할 때 ClassCastException을 던지게 됩니다.
이 경고를 숨기는 방법은 아이템 27을 참고하십시오.
드물긴 하지만, 리플렉션은 런타임에 존재하지 않을 수도 있는 다른 클래스, 메서드, 필드와의 의존성을 관리할 때 적합합니다.
이 기법은 버전이 여러 개 존재하는 외부 패키지를 다룰 때 유용합니다.
가동할 수 있는 최소한의 환경, 즉 주로 가장 오래된 버전만을 지원하도록 컴파일한 후, 이후 버전의 클래스와 메서드 등은 리플렉션으로 접근하는 방식입니다.
이렇게 하려면 접근하려는 새로운 클래스나 메서드가 런타임에 존재하지 않을 수 있다는 사실을 반드시 감안해야 합니다.
즉, 같은 목적을 이룰 수 있는 대체 수단을 이용하거나 기능을 줄여 동작하는 등의 적절한 조치를 취해야 합니다.
정리
- 리플렉션은 복잡한 특수 시스템을 개발할 때 필요한 강력한 기능이지만, 단점도 많다.
- 컴파일타임에는 알 수 없는 클래스를 사용하는 프로그램을 작성한다면 리플렉션을 사용해야 할 것이다.
- 단, 되도록 객체 생성에만 사용하고, 생성한 객체를 이용할 때에는 적절한 인터페이스나 컴파일타임에 알 수 있는 상위클래스로 형변환해 사용해야 한다.
'JAVA > 이펙티브 자바' 카테고리의 다른 글
| 아이템[67]. 최적화는 신중히 하라 (0) | 2022.05.19 |
|---|---|
| 아이템[66]. 네이티브 메서드는 신중히 사용하라 (0) | 2022.05.14 |
| 아이템[64]. 객체는 인터페이스를 사용해 참조하라 (0) | 2022.05.14 |
| 아이템[63]. 문자열 연결은 느리니 주의하라 (0) | 2022.05.14 |
| 아이템[62]. 다른 타입이 적절하다면 문자열 사용을 피하라 (0) | 2022.05.14 |
- Total
- Today
- Yesterday
- Java
- AWS
- Algorithm
- 이팩티브 자바
- BAEKJOON
- MSA
- kkoon9
- 알고리즘
- Spring
- programmers
- Kotlin
- 프로그래머스
- BOJ
- C++
- 코테
- Spring Boot
- kotest
- 디자인 패턴
- 객체지향
- 백준
- node.js
- 클린 아키텍처
- JPA
- 정규표현식
- 이펙티브 자바
- 테라폼
- 디자인패턴
- Effective Java
- 클린 코드
- Olympiad
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |