C/C++처럼 직접 메모리 관리를 해야하는 언어와 달리, GC가 포함된 언어를 사용하면 프로그래밍이 아주 편해진다. 

 위의 코드는 기능상 문제는 없으나, 메모리 누수 문제가 있다. 이 메모리 누수는 과연 코드의 어느 부분에서 생길까? 메모리누수는 바로

pop() 메서드에서 발생하는데, elements[--size]를 리턴을 하였으나 , element[size]에 있는 객체는 메모리가 해제되어 있지 않기 떄문에 메모리 누수가 생긴다. 이와 같은 문제를 해결하기 위해서는 쓸일 없는 객체(element[size]) 참조는 무조건 null로 만드는 것이다.

하지만 위와 같은 참조처리는 항상 그럴필요는 없는데, 프로그램만 난잡해지기 때문이다. 객체 참조를 null로 처리하는 것은 규범이라기 보단 예외적인 조치가 되어야 한다. 만기 참조를 제거하는 가장 좋은 방법은 변수를 정의할 때 그 유효범위를 최대한 좁게 만들면 자연스럽게 해결된다.


캐시나 리스너를 구현할 때도 메모리 누수가 자주 발생하는데, WeakHashMap을 가지고 캐시를 구현하면 해결할 수 있다.



기능적으로 동일한 객체는 필요한 때마다 만드는 것보다 재사용하는 편이 낫다. 절대로 피해야 하는 극단적인 예.

 위의 문장은 실행될 떄마다 String 객체를 만드는데, 쓸데없는 짓이다. String 생성자에 전달되는 "test"는 그 자체로 String객체이기 때문인데, 이러한 문장이 반복문안에서 실행될 경우 쓸데없는 String객체가 생성되게 되는 것이다. 아래와 같이 고치는 것이 옳다.

생성자 대신 정적 팩터리 메서드를 이용하면 불필요한 객체 생성을 피할 수 있을 경우가 많다. 생성자는 호출할 때마다 새 객체를 만들지만, 정적 팩터리 메서드는 그럴 필요도 없고 실제로 그러지도 않을 것이기 때문이다.

 또 한가지 예를들면, 아래와 같은 베이비붐 세대의 사람인지 판별하는 메서드가 있다고 치자. 

 위의 보인 isBabyBoomer 메서드는 호출될 때마다 Calendar 객체 하나, TimeZone 객체 하나, 그리고 Date 객체 두 개를 쓸데없이 만들어 댄다. 이렇게 비효율적인 코든느 정적 초기화 블록을 통해 개선하는 것이 좋다. isBabyBoomer가 자주 호출되는 메서드였다면 성능은 크게 향상될 것이다.


또한 자동 객체화(AutoBoxing)에 대해서도 조심하여 사용하여야 한다.

 위 코드의 문제점은 무엇일까? sum이 long이 아니라 Long으로 선언되어 있는데 , 그 덕에 Integer.MAX_VALUE의 수만큼 Long객체가 생성되는 문제점이다. 객체 표현형 대신 기본 자료형을 사용하고, 생각지도 못한 자동객체화가 발생하지 않도록 유의해야 한다.

 그러나 객체를 만드는 비용이 높으니 무조건 피하라는 것이 아니고, 객체를 만들어서 코드의 명확성과 단순성을 높이고 프로그램의 능력을 향상시킬 수 있다면, 일반적으로는 만드는 것이 좋다.

 마찬가지로, 직접 관리하는 객체 풀을 만들어서 객체 생성을 피하는 기법은 객체 생성 비용이 극단적으로 높지 않다면 사용하지 않는 것이 좋다. 객체 풀을 만드는 비용이 정당화 될 만한 객체의 예로 가장 고전적인 것은 데이터베이스 연결이다.

때로는 정적 메서드나 필드만 모은 클래스를 만들고 싶을 때가 있다. 자바의 기본 자료형 값 또는 배열에 적용되는 메서드를 한군데에 모아 두는 클래스라던가, 정적 메서드를 모아둔 클래스 등등 이러한 유틸리티 클래스들은 객체를 만들 목적의 클래스가 아니다. 하지만 생성자를 생략하면 컴파일러는 자동으로 기본생성자를 만들어 버린다. 따라서 원래 의도와는 달리 객체가 생성되어 버리는 것이다.

해결방법. private 생성자를 클래스에 넣어서 객체 생성을 방지

위와같이 구현한 클래스는 어떠한 방식으로도 객체를 생성할 수 없다. 또한 이렇게 하면 하위 클래스도 만들 수 없다. 모든 생성자는 상위 클래스의 생성자를 명시적으로든 아니면 묵시적으로든 호출할 수 있어야 하는데, 호출 가능한 생성자가 상위클래스에 없기 때문이다.



