인스턴스 메서드 vs 클래스 메서드
static 메서드는 언제 사용해야 하나?
평소에 static 메서드의 사용 여부에 대해 많이 고민이 되는 것 같다. 자동차 경주 게임 미션을 하면서도, 고민이 되는 경우가 있었다. 일단, 게임에 있어서 판정을 해주는 Referee 클래스 이다.
public class Referee {
private static final int MOVE_CRITERIA = 4;
public static boolean canCarMove(int randomValue) {
return randomValue >= MOVE_CRITERIA;
}
public static List<Car> judgeWinner(Set<Car> cars, int maxPosition) {
return cars.stream()
.filter(car -> car.isPosition(maxPosition))
.collect(Collectors.toList());
}
}
고민 끝에, Referee 클래스는 유틸리티 클래스로 설계하였다. 그때, 유틸리티 클래스 설계로 결정한 이유로는 여러가지가 있다.
- 여러 개의 객체가 필요가 없다는 점
- 인스턴스 필드가 없다는 점
이때만 해도, 인스턴스 필드가 인스턴스의 상태 정보를 나타내는지는 몰랐다. 인스턴스 필드를 상태 변수라고도 이야기 한다고 한다. 무조건적이진 않겠지만, 인스턴스화 할 클래스로 만들 것인지, 유틸리티 클래스로 만들 것인지 고민이 된다면, 인스턴스 필드의 여부를 생각해보면 될 것 같다.
다시 생각해봤을 때, Referee 클래스는 아무런 상태 변수도 없기 때문에 유틸리티 클래스로 만들어 주길 잘한 것 같다.
두번재는, 모든 로직에 대해서 조립을 해주는 Game 클래스다.
public class Game {
private static final String WINNER_NAME_DELIMITER = ", ";
private static final String NEGATIVE_ERROR_MESSAGE = "[ERROR] 음수를 입력할 수 없습니다";
private static final int ERROR_CRITERIA_VALUE_ZERO = 0;
private final Random random = new Random();
public void start() {
Cars cars = new Cars(CarFactory.of(InputView.inputCarNames()));
Set<Car> carSet = cars.getCars();
int count = validateGameCount(InputView.inputGameCount());
OutputView.printGameResultTitle();
for (int i = 0; i < count; i++) {
play(carSet);
showResult(carSet);
}
showWinner(Referee.judgeWinner(carSet, cars.getMaxPosition()));
}
public void play(Set<Car> cars) {
for (Car car : cars) {
boolean canCarMove = Referee.canCarMove(makeRandomValue());
car.move(canCarMove);
}
}
....
지금 다시 보니, 애매한 것 같다. 인스턴스 필드는 없는데, 메서드들은 다 인스턴스 메서드로 선언이 되어 있다. 모든 메서드를 static 메서드로 바꾸면, 유틸리티 클래스가 될 것 같은데,,,, 무언가 모든 메서드를 static 메서드를 만들기엔 부담스럽다는 생각이 든다.
그리고, 이 Game 클래스의 play()
는 상태를 Car들의 position값을 변하게 해준다,
즉, 객체의 상태를 변화시켜준다.
Car들을 관리해주는 Cars를 인스턴스 필드로 사용해서 리팩토링 해봐야겠다.
인스턴스 메서드
인스턴스의 상태를 변경하거나 상태 정보를 반환할 때 사용하는 메소드다. 인스턴스를 생성한 후, 메세지를 보낼 수 있다.
public void move(boolean canCarMove) {
if (canCarMove) {
position++;
}
}
클래스 메서드
인스턴스의 상태와는 관련이 없다. 인스턴스를 생성하지 않은 상태에서도 호출이 가능하다. 클래스 메서드는 유틸리티 메서드라고 부른다. static이 붙은 변수나 메서드들은 JVM이 시작될 때, static 영역에 저장되고 프로그램이 끝날 때까지 사라지지 않고 메모리에 남아있다.
public static List<Car> of(String names) {
String[] arrNames = names.split(",");
return Arrays.stream(arrNames)
.map(String::trim)
.map(Car::new)
.collect(Collectors.toList());
}
static 메서드의 장단점
속도가 빨라지고 반복적인 사용에 효율적이다.
메서드를 사용할 때, 객체를 생성해 줄 필요가 없으니 생성자를 호출할 필요가 없게 되고 속도가 빨라질 수밖에 없다.
객체 지향에서 멀어진다.
static 메서드는 객체의 생명 주기와 관계없이 프로그램 시작부터 끝날 때까지 메모리에 남아있기 때문에 객체 지향이라는 키워드와는 멀어진다. 객체 지향에서는, 객체들은 서로 관계를 맺고 메시지를 통해 정보를 교환하고 반환한다. 정적 메서드는 객체에게 행위를 지시하는 것이 아닌 함수 호출에 불과하다. 그리고 정적 메서드는 오버라이딩이 불가하므로 인터페이스에서는 사용할 수도 없게 된다. 정적 메서드는 런타임 이전 컴파일시에 정적 바인딩이 이루어지기 때문에, 동적 바인딩도 불가능하다.
메모리 효율이 떨어질 수 있다.
런타임 중 동적으로 생성된 것들은 가비지 컬렉션의 대상이 되는데, 정적으로 할당된 static 영역은 대상이 아니다. static으로 할당된 영역이 커지면 가비지 컬렉션의 효율이 떨어지게 된다. static 영역은 프로그램이 끝날 때까지 메모리에서 내릴 수 없게 되므로 효율이 떨어질 수 있다.
참고 https://tecoble.techcourse.co.kr/post/2020-07-16-static-method/