우테코 Lv1. 마지막인 체스 미션을 진행하면서 자바 리플렉션을 사용했었다.
초기 체스판 기물 배치를 하는데 있어 비숍, 룩, 나이트 등 각 기물의 클래스 객체를 통해
인스턴스를 생성하는 과정에서 리플렉션을 얄팍하게 접했다.
private Piece createPieceInstance(Class<? extends Piece> clazz, Team team) {
try {
return clazz.getDeclaredConstructor(Team.class) // 생성자를 불러와 인스턴스 생성
.newInstance(team);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("[INTERNAL ERROR] 기물을 생성하여 배치하는 리플렉션에서 오류가 발생했습니다 ");
}
}
리플렉션 코드가 정말 멋있다 생각했다. 그렇기에 자부심도 가졌다.
그러나, 리뷰어 찰리로부터 이런 리뷰가 왔다
리플렉션을 사용하셨군요?
1. 어떤 배경에서 리플렉션이 필요하셨을까요?
2. 리플렉션은 객체지향적인 코드일까요?
결심했다. 활용에 그치지 않고 리플렉션에 대해 조금 더 자세히 이해해보기로
리플렉션이란?

구체적인 클래스 타입을 몰라도, 메소드, 필드에 접근 가능하도록 해주는 자바 API
클래스 객체를 통해 클래스 정보를 분석하여 런타임에 클래스 동작을 검사하거나 조작함
사용하는 경우
- 런타임에 지금 실행되는 클래스를 가져와서 실행해야 하는 경우
- 동적으로 객체를 생성/메서드를 호출해야 하는 경우
사용 예시
brocoli라는 클래스 파일을 통해 실험해보자
public final class Brocoli {
String color;
public Brocoli(String color) {
this.color = color;
}
public Brocoli() {
this("green");
}
public void eat() {
System.out.println("초장에 찍으면 더 맛있어요");
System.out.println("아삭아삭");
}
}
ex1) 생성자 찾기
Class clazz = Class.forName("Brocoli"); //브로콜리라는 이름의 클래스를 가져옴
Constructor constructor1 = clazz.getDeclaredConstructor(); // 인자가 없는 생성자
Constructor constructor2 = clazz.getDeclaredConstructor(String.class); // String을 인자로 받는 생성자
- getDeclaredConstructor() >> 인자가 없는 생성자
- getDeclaredConstructor(String.class) > > String 인자를 받는 생성자
ex2) 객체 생성
Brocoli coli1 = (Brocoli) constructor1.newInstance();
Brocoli coli2 = (Brocoli) constructor2.newInstance("blue");
생성자로부터 객체 생성이 가능하다
이는 런타임에 결정된 클래스를 기반으로 리플렉션을 활용하면 인스턴화를 할 수 있음을 의미한다
ex3) method 조작
//메서드 찾기
Class clazz = Brocoli.class; //브로콜리라는 이름의 클래스를 가져옴
Method[] methods = clazz.getDeclaredMethods(); // 브로콜리 클래스의 메서드들을 불러옴 ['eat' ...]
System.out.println(methods[0].invoke(clazz.newInstance())); // 객체를 만들어 method 호출
- getDeclaredMethods() == 클래스에 선언된 메서드를 순서대로 배열에 담음
- invoke 메서드 == 메서드 실행
ex4) Field 조작
// 필드 변경
Brocoli coli = new Brocoli(); //브로콜리라는 이름의 클래스를 가져옴
Class clazz = Brocoli.class;
Field[] fields = clazz.getDeclaredFields(); // 브로콜리 클래스의 필드들을 불러옴 ['color' ...]
fields[0].set(coli, "blue"); //coli 인스턴스의 color 필드를 'blue'로 바꿈
System.out.println(fields[0].get(coli)); // blue가 출력됨
- getDeclaredFields :선언된 필드들은 배열에 담아 반환
- field.set(인스턴스, value) :인스턴스의 field를 value로 바꿈
- field.get(인스턴스) : 인스턴스의 field를 가져옴
- setter가 없어도 set이 가능!
- getXXX() vs getDeclaredXXX()
class 객체 메소드에는 getXXX와 getDeclaredXXX와 같은 메소드가 양분되어 있다
getXXX : getFields(), getMethods(), getAnootations()
getDeclaredXXX : getDeclaredFields() , getDeclaredMethods()...
이 둘의 차이는 무엇일까?
getXXX : 해당 클래스와 상위 클래스의 public 요소만
getDeclaredXXX : 해당 클래스에 정의된 모든 요소(private, protected, public)
리플렉션의 단점
- 단점1. 안좋은 성틍 by 런타임 분석
일반적인 메소드 호출 : 컴파일 시점에 분석된 클래스
리플렉션 : 런타임에 클래스를 분석하여 메서드 호출
- 단점2. 컴파일 타임 체크가 불가하다 by 런타임 분석
예를 들어 브로콜리 class에서 'eat' method를 가져오는 상황을 생각해보자

오타로 eat method가 아닌 ear를 쳤다면 기존에는 컴파일 타임에 객체에 ear 메서드가 없음을 알려준다.
그러나, 리플렉션은 런타임에 클래스 정보를 분석해 "ear" method의 존재 여부를 아직 알지 못해 오류를 발생시킬 수 없다.
이는 좋은 오류의 원칙인 [최대한 빠르게, 그리고 요란하게 발생해라]에 위반된다
- 단점3. 객체의 캡슐화가 깨진다
Field.setAccessible() 메서드를 사용하면 private 멤버 변수, 심지어 private final에도 접근이 가능할 수 있다
이는 객체 지향 원칙에 혼란을 야기할 수 있는데, setter/getter가 없더라도 외부에서 객체에 직접 접근이 가능한 것을 의미한다
reference)
'우테코 > Level1' 카테고리의 다른 글
상속과 조합 : is-a / has-a (8) | 2024.03.18 |
---|---|
[스크랩] 풀링 / 캐싱 (0) | 2024.03.14 |
자바에서 Stack보다 Deque이 권장되는 이유 (0) | 2024.03.10 |
.collect(Collectors.toList()) vs Stream.toList() (0) | 2024.03.04 |
[TDD] JUnit - @ParameterizedTest 공식문서 정리하기 (1) | 2024.02.28 |