티스토리 뷰
5. 불필요한 객체는 만들지 말라
1) 기능적으로 동일한 객체는 필요할 때마다 만드는 것보다 재사용하는 것이 더 낫다.
- 변경 불가능한(immutable) 객체는 언제든지 재사용할 수 있다.
- 변경 불가능한 클래스(final class. 대표적으로 String, Math 등...) 의 경우, 생성자보다는 정적 팩토리 메서드를 이용하면 불필요한 객체 생성을 막을 수 있다.
메서드를 호출할 때마다 생성되는 객체가 많아 비용이 높다면 정적 초기화 블록(Static initializer)를 사용해보자
===============
* 필드를 초기화 하는데에는 3가지 방법이 있다
1. 명시적 초기화 (= 선언과 동시에 초기화하는 것)
2. 생성자 초기화
3. 초기화 블록을 통한 초기화
초기화 블록은 또 다시 인스턴스 초기화 블록과 클래스 초기화 블록으로 나뉜다.
인스턴스 초기화 블록은 중괄호 { } 를 이용해서 정의.
- 인스턴스가 생성될 때마다 실행된다 (생성자보다 먼저 실행된다!!)
- 생성자가 여러개 있을 경우, 모든 생성자에서 공통으로 수행되는 부분을 인스턴스 초기화 블록으로 분리시켜서 코드의 중복을 쪼금 막는데 사용될 수 있다.
클래스 초기화 블록은 인스턴스 초기화 블록 앞에 static을 붙여서 정의한다.
- 클래스가 처음으로 메모리에 로딩되는 시점에 딱 한번 실행된다.
-> 객체 생성 이전에 수행되므로 인스턴스 변수에 접근할 수 없음.
- 생성자로는 불가능한 클래스(static) 변수의 초기화를 진행할 때 사용할 수 있음 -> 명시적 초기화로는 복잡할 때 사용
(생성자에서 클래스 변수에 접근하는 것은 초기화가 아니라.. 그냥 기존 값을 변경하는 것)
* 클래스 변수는 객체 생성 없이도 사용 가능해야 하므로 생성자 초기화를 할 수 없다
(+ 내부에 인스턴스 변수/메서드도 사용할 수 없고, this 키워드도 사용 불가)
* 필드의 초기화 순서(우선순위. 저 --> 고)
클래스 변수: 기본값(0 or null) -> 명시적 초기화 -> 클래스 초기화 블록
인스턴스 변수: 기본값 -> 명시적 초기화 -> 인스턴스 초기화 블록 -> 생성자
public class User {
private static final Date BOOM_START;
private static final Date BOOM_END;
static { // 정적 초기화(클래스 초기화) 블록
Calendar cal = Calendar.getInstance(TimeZone.getDefault());
cal.set(1950, Calendar.JANUARY, 1, 0, 0, 0);
BOOM_START = cal.getTime();
cal.set(1955, Calendar.DECEMBER, 31, 24, 59, 59);
BOOM_END = cal.getTime();
}
...
}
===============
2) 생각지 못한, 의도치 않은 자동 객체화(AutoBoxing)가 발생하지 않도록 유의해라
- 딱히 이유가 없다면 primitive 타입을 사용하는 것이 속도나 메모리 면에서 좋다
-> primitive 타입 데이터는 스택 영역만 사용하는데 반해, Wrapper 타입 객체는 힙 + 스택 영역 둘 다 사용함
* AutoBoxing: primitive 타입 자료형을 Wrapper 클래스의 객체로 자동으로 바꿔주는 것
<-> Unboxing: Wrapper 클래스 타입을 primitive 타입으로 자동으로 바꿔주는 것
* 토막상식: Wrapper 클래스는 산술연산을 위해 정의된 클래스가 아니므로, 인스턴스에 저장된 값을 변경할 수 없다.
- 산술 연산이 되는 것은 사실 내부에서 Unboxing 후 primitive 타입으로 변환 및 연산하고 다시 Autoboxing 해주는 것.
- 값이 변경되는 것이 아니라, 새로운 객체를 만들어서 통으로 갈아끼워지는 것이다. (객체는 불변해야 해)
- "Wrapper 클래스끼리" == 비교하는 것은 내부의 값을 비교하는 것이 아니라 주소값을 비교하는 것.
-> 내부 값을 비교하기 위해서는 equals() 메서드를 이용해야 함.
// time = 9923ms
public static void main(String[] args) {
long start = System.currentTimeMillis();
Long sum = 0L;
for(long i = 0; i < Integer.MAX_VALUE; i++) {
sum += i; // AutoBoxing 발생 - 신규 객체 생성
}
long end = System.currentTimeMillis();
System.out.println("sum = " + sum);
System.out.println("time = " + (end - start) + "ms");
}
//////////////////////////////
// time = 1559ms
public static void main(String[] args) {
long start = System.currentTimeMillis();
long sum = 0L;
for(long i = 0; i < Integer.MAX_VALUE; i++) {
sum += i;
}
long end = System.currentTimeMillis();
System.out.println("sum = " + sum);
System.out.println("time = " + (end - start) + "ms");
}
꽤 성능차이가 유의미하게 발생한다
불필요한 객체를 만들지 마라고 해서 무조건 객체 생성을 피하라는 것은 아님. 알잘딱깔센
= "재사용이 가능하다면" 굳이 새로운 객체를 만들지 마라.
(근데 새로운 객체를 만들어야 한다면, 기존 객체는 재사용하지 마라. (추후 다룸))
'JAVA' 카테고리의 다른 글
종료자 사용을 피하자 (0) | 2022.07.17 |
---|---|
더 이상 사용되지 않는 객체는 제때 처리하자 (0) | 2022.07.17 |
객체 생성을 막을때는 private 생성자 (0) | 2022.07.15 |
싱글톤은 Enum 타입으로 만들어라 (0) | 2022.07.15 |
생성자 인자가 많을때는 빌더 패턴 (0) | 2022.07.15 |