Study/Effective-Java

[item#06] 불필요한 객체 생성을 피하라

hongeeii 2023. 11. 27. 18:29
728x90
반응형

불필요한 객체 생성을 피하라

매번 객체를 생성하는 것 보다 객체를 재사용하는 것이 더 빠르고 세련.(특히 불변 객체)

String s = new String("bikini"); // 1. 이거보다 (매번 객체 생성)
String s = "bikini"; // 2. 이게 나음 (하나의 인스턴스를 사용)

2 번 방식으로 하면 같은 가상 머신 안에서 똑같은 문자열 리터럴을 사용하는 모든 코드가 같은 객체를 재사용함이 보장됨.


정적 팩터리 메서드를 제공하는 불변클래스에서는 정적 팩터리 메서드를 사용해 불필요한 객체생성을 피할 수 있음.


(Boolean(String) 생성자 보다 Boolean.valueOf(String) 이 더 좋음)


가변객체라도 사용중 변경되지 않는다면 재사용할 수 있음.

생성 비용이 비싼 객체가 반복해서 필요하다면 캐싱하길 권장.

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})$");
}

String.matches는 정규표현식으로 문자열 형태를 확인하는 가장 쉬운 방법이지만, 성능이 중요한 상황에서 방복해 사용하기 적합하지 않음.
이 메서드 내부에서 사용하는 Pattern 인스턴스는 한번 사용하고 바로 가비지 컬렉션 대상이 됨.
Pattern은 입력받은 정규표현식에 해당하는 유한 상태 머신(finite state machine)을 만들기 때문에 인스턴스 생성 비용이 높음.

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 isRomanNumeral(String s){
    return ROMAN.matcher(s).matches();
  }
}

변경 전과 변경 후를 비교하면 성능이 좋아질뿐만 아니라 코드도 더 명확해 짐.

RomanNumerals 클래스 방식은 초기화 이후 사용되지 않으면 불필요한 초기화를 한 꼴이 됨.

지연초기화(lazy initialization)을 할 수도 있지만 코드를 복잡하게 만들고 성능은 크게 개선되지 않기에 권하지 않음.

어댑터는 객체당 하나씩이면 충분

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

어댑터는 뒷단 객체를 관리하기만 하면 됨. => 뒷단 객체당 하나만 있어도 충분함.

Map을 예시로 들자면 keySet 메서드를 사용해 Set 을 반환할 때 새로운 Set 인스턴스가 반환한다 생각할 수도 있지만 아닐 수도 있다.

// HaspMap 의 keySet
public Set<K> keySet() {
    Set<K> ks = keySet;
    if (ks == null) {
        ks = new KeySet();
        keySet = ks;
    }
    return ks;
}

반환된 Set은 가변이더라도 기능적으로 모두 똑같음.

keySet이 뷰를 여러개 만들어도 상관은 없지만 어떤 필요나 이득도 없음.

오토박싱

오토박싱은 기본 타입과 그에 대응하는 박싱된 타입의 구분을 흐려주지만 완전히 없애주는 것은 아니다.

의미상으로는 별다를 것 없지만 성능으로는 아니다.

private static long sum(){
  Long sum = 0L;
  for(long i = 0; i <= Integer.MAX_VALUE; i++){
    sum += i;
  }
  return sum;
}

위처럼 변수를 클래스로 선언하면 불필요한 Long 인스턴스가 연산때마다 생성됨. => 성능 저하

박싱된 타입보다는 기본타입을 사용하고, 의도치 않은 오토박싱이 숨어들지 않도록 주의하자.

객체 생성은 비싸지 피해자 -> X

기존 객체를 재사용해야 한다면 새로운 객체를 생성하지 말자 -> O

아주 무거운 객체가 아니라면 객체 풀(pool)을 만들지 말자.

방어적 복사에 실패하면 버그와 보안구멍으로 이어지지만, 불필요한 객체 생성은 코드 형태와 성능에만 영향을 줌.

new String

JVM은 String을 풀(pool)에다가 캐싱을 하고 있다고 보면 됨.

=> 한번 만들어진 문자열을 재사용 할 때는 풀에서 꺼내서 씀. => new 를 사용하면 강제로 새로운 문자열을 만들기 때문에 불필요한 객체를 생성함.
image

정규표현식

정규표현식은 한번 만들때 CPU 메모리를 사용 하는 등 비용이 비쌈.

패턴을 문자열로 받고 컴파일하여 인스턴스를 만드는 과정이 오래걸림.

image


String의 matchs, split, replaceAll(replaceFirst) 에서 사용.

split의 경우에는 하나의 문자로 할때는 정규식을 사용하지 않는 것이 더 빠를 수도 있음.

Deprecation

image


forRemoval : 더 강력하게 표시

since : deprecate 한 버전

javaDoc 으로 별도의 표시 가능

728x90
반응형