개발하는 두더지

[Effective Java 규칙4] 불필요한 객체는 만들지 말자 본문

Java,Android

[Effective Java 규칙4] 불필요한 객체는 만들지 말자

덜지 2018. 9. 4. 18:36

Effective Java 2/E 책과 구글링을 통해 내용을 정리하고 개인적인 견해가 포함된 글입니다.


기능적으로 동일한 객체는 필요할 때마다 만드는 것보다 재사용하는 것이 낫다.

객체를 재사용하는 프로그램은 더 빠르고 우아하다


String s = new String("hello");

객체를 생성할 때마다 새로운 객체가 만들어진다.


s = "hello";

이렇게하면 실행할 때마다 객체를 새로만드는 것이 아닌 동일한 객체를 사용한다. ( 아마 GC에 의해 사라지기 전까지 )

같은 가상 머신에서 실행되는 모든 코드가 해당 객체를 재사용한다.



예를들어 아래와 같은 클래스가 있다

1946년에서 1965사이에 태어났는지 확인하는 메서드이다

public class Person {
private final Date birthDate;

// 생성자, 초기화 생략

public boolean isBabyBoomer() {
Calendar gmtCal =
Calendar.getInstance(TimeZone.getTimeZone("GMT"));
gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
Date boomStart = gmtCal.getTime();
gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
Date boomEnd = gmtCal.getTime();
return birthDate.compareTo(boomStart) >= 0 &&
birthDate.compareTo(boomEnd) < 0;
}
}

문제점을 알겠는가?

Calanedar 객체 1개, Date객체 2개를 호출시마다 생성하고 있다.


위와 같은 비효율적인 코드를 정적 초기화 블록을 통해 개선할 수 있다

public class Person {
private final Date birthDate;

// 생성자, 초기화 생략

private static final Date BOOM_START;
private static final Date BOOM_END;

static {
Calendar gmtCal =
Calendar.getInstance(TimeZone.getTimeZone("GMT"));
gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
BOOM_START = gmtCal.getTime();
gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
BOOM_END = gmtCal.getTime();
}

public boolean isBabyBoomer() {
return birthDate.compareTo(BOOM_START) >= 0 &&
birthDate.compareTo(BOOM_END) < 0;
}
}

클래스가 초기화될 때 Calendar 1개, Date 2개의 객체가 한번만 만든다

메서드가 호출되도 새로운 객체를 만들지않고 만들어진 객체로 비교한다.


Effective Java 책에 위 메서드를 10만번 호출할 경우

1번 방식은 32,000ms , 2번 방식은 130ms 의 시간이 소요된다. 즉 성능이 250배나 좋아진 것이다.

물론 Calendar 객체 생성 비용이 특별히 높기 때문에 이러한 결과가 나온 것이다

최적화를 한다고 이렇게 항상 성능이 좋아지는 것이 아님을 명심하자


만약 메서드가 한번도 호출되지 않는다면?

불필요한 객체가 만들어졌기 때문에 비효율적일 수 있다. 이를 방지하기 위해서 초기화 지연(lazy initialization) 기법을 사용하면 된다

하지만 이 방법은 추천할 방법은 아니다. 초기화를 지현시키면 구현이 복잡해지고 성능을 개선하기도 어렵기 때문이다




또한 기본자료형에도 비슷한 케이스가 있다

바로 자동객체화( autoboxing ) 라는 것인데, 기본 자료형과 객체 표현형을 섞어서 사용할 수 있게 해준다. 둘 간의 변환은 자동으로 이루어지므로 예상치 못하게 느린 프로그램을 만들어 버릴 수 있다.

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("calculation time " + ((end - start) / 1000));

Long 타입은 sum에 long 타입인 i를 더하면 autoboxing해서 Long 객체를 새로 만든다.

이를 Integer.MAX_VALUE 값인 2^31 번을 수행하게 된다.

필자의 PC에서 10초의 시간이 걸렸는데 sum의 타입을 long으로 바꾸면 1초가 걸린다.


이와 같이,

객체 표현형 대신에 기본 자료형을 사용하고, 생각지도 못한 자동 객체화가 발생하지 않도록 유의해야 한다.




lazy initialization 초기화 지연


1. 메서드 등으로 객체에 접근이 될 때 초기화하는 방법   예를들면 코틀린에서 late init { 초기화 }  하는 방법인데 객체가 최초로 호출되는 시점에 초기화 한다

2. 사용자가 초기화를 개입하는 코드. 예를 들면 UI 컴포넌트의 경우 onCreate할 때 먼저 초기화를 시켜야 다른 메서드 등에서 참조할 수 있다. 이와 같이 초기화하는 방법




상수풀의 개념

https://m.blog.naver.com/PostView.nhn?blogId=adison777&logNo=80173278713&proxyReferer=https%3A%2F%2Fwww.google.co.kr%2F























Comments