똑같은 기능의 객체를 매번 생성하기보다는 객체 하나를 재사용하는 편이 나을 때가 많다.
간단한 예로
String s1 = new String(" aaa " ) ;
String s2 = " aaa " ;
위 문장은 실행될 때 마다 String 인스턴스를 새로 만든다.
이 문장을 반복문이나 자주 호출되는 메서드 안에 넣으면 s1을 사용할 때 마다 " aaa " 라는 String 인스턴스가 계속 생성된다.
아래 문장은 하나의 String 인스턴스를 사용한다.
생성자 대신 정적 팩터리 메서드를 제공하는 불변 클래스에서는 싱글턴을 보장해 불필요한 객체 생성을 피할 수 있다.
public class RomanNumerals {
// 코드 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})$");
}
// 코드 6-2 값비싼 객체를 재사용해 성능을 개선한다.
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();
}
public static void main(String[] args) {
boolean result = false;
long start = System.nanoTime();
for (int j = 0; j < 100; j++) {
//TODO 성능 차이를 확인하려면 xxxSlow 메서드를 xxxFast 메서드로 바꿔 실행해보자.
result = isRomanNumeralSlow("MCMLXXVI");
}
long end = System.nanoTime();
System.out.println(end - start);
System.out.println(result);
}
}
isRomanNumeralSlow 메서드의 문제는 String.matches를 사용한다는 데 있다.
String.matches는 정규표현식으로 문자열을 확인하는 가장 쉬운 방법이지만,
성능이 중요한 상황에서 반복적으로 사용해야 한다는 적합하지 않다.
이 메서드가 내부에서 만드는 Pattern 인스턴스는 한 번 쓰고 버려진다.
성능을 개선하려면 정적 객체를 생성해 Pattern 인스턴스를 생성해서 캐싱해두고 나중에 isRomanNumeralFast 메서드가 호출 될 때
인스턴스를 재사용하면 된다.
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})$");
위 코드에서 이렇게 사용한다면 성능을 상당히 끌어올릴 수 있다.
하지만 이 방식이 초기화 된 후 메서드를 한 번도 호출하지 않는다면 ROMAN 필드는 필요없이 초기화 된 꼴이다.
지연초기화를 이용하면 불필요한 초기화를 없앨 수 있지만, 코드를 복잡하게 만들고 성능은 크게 개선되지 않을 때가 많기 때문에 권하지 않는다.
불필요한 객체를 만들어내는 예로 오토박싱을 들 수 있다.
// 코드 6-3 끔찍이 느리다! 객체가 만들어지는 위치를 찾았는가? (34쪽)
public class Sum {
private static long sum() {
Long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++)
sum += i;
return sum;
}
public static void main(String[] args) {
int numSets = Integer.parseInt(args[0]);
long x = 0;
for (int i = 0; i < numSets; i++) {
long start = System.nanoTime();
x += sum();
long end = System.nanoTime();
System.out.println((end - start) / 1_000_000. + " ms.");
}
// VM이 최적화하지 못하게 막는 코드
if (x == 42)
System.out.println();
}
}
sum 변수를 Long으로 선언해서 long타입인 i가 더해질 때 마다 불필요한 Long 인스턴스가 생성된다.
단순히 sum의 타입을 long으로만 바꿔주면 책의 기록에선 6.3초에서 0.59초로 빨라진다.
교훈은 명확하다. 박싱된 기본 타입보다는 기본타입을 사용하고 의도치않은 오토박싱이 숨어들지 않도록 주의하자.
내 경험에서도 무거운 객체인 데이터베이스 연결을 CRUD가 일어날때마다 생성해서 속도가 매우 느려졌던 적이 있다.
항상 코드를 짤 때 불필요한 생성을 피하고 재사용 할 수 있도록 주의해야 겠다.
'JAVA > 이펙티브 자바' 카테고리의 다른 글
8. finalizer와 cleaner 사용을 피하라 (0) | 2023.06.20 |
---|---|
7. 다 쓴 객체 참조를 해제하라 (0) | 2023.06.19 |
5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (0) | 2023.06.11 |
4. 인스턴스화를 막으려면 private 생성자를 사용하라 (0) | 2023.06.10 |
3. private 생성자나 열거타입으로 싱글턴을 보증하라 (0) | 2023.06.10 |