728x90
반응형
아이템 6 불필요한 객체 생성 피하라
똑같은 기능의 객체 매번 생성 < 객체 하나 재사용이 효율적
-> 빠르고 세련됨/ 특히, 불변 객체는 언제든 재사용 O
불필요한 객체 생성 예시
1) 하지 말아야 할 극단적인 예
String s = new String("asdf");
- `new String("asdf")`: 실행될 때마다 새로운 `String` 인스턴스 생성
- 생성자에 넘겨진 `asdf` 자체가 이미 동일한 `String` 객체
- 반복문 or 빈번히 호출되는 메서드에서 사용 -> 수백만 개의 불필요한 객체 생길 수 O
개선 버전
String s = "asdf";
- 새로운 인스턴스 매번 만드는 대신 하나의 `String` 리터럴 재사용
- JVM) 동일한 문자열 리터럴 사용하는 모든 코드가 같은 객체 공유하도록 보장
2) 정적 팩터리 메서드 사용
- 생성자) 호출 시 항상 새로운 객체 생성
- 불변 클래스에서는 정적 팩터리 메서드 ~> 불필요한 객체 생성 피할 수 O
// 나쁜 예
Boolean b = new Boolean("true");
// 좋은 예
Boolean b = Boolean.valueOf("true");
3) 비싼 객체 재사용 (정규표현식 Pattern)
ex. 주어진 문자열이 유효한 로마 숫자인지 확인하는 메서드
static boolean isRomanNumeral(String s) {
return s.mathces("^(?=.)M*(C[MD]|D?C{0,3})"
+ "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
}
- `String.matchers()`: 내부에서 매번 새로운 `Pattern` 인스턴스 생성
- `Pattern`: 정규표현식을 기반으로 유한 상태 머신(FSM) 생성 -> 비용 ↑
- 한 번 쓰고 버려져 GC 대상이 됨
개선 버전
public class RomanNumerals {
private static final Pattern ROMAN = Pattern.complie(
"^(?=.)M*(C[MD]|D?C{0,3})"
+ "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
static boolean isRomanNumeral(String s) {
return ROMAN.matcher(s).matches();
}
}
- `static final` 필드로 `Pattern` 캐싱 -> 약 6.5배 성능 향상
- 코드 가독성 ↑
단`isRomanNumeral`이 한 번도 호출 X -> `ROMAN` 불필요하게 초기화됨
=> 지연 초기화(lazy initialization)로 해결할 수 있음 But, 코드 복잡도 ↑, 성능 개선 효과 미미 => 권장 X
4) 어댑터(뷰) 객체 재사용
- 불변 객체는 재사용해도 안전
- 어댑터(뷰)처럼 상태 X 객체도 재사용하는 편이 남
- 어댑터) 실제 작업 -> 뒷단 객체에 위임, 자신은 제2의 인터페이스 역할을 해주는 객체
- -> 뒷단 객체만 관리하면 됨 = 뒷단 객체 외에 관리할 상태 X
- => 뒷단 객체 하나당 어댑터 하나씩만 만들어지면 충분
- ex. `Map.keySet()`
- `keySet()` 호출 시마다 새로운 `Set` 인스턴스 만든다고 생각할 수 있음
But, 사실 동일한 `Set` 뷰 객체 반환할 수도 있음 - 반환된 뷰 객체는 모두 원본 `Map`의 동일한 상태 공유
- 반환된 객체 중 1개 수정 => 다른 모든 객체 변경
- => 굳이 매번 새로운 뷰를 만들 이유 X
- `keySet()` 호출 시마다 새로운 `Set` 인스턴스 만든다고 생각할 수 있음
5) 오토박싱(auto boxing) 문제
오토박싱: 프로그래머가 기본 타입과 박싱된 기본 타입을 섞어 쓸 때 자동으로 상호 변환해주는 기술
기본 타입과 그에 대응하는 박싱된 기본 타입의 구분 흐려줌 But, 완전히 없애지 X => 성능 차이 O
private static long sum() {
Long sum = 0L; // 박싱 타입 Long
for (long i = 0; i <= Integer.MAX_VALUE; i++)
sum += i; // 오토박싱 발생
return sum;
}
- `sum` 변수를 `Long`으로 선언 -> 불필요한 박싱/언박싱 발생
- `Long` 인스턴스 약 $2^{31}$개 생성
기본 타입이 박싱 타입보다 항상 빠르고 메모리 효율적 => 의도치 X 오토박싱 숨어들지 않게 주의
객체 풀(Pool) 남용 금지
- 객체 생성은 비싸니 피해야 한다 (X)
- 현대 JVM) 작은 객체의 생성 및 회수 가볍고 효율적
- 프로그램의 명확성, 간결성, 기능 -> 필요한 객체는 그냥 생성하는 편이 남
- 객체 풀 사용
- (권장) 데이터베이스 연결처럼 생성 비용 매우 ↑ 경우(아주 무거운 객체)만
- (-) 일반적인 가벼운 객체 대해 직접 풀 생성
- => 코드 복잡성 ↑, 메모리 낭비, 성능 ↓ (JVM의 GC가 더 빠름)
방어적 복사 vs 재사용
- 방어적 복사가 필요한 상황: 객체 재사용 피해 > 반복 생성 피해
- 객체 재사용 억지로 시도 -> 보안 취약점 or 버그 발생 O
- 불필요한 객체 생성 -> 코드 형태 or 성능에만 영향
- => 객체 재사용 < 안전성
728x90
반응형