싱글턴은 객체를 하나만 만들 수 있는 클래스다. 싱글턴은 보통 유일할 수 밖에 없는 시스템 컴포넌트를 나타낸다. 

싱글턴 구현방법1

생성자는 private로 선언하고, 싱글턴 객체는 정적멤버를 통해 이용한다. 정적멤버는 final로 선언하여 변경불가능하도록 구현한다.

private생서자는 public static final 필드인 Elvis.INSTANCE를 초기화 할 때 한 번만 호출된다. public이나 protected로 선언된 생성자가 없으므로, Elvis 객체는 일단 Elvis클래스가 초기화되고 나면 하나만 존재하게 된다. 클라이언트가 이 상태를 변경할 방법은 없지만 주의할 것이 하나 있다. AccessibleObject.setAccessible 메서드의 도움을 받아 권한을 획득한 클라이언트는 리플렉션기능을 통해 private 생성자를 호출할 수 있다. 이런 종류의 공격을 방어하고 싶다면, 두 번째 객체를 생성하라는 요청을 받으면 예외를 던지도록 생성자를 고쳐야 한다.

싱글턴 구현방법2

정적 팩터리 메서드를 이용하는 방법이다.

 Elvis.getInstance는 항상 같은 객체에 대한 참조를 반환한다. 팩터리 메서드를 사용하는 방법의 장점은 

장점1. API를 변경하지 않고도 싱글턴 패턴을 포기할 수 있다는 것. return INSTANCE 대신 new Elvis(); 로 바꿔주면 API 변경없이 싱글턴 패턴을 포기할 수 있다.

장점2.제네릭 타입을 수용하기 쉽다

규칙1의 HashMap의 팩터리 메서드를 이용한 생성자부분을 참고하면 된다.

Enum을 이용한 실글턴 구현

 이 접근법은 기능적으로 public 필드를 사용하는 구현법과 통일하다. 한가지 차이는 좀 더 간결하는 것과, 직렬화가 자동으로 처리된다는 것.


요약

원소가 하나뿐인 enum자료형이야 말로 싱글턴을 구현하는 가장좋은 방법이다.

> 사실 무슨소린지 감이 잘 안온다..


정적 팩터리나 생성자는 같은 문제를 갖고 있다. 선택적 인자가 많은 상황에 잘 적응하지 못한다는 것. 예를들면, 영양 성분표를 나타내는 클래스가 있는 필수적인 항목은 몇가지 되지 않는다. 총 제공량 , 1회 제공량 , 1회 제공량당 칼로리 정도? 그러나 선택적인 항목은 엄청나게 많다. 지방함량율 , 나트륨 함량, 콜레스테롤 함량 등등.. 이러한 항목을 어떻게 표현할 수 있을까?

점층적 생성자 패턴

필수 인자만 받는 생성자를 하나 정의하고, 선택적 인자를 하나받는 생성자를 추가하고, 선택적인자 두가지를 받는 생성자를 추가하고...이와같은 반복적인 작업으로 수많은 항목을 표현하는 패턴. 인자가 많지 않을 때야 큰 문제없겠지만 인자가 늘어날 수록 곤란해질 것이다..
 즉, 점층적 생성자 패턴은 잘 동작하지만 인자 수가 늘어나면 클라이언트 코드를 작성하기 어려워지고, 가독성이 떨어진다.



자바빈 패턴

생성자에 전달되는 인자 수가 많을 때 적용 가능한 두 번째 대안이다. 인잔 없는 생성자를 호출하여 객체부터 만든 다음, 설정 메서드를 호출하여 필수 필드뿐 아니라 선택적 필드의 값들까지 채우는 것.


 그러나 자바빈 패턴에는 심각한 단점이 있다. 1회의 함수 호출로 객체 생성을 끝낼 수 없으므로, 객체 일관성이 일시적으로 깨질 수 있다.생성자의 인자가 유효한지 검사하여 일관성을 보장하는 단순한 방법을 여기서는 사용할 수 없다. 

   책에는 위와같이 적혀있는데 , 아래과 같이 인자가 유효한지 검사할 수 있지 않나..? 아마 책의 의도는 생성자 한곳에서 검사하지 않는 다는 말인 것같음.. 


 그리고 자바빈패턴에서는 변경 불가능한 클래스(생성자가 private)를 만들 수 없다.


빌더 패턴

