본문 바로가기

Effective Java

[Effective Java] 아이템 6 - 불필요한 객체 생성을 피하라

똑같은 기능의 객체 매번 생성하기보다는 객체 하나를 재사용하는 것이 낫다.

 

String s = new String("string");

 

이 문장은 실행 될 때마다 String 인스턴스를 새로 생성 - "string" 자체가 생성자로 만들어내려는 String과 기능적으로 동일

-> 계속 호출되는 메서드 안에 존재할 경우 쓸데없는 String 인스턴스가 계속 생성될 수 있다.

 

개선된 방법

String s = "string";

이 코드는 새로운 인스턴스를 매번 만드는 대신 하나의 String 인스턴스를 사용

-> 가상 머신 안에서 똑같은 문자열 리터럴을 사용하는 모든 코드가 같은 객체를 재사용함이 보장된다.

 

+ 생성자 대신 정적 팩터리 메서드(아이템 1)를 사용하는 것도 불필요한 객체 생성을 피하는 하나의 방법

ex) Boolean(String) 생성자 대신 Boolean.valueOf(String) 팩터리 메서드 사용

 

 

 

만약, 생성 비용이 아주 비싼 객체가 반복해서 필요하다면 캐싱하여 재사용하는 것이 좋다.

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 메서드를 반복하는 가장 쉬운 방법  "but" 성능이 중요한 상황에서는 적합하지 x
- 이 메서드가 내부에서 만드는 정규 표현식용 Pattern인스턴스는 한 번 쓰고 버려저서 곧바로 가비지 컬렉션 대상이 된다.
     (정규 표현식을 표현하는 Pattern 클래스는 생성비용이 높은 클래스중 하나)

해결 방법 : Pattern 인스턴스를 클래스 초기화(정적 초기화) 과정에서 직접 생성해 캐싱을 한다. & 나중에 isRomanNumeralFast 메서드가 호출 될 때마다 이 인스턴스를 재사용

static final을 이용해 초기에 캐싱 
public class RomanNumerals { 
	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(); 
    } 
}

불변 객체의 경우 재사용해도 안전하다. 하지만 어댑터의 경우는 해당이 되지 않는다. 어댑터는 인터페이스를 통해 뒤에 있는 객체로 연결해주는 view라 여러 개 만들 필요x

(어댑터는 실제 작업은 뒷단 객체에 위임하고 자신은 제2의 인터페이스 역할을 해주는 객체)

 

Map<String, Object> map = new HashMap<>();
map.put("Effective", "Java");

Set<String> set1 = map.keySet();
Set<String> set2 = map.keySet();

assertThat(set1).isSameAs(set2); // TRUE

set1.remove("Effective");
System.out.println(set1.size()); // 0을 출력
System.out.println(set2.size()); // 0을 출력

Map 인터페이스의 keySet 메서드는 Map 객체 안의 키 전부를 담은 Set 인터페이스의 뷰를 반환
"but" 동일한 Map에서 호출하는 keySet 메서드는 같은 Map을 대변하기 때문에 반환한 객체 중 하나를 수정하면 다른 모든 객체가 따라서 바뀐다.
따라서, keySet이 뷰 객체 여러 개를 만들 필요도 없고 이득도 없게 된다.

 

박싱된 기본 타입보다는 기본 타입을 사용하고, 의도치 않은 오토박싱을 하지 말아야 한다.

 

private stating long sum(){
	Long sum = 0L; // 오토박싱
    for(long i = 0; i <= Integer.MAX_VALUE; i++)
    	sum += i;
        
    return sum;
}

위 코드는 sum을 오토박싱 함으로써 불필요한 객체를 Integer.MAX_VALUE만큼 생성하게 된다. 

따라서 불필요한 Long 인스턴스를 만들지 않으려면, long타입을 선언하면 된다.

+ 아주 무거운 객체가 아닌 단순한 객체 생성을 피하고자 객체 풀을 만들지 않는 것이 좋다.

-> 왜냐하면, 요즘 JVM의 가비지 컬렉터는 상당히 최적화( 가벼운 객체용을 다룰 때 직접 만든 객체 풀보다 훨씬 빠르다.)

(아주 무거운 객체를 만드는 경우: 데이터베이스 연결의 경우)