item 65 리프렉션보다는 인터페이스를 사용하라
핵심 정리
리플렉션은 복잡한 특수 시스템을 개발할 때 필요한 강력한 기능이지만, 단점도 많다.
컴파일 타임에는 알 수 없는 클래스를 사용하는 프로그램을 작성한다면 리플렉션을 사용해야 한다.
단, 되도록 객체 생성에만 사용하고, 생성한 객체를 이용할 때는 적절한 인터페이스나 컴파일타임에 알 수 있는 상위 클래스로 형변환해 사용해야 한다.
리플렉션
실행할떄마다 클래스에 대한 정보를 얻을 수 있다.
java의 고유한 기능이다.





리플랙션(java.lang.reflect)를 이용하면 컴파일 타임에 알 수 없는 임의의 클래스까지 접근을 할 수 있다.
생성자와 메서드를 가져올 수 있으며 맴버 이름, 필드 타입도 가져올 수 있다.
Class 객체가 주어지면 클래스 정보를 통해 아래와 같은 인스턴스를 가져올 수 있다.
- Constructor
- 생성자 시그니처를 가져올 수 있다.
- 생성자 인스턴스를 통해 객체를 생성할 수 있다.
- Method
- Method 시그니처를 가져올 수 있다.
- Method 인스턴스를 통해 Method를 실행시킬 수 있다. (Method.invoke)
- Field
- 필드타입, 멤버필드 이름등을 가져올 수 있다.
예를 들어 Method.invoke()를 이용해서 블러들인 메소드를 사용할 수 있다.

리플렉션의 단점
리플렉션을 이용하면 컴파일 당시에 존재하지 않던 클래스도 이용할 수 있다.
(예를들면.. 외부 라이브러리의 클래스를 리플렉션으로 인스턴스를 생성한다든지…)
컴파일타임 타입 검사가 주는 이점을 누릴 수 없다.
- 존재하지 않는 클래스, 메소드를 호출해도 컴파일타임 검사에서는 발견하지 못해 런타임 오류가 발생한다.
- 예외 검사, 컴파일 타임 에러를 잡아낼 수 없다.
- 프로그램이 리플렉션 기능을 써서 존재하지 않는 혹은 접근 불가능한 (private 메서드)를 호출하려 하면 런타임 오류가 발생한다.
리플렉션을 이용하면 코드가 지저분하고 장황해진다.
- 지루한 일이고 읽기도 어렵다.
성능이 떨어진다.
- 리플렉션을 이용한 메서드 호출은 일반 메서드 호출보다 훨씬 느리다.
- 매개변수가 없이 int를 반환하는 메소드도 리플렉션을 이용하면 11배나 느려졌다.
- 고려해야 하는 요소가 많아 정확한 차이는 이야기하기 어렵다
리플렉션은 아주 제한된 형태로만 사용해야 그 단점을 피할 수 있다
- 컴파일타임에 이용할 수 없는 클래스를 사용해야만 하는 프로그램은 비록 컴파일타임이라도 적절한 인터페이스나 상위 클래스를 이용할 수는 있을것이다.
- 리플렉션은 인스턴스 생성에만 쓰고 이렇게 만든 인터페이스나 상위 클래스로 참조해 사용하자
리플렉션의 취약한 예시를 통한 리플렉션의 두가지 단점
public static void main(String[] args) {
// 클래스 이름을 Class 객체로 변환
Class<? extends Set<String>> cl = null;
try {
cl = (Class<? extends Set<String>>) Class.forName(args[0]); //비검사 형변환
//forName()에 String 형태의 클래스 파일명을 넣어주면 해당하는 클래스를 반환해준다.
//단, 실제 클래스가 만들어진 것은 아니라서 메모리에 올라오지 않았다.
} 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();
//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);
//java.io.reflect가 제공해준 생성자로 인스턴스를 생성한 이후 부터는
//reflect의 기능을 쓰지 않고 인터페이스나 상위 클래스를 참조해서 사용한다.
}
private static void fatalError(String msg) {
System.err.println(msg);
System.exit(1);
}
런타임에 총 6가지의 예외를 던진다.
- 위에서 발생하는 예외는 모두 컴파일타임에 체크할 수 있는 예외들이다.
25줄 <<< 1줄
- 클래스 이름만으로 인스턴스를 생성해내기 위해 무려 25줄이나 되는 코드를 작성했지만, 그게 아닌 경우에는 생성자 1줄이면 끝난다.
- 리플렉션 예외를 각각 잡는 대신(위의 예외 6개).... 상위 클래스인 ReflectiveOperationException을 사용해 코드량을 줄일 수 있다. (ReflectiveOperationException은 Java 7부터 지원한다.)
리플렉션으로 생성자 생성시 비검사 형변환 경고가 뜬다.
클래스의 인스턴스를 생성하려고 할때 ClassCastException을 throw(던지게) 한다. 경고문을 숨기는 방법은 item 27.
리플렉션을 사용하기 적절한 상황
- 드물지만 리플렉션은 런타임에 존재하지 않을 수도 있는 다른 클래스, 메서드, 필드와의 의존성을 관리할 때 적합하다.
- 이 기법은 버전이 여러 개 존재하는 외부 패키지를 다룰 때 유용하다. 가동할 수 있는 최소한의 환경, 즉 주로 가장 오래된 버전만을 지원하도록 컴파일한 후, 이후 버전의 클래스와 메소드 등은 리플렉션으로 접근하는 방식이다.
- 이런 경우 접근하려는 새로운 클래스나 메서드가 런타임에 존재하지 않을 수 있다는 사실을 감안해서 대체 수단을 만들어 둬야한다.
리플렉션은 무조건 쓰지 말아야 한다?
- Spring MVC, Serialize/Deserialize, BeanUtils.copyProperties등 실무에서 사용하는 코드에 리플렉션이 적용된 예는 굉장히 많다.
- 단점이 많다고는 하지만 공통적인 기능을 설계하거나, 재사용 가능한 코드를 설계할 경우에는 오히려 리플렉션이 적합할 수 있다.
- 그렇기 때문에 Java 1.3 이후부터 리플렉션에 대한 성능향상을 발전시켜왔다고 한다.
- 이러한 발전으로 리플렉션은 우려할 만큼 성능이 떨어지지는 않는다고 한다.
- 리플렉션을 남발하는 것이 아닌 필요한 상황에 적시적소에 사용한다면 오히려 서비스 개발을 더 단순화 시킬수 있다.
참고
- Effective Java 3rd Edition - Item 65. 리플렉션보다는 인터페이스를 사용하라
- https://jaehun2841.github.io/2019/03/03/effective-java-item65/
- Java 리플렉션의 오해와 진실
'학습 기록 (Learning Logs) > 이펙티브 자바' 카테고리의 다른 글
[item 70] 복구할 수 있는 상황에서는 검사 예외, 프로그래밍 오류에는 런타임 예외를 사용하라 (0) | 2022.02.06 |
---|---|
[item 69] 예외는 진짜 예외 상황에만 사용하라 (0) | 2022.02.06 |
Item51~Item52 (0) | 2022.01.23 |
item 43~46 (0) | 2022.01.16 |
Item29~30 (0) | 2022.01.09 |