빌더패턴은 점층적 생성자 패턴의 안전성과 과 자바빈 패턴의 가독성을 결합한 패턴이다. 필요한 객체를 직접 생성하는 대신, 클라이언트는 먼저 필수 인자들을 생성자에 전부 전달하여 빌더 객체를 만든 후, 빌더 객체에 정의된 설정 메서드들을 호출하여 선택적 인자들을 추가한 후 , build메서드를 통해 객체를 생성한다.

생성자와 마찬가지로 빌더 패턴을 사용하면 인자에 불변식을 적용할 수 있다. build메서드 안에서 해당 불변식이 위반되었는지 검사할 수 있는 것이다.


장점1. 작성하기도 쉽고, 가독성도 좋다.

생성자를 하나하나 만들어주지 않았기 때문에 코드를 작성하기도 쉽고, 가독성도 뛰어남.

장점2. 여러개의 선택적인 인자를 받을 수 있다.

생성자와 비교했을 때 빌더패턴은 선택적인 인자를 받을 수 있기때문에 유연한 코드작성이 가능하다.

단점1. 객체를 생성하려면 우선 빌더 객체를 생성해야 한다.

이는 성능이 우선이 되는 곳에서는 오버헤드가 발생할 여지가 있다.

요약

빌더 패턴은 인자가 많은 생성자나 정적 팩터리가 필요한 클래스를 설계할 때, 특히 대부분의 인자가 선택적 인자인 상황에 유용하다.



클래스를 통해 객체를 만드는 일반적인 방법은 public으로 선언된 생성자를 이용하는 것이다. 또한 클래스에 public으로 선언된 정적 팩터리 메서드를 이용하여 객체를 생성하는 방법도 있다. 정적 팩터리 메서드를 이용했을 때 장단점은 아래와 같다.

장점1. 정적 팩터리 메서드에는 이름이 있다.

생성자는 클래스명과 이름이 같다. 생성자에 전달되는 인자들은 어떤 객체가 생성되는지 설명하지 못하며, 시그니처별로 하나의 생성자만 넣을 수 있는데 정적 팩터리 메서드를 사용하면 이러한 문제를 해결할 수 있다. 적절한 이름의 정적 팩터리 메서드이름은 생성되는 객체가 어떠한지 충분히 설명할 수 있는 장점이 있다. 

장점2. 호출할 떄마다 새로운 객체를 생성할 필요가 없다.

변경 불가능한 클래스라면(생성자가 private로 막혀있는..)  이미 만들어 둔 객체를 활용할 수도 있고, 만든 객체를 캐시해놓고 재사용하여 같은 객체가 불필요하게 생성되는 것을 제한할 수 있는 장점이 있다. 특히 객체를 만드는 비용이 클 때 위와같은 방법은 더 효율이 좋다.

장점3. 반환값 자료형의 하위 자료형 객체를 반환할 수 있다.

subA가 A의 서브클래스라고 할 때, 아래와 같은 코드를 작성할 수 있다.


위와 같은 방식으로 코드를 작성하면, SubA클래스의 구현 세부사항을 감출 수 있다. 


장점4. 형인자 자료형 객체를 만들 떄 편리하다.

보통 형인자를 전달해서 객체를 생성할 때는 아래와 같이 작성한다.


 이처럼 자료형 명세를 중복하면, 형인자가 늘어남에 따라 길고 복잡한 코드가 만들어져서, 가독성이 떨어진다. 하지만 정적 팩터리 메서드를 사용하면 컴파일러가 스스로 알아내도록 할 수 있다.이런 기법을 자료형 유추라고 한다.

 만약 HashMap클래스에서 위와같은 메서드를 지원해줬다면 코드를 좀 더 간결하게 바꿀 수 있다.


단점1. 정적 팩터리 메서드만 있는 클래스를 만들면 생기는 가장 큰 문제는, public이나 protected로 선언된 생성자가 없으므로 하위 클래스를 만들수가 없다.

즉 상속을 이용한 코드작성보다는 구성을 이용한 코드로 작성해야 한다는 것이다. 음 .. 근데 둘다 상황에 따라 설계가 다를 것 같으므로 단점이라고 생각되지는 않는다.. 

단점2. 정적 팩터리 메서드는 다른 정적 메서드와 확연히 구분되지 않는다.

정적 팩터리 메서드는 생성자처럼 다른 메서드와 확연하게 구분되지 않는다. 따라서 네이밍을 신중히 해야하고, 주석을 달아야할 것 같다..


요약

정적 팩터리 메서드와 public 생성자는 용도가 서로 다르다. 정적 팩터리 메서드의 용도는 위와같은데, 두개를 명확히 용도에 따라 구분하자니 쉽지가 않다.  일반적인 public을 이용한 생성자는 멤버변수의 초기화, 또는 상속을 이용한 설계를 할 때 사용하는 것 같다.


+ Recent posts