기능적으로 동일한 객체를 매번 생성하는 것은 비효율적일 수 있다.
-> 객체 하나를 재사용 하는 것이 훨씬 적절한 방법이다. 특히, 불변 개체는 언제든지 재사용할 수 있다.
불변 객체의 대표적인 예로는 String, Integer, Boolean 등이 있다.
String bad = new String("bad"); // 따라 하지 말 것
String good = "good";
new로 생성한 코드는 실행될 때마다 String 인스턴스를 새로 만든다. 쓸데없는 String 인스턴스가 여러 개 만들어질 수 있다.
* 아래 코드는, 새로운 인스턴스를 매번 만드는 대신 String 인스턴스 하나를 재사용한다.
-> Java의 가상머신 안에서 똑같은 문자열 리터럴에 대해서 동일 코드, 같은 객체를 사용하는 재사용성이 보장된다는 것이다.
* 또한, 정적 팩토리 메서드를 제공하는 불변 클래스에서는 정적 팩터리 메서드를 사용해 불필요한 객체 생성을 피할 수 있다.
생성자 : 호출할 때마다 매번 새로운 객체를 만든다.
팩토리 메서드 : 사용 중 변경되지 않을 것이란 걸 안다면, 언제나 재사용 가능하다.
* 생성 비용이 비싼 객체는, 캐싱 해서 재사용하자.
-> 생성 비용이 비싼 경우는 어떤 경우가 있을까?
- 메모리, 디스크 등 시스템 자원이 많이 필요할 때
- 데이터 크기가 클 때
- 객체 내부에 여러 객체가 포함되었을 때 등등
ex) 정규 표현식에 match가 되는지 확인하는 방법인 String.matches 메서드
- 가장 쉬운 방법이긴 하지만, 내부적으로 객체를 만들어 쓰기 위해서는 유한 상태 기계로 컴파일을 해서 사용해야 한다.
( 유한 상태 기계 설명 - 유한 상태 기계는 유한한 개수의 상태를 가질 수 있는 오토마타, 즉 추상 기계라고 할 수 있다. 이러한 기계는 한 번에 오로지 하나의 상태만을 가지게 되며, 현재 상태(Current State)란 임의의 주어진 시간의 상태를 칭한다. 이러한 기계는 어떠한 사건(Event)에 의해 한 상태에서 다른 상태로 변화할 수 있으며, 이를 전이(Transition)이라 한다. )
- 따라서, Pattern 객체를 만들어 재사용하는 것이 좋다.
//계속 생성하지 말고,
static boolean isRomanNumeral(String s) {
return s.matches("^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
}
public static class RomanNumber {
//재사용하자!
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 isRomanNumeral(String s) {
return ROMAN.matcher(s).matches();
}
}
* 불변 객체를 재사용할 때, 안정하게 재사용되는 것이 분명하지 않은 경우
-> 어댑터
- 어댑터란, 인터페이스를 통해 뒷단 객체로 연결해주는 객체이다.
- 뒷단 객체만 관리하는 역할을 담당한다.
- 따라서, 뒷단 객체 하나 당 어댑터 하나만 만들면 충분하다.
- ex) Map 인터페이스의 keySet 메서드.
- 결국, Map 뒷단에 있는 Set 인터페이스의 뷰를 제공하기 때문에, keySet 호출 시마다 같은 객체를 리턴한다.
-> 오토박싱
- 프로그래머가 기본 타입과 boxing된 기본 타입을 섞어 쓸 때(primitive : int <-> boxing : Integer), 자동으로 변환해주는 것. (boxing, unboxing)
- 기본 type과 boxing된 기본 type의 경계를 흐리게 만든다. (없애주진 않는다.)
- 결론 : 불필요한 boxing type의 사용은 성능을 저하시킬 수 있다.
객체 생성은 비싸니, 언제나 피하는 것이 답일까?
-> NO !
- 프로그램의 명확성, 간결성, 기능을 위해 객체를 생성하는 것은 일반적으로 좋을 수 있다.
- 특히, value object로써 객체 자체를 값으로 사용하는 방식은 thread safe한 방법으로, multi thread 환경에서 안전하다.
- 단순히 객체 생성을 피하고자 만드는 객체 pool은 코드의 가독성과 메모리 효율성을 떨어트릴 수 있다.
- 또한, 방어적 복사가 필요한 상황의 경우에도 객체를 재사용하면 보안에 문제를 줄 수 있다.
(방어적 복사란 ? - 생성자의 인자로 받은 객체의 복사본을 만들어 내부 필드를 초기화하거나, getter메서드에서 내부의 객체를 반환할 때, 객체의 복사본을 만들어 반환하는 것이다. 생성자의 인자로 객체를 받았을 때 외부에서 넘겨줬던 객체를 변경해도 내부의 객체는 변하지 않아야 한다. 이럴 때 방어적 복사를 사용한다.)
'JAVA > Effective Java' 카테고리의 다른 글
item 8. finalizer과 cleaner 사용을 피하라. (0) | 2022.04.16 |
---|---|
item7. 다 쓴 객체 참조를 해제하라 (0) | 2022.04.16 |
item 5. 자원을 직접 명시하지 말고 '의존 객체 주입'을 사용하라. (0) | 2022.04.12 |
item 4. 인스턴스화를 막으려면 private 생성자를 사용하라. (0) | 2022.04.12 |
item 3. private 생성자나 열거 타입으로 싱글턴임을 보증하라. (0) | 2022.04.09 |
댓글