2020. 4. 22. 16:40ㆍTIL/자바
객체지향 프로그래밍 스터디를 진행하던 중, 콘솔로 진행하는 간단한 게임을 구현해보았었다. (레이싱게임)
객체지향과 관련된 책을 읽고있지만, 역시 직접 개발을 하는것은 또 다른 문제였다.
열심히 작성하고 피드백을 받았는데, 그 내용이 너무 중요하고 기본적인 것이라 정리를 하게 되었다.
1. 배열보다 Java Collection을 사용하라.
처음 이 피드백을 보았을 때는, List가 배열보다 사이즈 조절에서 아주 강점이 있기 때문이라고 생각했다.
즉, 삽입과 삭제가 배열에 비해 매우 쉽기 때문이라고 생각했다.
뭐 틀린 것은 아니겠지만 구글링을 해본 결과 생각해본 적도 없는 내용을 발견하였다.
'이펙티브 자바 규칙 25 - 배열대신 리스트를 사용하라' 가 바로 그 내용이다.
배움이 얕은 나로서는 내용이 좀 어려웠는데, 내가 느낀 결론은 타입에 관련된 에러를 런타임 단에서가 아닌 컴파일 단에서 발견해준다는 것이다.
이것은 배열은 실체화 가능한 자료형이고, 리스트는 실제 실행때는 타입을 삭제하는 제네릭 자료형이기 때문이다.
즉 데이터의 타입이 배열은 실제 실행시에 정해지게 되는 반면, 리스트는 컴파일시에 타입을 확인후 실제 프로그램 실행때는 타입에 대한 정보를 삭제한다.
어려운 내용이지만, 그 차이점과 필요성에 대한 이해는 갔다.
만일 배열과 리스트를 혼용해서 사용하다가 컴파일 에러를 마주할 경우, 본능적으로 배열을 리스트로 바꾸어 줘야 하겠다.
2. private 생성자를 통해 인스턴스화 되는것을 막아라.
나는 레이싱게임을 구현하는데 있어서, 유틸의 성격을 지닌 domain 클래스들을 구현하였다.
이 클래스들은 상태값이 없을 뿐더러, 단지 넘어온 데이터를 조작하여 넘겨주는 역할만을 하는 클래스들이었다.
이러한 클래스들의 메소드를 사용하기 위해서, 굳이 객체를 생성하는것은 메모리 낭비이자 시간낭비 이다.
package domain;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
public class WinnerProgress {
private WinnerProgress() {
throw new AssertionError();
}
public static int calculateWinnerPosition(Car[] cars) {
List<Integer> carsPosition = new LinkedList<>();
for (Car car : cars) {
carsPosition.add(car.getPosition());
}
Collections.sort(carsPosition);
return carsPosition.get(carsPosition.size() - 1);
}
public static List<String> extractWinners(Car[] cars, int winnerPosition) {
List<String> winners = new LinkedList<>();
for (Car car : cars) {
if (car.isWinner(winnerPosition)) {
winners.add(car.getName());
}
}
return winners;
}
}
위의 클래스는 따로 상태값이 없으며 모든 메소드가 클래스 메소드인 예시이다.
위 클래스의 calculateWinnerPosition 메소드나 extractWinners 메소드를 사용하기 위해서 외부에서 인스턴스를 생성할 이유는 전혀 없다. 단지, 올바른 매개변수를 담아 호출해주면 되는 것이다.
이러한 클래스의 무의미한 인스턴스화를 막기 위해서는 생성자를 private으로 선언해 주어야 한다.
이렇게 하면 이 클래스를 보는 타인들도 '이 클래스는 인스턴스화 해서 사용할 필요가 없구나' 라는 것을 쉽게 알 수 있다.
대표적으로 자바의 Math클래스를 생각하면 쉽게 이해할 수 있을 것이다.
3. static
2번에서 static변수와 static메소드만 있는경우 인스턴스화할 필요가 없어서 private 생성자를 만들어 준다는 것을 알았다.
이참에 static에 관한 정리도 하면 좋을 것 같아서 공부해보았다.
static은 위에 언급한 것 처럼 변수와 메소드에 키워드로 사용될 수 있다.
static은 왜 사용 되는걸까?
먼저 변수의 관점에서 생각해 보았다.
static변수는 클래스가 메모리에 올라갈 때 자동으로 생성됨으로서, 인스턴스 생성없이 바로 사용이 가능하다.
그러므로, 접근이 편리하고 속도도 빠르다.
static변수는 '전역변수'와 같은 개념을 통해 접근하는 것이 이해가 쉬운데, 프로그램 내에서 공통으로 사용되기 때문이다.
package 낙서장;
import java.awt.Frame;
import java.util.*;
public class Practice {
static String name = "졸리다";
public static void main(String[] args) {
Practice one = new Practice();
Practice two = new Practice();
System.out.println("one : " + one.name + " two : " + two.name);
// one : 졸리다 two : 졸리다
one.name = "안졸리다";
System.out.println("one : " + one.name + " two : " + two.name);
// one : 안졸리다 two : 안졸리다
}
}
주석처리를 해놓은 것 처럼 one의 name만을 변경했는데 결과적으로 two의 name도 변경되는 것을 알 수 있다.
이것은 static으로 변수를 선언하면 공용으로 사용됨을 보여준다.
이러한 이유로 static을 사용하기 위해서는 그 값이 변경될 일이 매우 적은 것에 사용해야한다.
(그것도 주의해서 사용해야겠다.)
이와 반대로 인스턴스 변수는 생성되는 인스턴스마다 그 고유의 값을 가지는 변수이기 때문에,
각 객체마다 같은값을 가질지, 고유의 값을 가질지에 따라 static변수로 선언할지 말지를 결정하면 되겠다.
그렇다면 static메소드가 의미하는 것은 무엇일까?
인스턴스 메소드가 객체를 생성하여 호출해야 하는 것과는 달리
static메소드는 클래스명.메소드명()을 통해 호출할 수 있다. 이것은 3번에서도 말했듯이 호출시간이 짧아지기 때문에 효율이 높아진다.
따라서, 메소드에서 인스턴스 변수를 사용할 일이 없다면 메소드를 static으로 선언하는 것이 좋겠다.
4. final
final은 static과 함께 보이는 경우가 종종 있다.
final 키워드에 대해 위키피디아에 검색을 해본 결과,
'final은 해당 entity가 오로지 한 번 할당될 수 있음을 의미합니다.' 라고 되어있다.
즉, 변수로 치면 한번 초기화가 된 후 값이 변하지 않게 된다는 것이다.
그렇다고 final이 상수라는 의미는 아니다.
private final int number;
public numberConstructor(int number) {
this.number = number;
}
위와 같은 코드는 생성자를 통해 number값을 초기화 해주고 있다.
하지만 인스턴스를 생성할 때 마다 그 값은 고유의 number값을 갖게 된다.
따라서 static으로 선언하는 것과는 차이가 있다.
위의 static과 final을 합쳐서 사용할지, 하나만 선택하여 사용할지, 둘다 사용하지 않을지를 잘 고려하는것이 좋겠다.
'TIL > 자바' 카테고리의 다른 글
[자바] 로또 게임 구현 (0) | 2020.05.18 |
---|---|
[자바] Java와 JVM (0) | 2020.05.06 |
[자바] getter메소드 사용을 지양하자 (0) | 2020.04.25 |
[테스트] JUnit 과 AssertJ (0) | 2020.04.23 |
[자바] 코드 컨벤션 (0) | 2020.04.06 |