티스토리 뷰
불필요한 객체 생성을 피하라
-
똑같은 기능의 객체를 매번 생성하기보다는 객체 하나를 재사용하는 편이 나을 때가 많다.
-
한 코드의 예를 통해 살펴보자
String s = new String("Effective Java"); String s = "Effective Java";
- 첫번 째 문장은 실행될 때마다 String 인스턴스가 새로 만들어진다.
- 이 문장은 매우 쓸데 없다.. 만약 반복문이나 빈번히 호출되는 메서드 안에 있다면 쓸데없이 해당 인스턴스가 새로 만들어질 수 있다. 그냥 재사용하면 되는데..
- 두번 째 문장을 살펴보면 새로운 인스턴스를 매번 만드는 대신 하나의 String 인스턴스를 사용한다. 즉, 반복문이나 빈번히 호출되는 메서드 안에 있어도 가상 머신 안에서 재사용함이 보장되는 것이다
- 첫번 째 문장은 실행될 때마다 String 인스턴스가 새로 만들어진다.
-
생성자 대신 정적 팩토리 메서드를 제공하는 불변 클래스에서는 정적 팩토리 메서드를 사용해 불필요한 객체 생성을 피할 수 있다.
Boolean(String); //생성자 사용 Boolean.valueOf(String); //팩토리 메서드 사용
- 생성자는 호출할 때마다 새로운 인스턴스를 만들지만, 팩토리 메서드는 전혀 그렇지 않는다.
-
생성 비용이 비싼 객체를 반복해서 필요한 경우가 있다면 캐싱하여 사용하기를 권장한다.
-
다음 문자열이 유효한 로마 숫자인지를 확인하는 메서드를 보자
static boolean isRomanNumeral(String s) { return s.matches("^(?=.)M*(C[MD]|D?C{0,3})..."); }
-
이 방식은 문제가 발생하는데,
String.matches
는 정규표현식으로 문자열 형태를 확인하는 가장 쉬운 방법이지만, 성능이 중요한 상황에서 반복해 사용하기엔 적합하지 않다.- 왜냐하면, 해당 메서드(matches) 안에는 정규 표현식을 표현하는 Pattern 인스턴스가 있는데, 이는 한 번 쓰고 버려져서 곧바로 가비지 컬렉션 대상이 되고, 반복적으로 사용하게 되면 쓸데없이 Pattern 인스턴스를 생성하기 때문이다.
-
성능을 개선하기 위해서는 Pattern 인스턴스를 클래스 초기화(정적 초기화) 과정에서 직접 생성해 캐싱해두고, 나중에 isRomanNumeral 메서드가 호출될 때마다 인스턴스를 재사용 하는 것이다.
static boolean isRomanNumeral(String s) { private static final Pattern ROMAN = Pattern.compile( "^(?=.)M*(C[MD]|D?C{0,3})..."); } static boolean isRomanNumeral(String s) { return ROMAN.matcher(s).matches(); }
- 이렇게 개선할 시 isRomanNumeral이 빈번히 호출되는 상황에서 성능을 상당히 끌어올릴 수 있다. why? 정규표현식을 표현하는 Pattern 인스턴스를 직접적으로 초기화 및 컨트롤하여
matches()
안에서 Pattern 인스턴스를 생성하지 않기 때문에 쓸데없는 객체 생성이 일어나지 않는다. - 또한, 개선 전에는 존재조차 몰랐던 Pattern 인스턴스를 static final 필드로 끄집어내고 이름을 지어주오 코드의 의미가 훨씬 잘 드러난다.
- 이렇게 개선할 시 isRomanNumeral이 빈번히 호출되는 상황에서 성능을 상당히 끌어올릴 수 있다. why? 정규표현식을 표현하는 Pattern 인스턴스를 직접적으로 초기화 및 컨트롤하여
-
-
하지만 주의해야 할 것은, 이렇게 개선된 isRomanNumeral 방식의 클래스가 초기화 한 후 한번도 호출하지 않는다면 ROMAN 필드는 쓸데없이 초기화된 꼴이다.
- 메서드를 처음 호출될 때 초기화하는 방식을 지연초기화라고 하는데, 지연초기화는 코드를 복잡하게 만들고, 성능에 크게 개선되지 않을 때가 많아 사용하는 것에 주의를 가져야 할 것이다.
-
불필요한 객체를 만들어내는 또 다른 예로는 오토박싱을 들 수 있다.
-
오토박싱
- 프로그래머가 기본 타입과 박싱된 기본 타입을 섞어 쓸 대 자동으로 상호 변환해주는 기술이다.
-
오토박싱은 기본 타입과 그에 대응하는 박싱된 기본 타입의 구분을 흐려주지만, 완전히 없애주는 것은 아니다
-
예를 통해 살펴보자
private static long sum() { Long sum = 0L; for(long i = 0; i <= Integer.MAX_VALUE; i++) { sum += i; } return sum; }
- sum 변수를 long 타입이 아닌 Long으로 선언해서 불필요한 Long 인스턴스가 약 231개나 만들어진 것이다.
- 단순히 Long 타입을 long으로 바꿔주면 시간은 6.3초에서 0.59초로 빨라진다.
-
즉, 박싱된 기본 타입보다는 기본 타입을 사용하고, 의도치 않은 오토박시잉 숨어들지 않도록 주의해야한다.
- 이번 아이템에서 객체 생성은 비싸니 피해야 한다가 아니라 시점에 맞게 사용하라는 것이다.
- 일반적으로 요즘 JVM은 부담되지 않는 객체를 생성하고 회수하는 부분은 크게 부담되지 않으므로, 프로그램의 명확성, 간결성, 기능을 위해서 객체를 추가로 생성하는 일이라면 일반적으로 좋은 작업이다.
- 아이템 50에서는 방어적 복사(새로운 객체를 만들어야 한다면 기존 객체를 재사용하지마 라)를 다루는데, 방어적 복사가 필요한 상황에서 객체를 재사용했을 때의 피해가 필요없는 객체를 반복 생성했을 때의 피해보다 훨씬 크다는 사실을 기억해두어야 한다.
http://www.yes24.com/Product/Goods/65551284?scode=032&OzSrank=1
'Java > Effective Java' 카테고리의 다른 글
[Effective Java] 아이템 15. 클래스와 멤버의 접근 권한을 최소화하라 (0) | 2020.01.04 |
---|---|
[Effective Java] 아이템6. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (0) | 2019.12.22 |
[Effective Java] 아이템 4. 인스턴스화를 막으려거든 private 생성자를 사용하라 (0) | 2019.12.14 |
[Effective Java] 아이템 3. Private 생성자나 열거 타입으로 싱글턴임을 보증하라 (0) | 2019.12.14 |
[Effective Java] 아이템2. 생성자에 매개변수가 많다면 빌더를 고려하라 (0) | 2019.12.14 |
- Total
- Today
- Yesterday
- try with resources
- 정적팩터리메서드
- 인프런
- 생성자
- 김영한
- try catch finally
- 연관관계
- Spring
- 빌더 패턴
- 스프링부트
- 이펙티브 자바
- 이펙티브자바
- 복사 팩토리
- junit
- 팩토리 메소드 패턴
- java8
- 빈 순환 참조
- 자바8
- java
- jdk버전
- springboot
- package-private
- effectivejava
- mustache
- flatMap
- Effective Java
- JPA
- 점층적 생성 패턴
- ifPresent
- @Lazy
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |