똑같은 기능의 객체를 매번 생성하지 말고 객체 하나를 재사용하는게 나을 때가 많다.
String s = new String("bikini"); // 안좋은 코드
이 문장은 실행될 때마다 String 인스턴스를 새로 만든다. 생성자에 넘겨진 매개변수와 생성자가 만들어내는 것이 기능적으로 완전히 똑같다. 이 문장이 반복문이나 빈번히 호출되는 메서드 안에 있다면 String 인스턴스가 수백만 개 만들어질 수도 있다.
String s = "bikini";
이 코드는 새로운 인스턴스를 매번 만드는 대신 하나의 String 인스턴스를 사용한다.
생성자 대신 정적 팩터리 메서드를 사용하면 불필요한 객체 생성을 피할 수 있다.
생성 비용이 아주 비싼 객체도 있다. 이런 비싼 객체가 반복해서 필요하다면 캐싱해서 재사용하라.
아래는 주어진 문자열이 유효한 로마숫자인지 확인하는 메서드다.
// 코드 6-1 성능을 훨씬 더 끌어올릴 수 있다!
static boolean isRomanNumeralSlow(String s) {
return s.matches("^(?=.)M*(C[MD]|D?C{0,3})"
+ "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
}
String.matches는 정규표현식으로 문자열 형태를 확인하는 쉬운 방법이지만, 성능이 중요한 상황에 반복하기는 부적합하다.. 이 메서드가 내부에서 만드는 Pattern 인스턴스는 한 번 쓰고 버려져서 가비지 컬렉션 대상이 된다. Pattern은 입력 받은 정규표현식에 해당하는 유한 상태 머신(finite state machine) (?)을 만들기 때문에 인스턴스 생성 비용이 높다.
유한 상태 머신 : 유한 개의 상태를 가지고 입력에 따라 다른 상태로 전환시키거나 액션이 일어나게 하는 모델.
java
라는 문자열을 포함하고 있는지 찾기 위해
1. 아무것도 못찾은 상태
2. 'j'라는 문자를 보고 'j'를 본 상태로 전이
3. 'a'를 보면 'ja'를 본 상태로 전이. 하지만 'a'가 아니면 의미 없으니 아무것도 못 본 상태
4. 모두 탐색 때까지 반복수행
성능을 개선하려면 캐싱해두고 인스턴스를 재사용하면 된다.
private static final Pattern ROMAN = Pattern.compile(
"^(?=.)M*(C[MD]|D?C{0,3})"
+ "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
static boolean isRomanNumeralFast(String s) {
return ROMAN.matcher(s).matches();
}
불필요한 객체를 만들어내는 또 다른 예로 오토박싱(auto boxing)을 들 수 있다. 오토박싱은 프로그래머가 기본 타입과 박싱된 기본 타입을 섞어 쓸 때 자동으로 상호 변환해주는 기술이다.
private static long sum() {
Long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++)
sum += i;
return sum;
}
long 타입인 i가 Long타입인 sum에 더해질 때마다 인스턴스가 생성된다. 박싱된 기본 타입보다는 기본 타입을 사용하고, 의도치 않은 오토박싱이 숨어들지 않도록 주의하자.
하지만 오해해서 객체 생성은 비싸니 피해야 한다 혹은, 무겁지도 않은데 객체 생성을 피하고자 객체 풀을 만들지는 말자. 요즘 JVM의 가비지 컬렉터는 잘 최적화되어 있다.