2011.04.07 23:42

 C++에는 소멸자(Destructor)라는 것이 존재한다. 생명주기가 끝난 객체의 자원을 회수하는데 필수적인 작업이다. 
 생성자를 쓴다면 소멸자는 반드시 써야 한다는 공식이 존재할 정도로 중요하고 필수적인 요소이다.

 하지만 자바는 자원을 회수 해주는 JVM이 존재하고, 실행 시점이 불분명하고 실행 여부도 불분명한 finalizer를 소멸자 처럼 쓰는것은 매우 위험한 방법이다.
 의도적으로 자원을 회수할 필요가 있다면 try~finally 블록을 이용하거나 따로 종료 메소드를 (스트림을 쓰는 곳에서 자주 볼 수 있는 close 메소드 등을) 사용하는 것이 바람직하다.

 책에는 쓰지 말아야 하는 설명이 많지만 중요한 것은 finalizer를 사용하지 않는 습관을 가지고, 네이티브(C나 C++ 등의 자바이외의 프로그래밍 언어) 피어 객체의 사용 등과 같이 가비지 컬렉터가 회수 해주지 못하는 경우에 안전망의 역할이 필요한 경우에만 한정적으로 사용하고 쓸때는 경고를 출력해야 한다는 것이다.
신고
Posted by JAVA_HOME

댓글을 달아 주세요

2011.04.07 23:22
 Java는 JVM의 가비지 컬렉터라는 녀석이 메모리 관리를 해주므로 C나 C++와 같이 메모리 관리를 프로그래머가 해줘야 하는 언어들보다 쓰기 편하다.
 하지만 메모리에 대해서 너무 우습게 생각하면 OutOfMemoryError와 같은 녀석을 만나고 당황하게 되는 경우가 생길지도 모른다.
 한번의 실행으로 끝나는 프로그램의 경우는 만날 경우가 흔치 않지만, UI처럼 기본적으로 루프가 돌고 있는 프로그램을 작성할 떄는 주의를 요한다.

 필자도 Eclipse RCP로 모니터링 UI를 만든적이 있는데 3일마다 프로그램이 죽는다고 해서 코드를 보니 Font를 dispose를 하지 않아서 메모리에 계속 쌓이고 있었다. 

 Stack에서의 메모리 누출을 볼 수 있는 간단한 예제를 보자. 

 

 자료구조 수업을 듣는다면 자주 만들어 보게 되는 기본적인 스택이다. push를 하다가 array의 capacity의 한도에 도달하면 사이즈를 2배로 키우는 것이다. 

 그렇지만 위의 코드는 메모리 누출이 발생하는데 pop을 해서 객체를 리턴하고 인덱스는 줄였지만 쓰지 않는 객체를 null로 바꿔주지 않아서 쓸모 없는 참조를 가지기 때문에 가비지 컬렉터가 회수를 하지 않기 때문이다. 

  쓸모 없는 참조를 null로 바꾸면 다른 이점도 생기는데 실수로 참조를 하게 될떄 NullPointerException이 발생하기 떄문에 디버깅 하기도 쉬워진다.

  하지만 코드 실행이 끝나는 즉시 모든 객체를 null로 변경하는 것은 필수도 아니고 바람직한 것도 아니다. 

  메모리 누출에 신경을 써야 할 경우는 배열과 같이 자신의 메모리를 스스로 관리하는 경우와 캐시를 사용하고 필요가 없어질때, 그리고 리스너와 콜백을 등록하고 말소 하지 않았을 때이다.

  메모리 누출은 잘 드러나지 않으므로 힙 프로파일러(heap profiler)와 같은  디버깅 툴을 이용한다면 도움이 될 것이다.
신고
Posted by JAVA_HOME

댓글을 달아 주세요

2011.04.07 22:54
 객체를 재사용하면 객체 생성에 소요되는 시간과 리소스를 아낄 수 있으므로,  오류가 없는 범위내에서(특히 불변 객체) 같은 객체는 재사용하는 것이 좋다.

 특별한 테크닉이 있는 것은 아니지만 주의해야 할 것은  String 을 사용할 때와 자바 1.5 이상에서 생긴 기본형의 auto-boxing 기능을 사용할 때이다.

 먼저 String의 경우는 String s = new String("Hello World!"); 와 String s = "Hello World!";를 예로 들어 설명하겠다.

 new String("Hello World!") 를 반복 실행 할 경우 실행때마다 새로운 String 인스턴스를 만들 것이다.
 하지만 "Hello World!"로 생성을 하면 실행 할 때 마다 String 인스턴스를 만드는 것이 아니라 하나의 String 인스턴스에서 동일한 리터럴을 갖도록 재사용 될 것이다. 리터럴에 대한 설명은 아래에 있다.

리터럴(literal) 이란?

 코드 안에  지정한 상수 값을 말하며, 자바에서는 리터럴 풀(pool)메모리 영역에 보관한다. 그리고 같은 값을 갖는 리터럴이 코드에 여러 번 나오더라도 하나만 만들어 공유한다.
 따라서 문자열 리터럴의 경우 String s1 = "Java" String; s2 = "Java"; 의 경우  "Java"라는 같은 String 객체를 참조한다. 참고로 두 개 이상의 객체 참조변수가 이처럼 같은 객체를 참조하는 것을 객체 Aliasing이라고 한다.


 비슷한 용법으로 기본형인 boolean의 경우 Boolean(String)보다  Boolean.valueOf(String)과 같은 팩토리 메소드를 사용하는 것이 바람직하다.

 Auto-boxing을 사용하는 경우는 기본형을 쓸 수 있는 곳에는 기본형을 쓰는 습관을 들이면 된다. 아래의 잘못된 예제를 보자.
 
위의 코드의 경우 Long으로 sum을 선언하여서 long인 i와 더하기를 한번 할 때 마다 불필요한 Long 객체를 만들므로 매우 느려진다.




하지만 뒤에서 살펴볼 방어 복사(Defensive copying, 항목 39)에서는 이와 반대의 주장을 하는데 새로운 것을 생성해야 할때는 기존 객체를  사용하지 말자고 한다.
 방어 복사가 필요한 곳에서 객체의 재사용을 했을 때의 불이익은 찾기 힘든 어려운 잠재적 결함이 생길 수 있고 보안에도 구멍이 뚤리는 등의 치명적일 수 있으나, 기존 객체를 사용해도 되는 곳에서 쓸데없는 객체를 만들었을때의 불이익은 성능에만 영향을 주기 때문에 신중히 사용해야한다.
신고
Posted by JAVA_HOME

댓글을 달아 주세요

2011.04.07 22:25
 유틸리티 클래스들은 주로 static 메소드나 static 필드만을 모아놓고 final로 상속을 받지 못하게 한다.
 
 이러한 유틸리티 클래스를 구현할떄는 private로 생성자를 두어 컴파일러가 default 생성자를 만들지 못하게 한다.

 혹자는 이러한 유틸리티 클래스를 abstract 클래스로 만드는데 이는 잘못된 방법이다.  
 abstract 클래스로 만들어버리면 상속을 하라는 의미로 착각할 수 있고, 상속을 하면 인스턴스를 만들수 있다. 따라서 이러한 방식은 잘못된 설계이다.

 private 생성자를 만들면 API문서에도 안 나타나게 할 수 있고, 상속도 하지 않게 할 수 있다.

 private 생성자를 사용하는 Idiom을 보자.

 AssertionError 예외는 꼭 필요한 것은 아니지만 클래스 내부에서 생성자를 호출하는 오류를 범할 때를 위한 것이다.

 
신고
Posted by JAVA_HOME

댓글을 달아 주세요

2011.04.07 22:10
 하나의 인스턴스만 생성하는 클래스인 Singleton. 이을 구현하는 기존의 방법과 특징은 디자인 패턴의 Singleton을 참고하자.
 
 private 생성자를 이용해서 싱글톤을 구현하는 것은 디자인 패턴에서 많이 보았을 것이다.
 하지만 싱글톤 클래스를 직렬화 가능하도록(Serializable) 하려면 여러 과정이 필요하다.

1. 클래스 선언부에 implements Serializable 추가
2.  모든 인스턴스 필드를 transient로 선언
3. readResolve 메소드를 추가하여 역직렬화시 새로운 INSTNACE를 생성하는 것을 방지한다.


 그런데 싱글톤을 구현하는 데는 또 다른 방법이 있는데, 자바 1.5이후에서 지원하는 열거형(enum)타입을 하나의 요소를 갖도록 만들면 된다.

 열거형으로 만들게 되면 직렬화가 자동으로 지원되고, 인스턴스가 여러개 생기지 않도록 확실하게 보장해준다. 이 방법은 아직 널리 적용되지 않았지만 싱글톤을 구현하는 가장 좋은 방법이다.

신고
Posted by JAVA_HOME

댓글을 달아 주세요

2011.04.07 21:29
객체를 언제 어떻게 생성하는지, 어떻게 생성을 피해야 하는지, 적합한 방법으로 소멸되는 것을 어떻게 보장하는지, 그리고 객체 소멸에 앞서 선행되어야 하는 클린업 작업을 어떻게 관리할 것인가에대해서 다룬다.
신고
Posted by JAVA_HOME

댓글을 달아 주세요

2011.04.06 22:16

 지금까지는 선택가능한 매개변수가 많아질 경우 텔리스코핑 생성자(telescoping constructor) 패턴을 사용했다.

 즉, 필수 매개변수들만 갖는 생성자 A, A에 선택 매개변수를 추가로 갖는 생성자 B, B에 선택 매개변수를 추가로 갖는 생성자 C와 같이 겹겹이 만드는 것이다.

 이러한 방법은 매개변수의 수가 증가하면 코드의 작성도 번거로워지고, 가독성도 떨어진다.
 또한 동일한 타입의 매개변수가 연속되어 있을 경우 컴파일 에러로 잡아낼 수 없다는 단점도 존재한다.

 매개변수가 많은 생성자의 두 번쨰 대안으로 자바빈즈(JavaBeans) 패턴이 있다.
 이 패턴은 매개변수가 없는 생성자를 호출해서 setter method를 호출해서 필수 필드와 선택 필드의 값을 지정한다.

 하지만 자바빈즈 패턴은 심각한 결점을 가지고 있는데, 생성 과정을 거치는 동안 자바빈(JavaBean) 객체가 일관된 상태를 유지하지 못하기 떄문에 결함을 찾기 어려운 문제를 야기할 수 있다.

 이에 추가하여 불변(immutable) 클래스를 만들 수 있는 가능성을 배제하므로 thread에서 안정성을 유지하려면 프로그래머의 추가적인 노력이 필요하다는 단점이 있다. 

 앞의 두개의 패턴을 보았다면 저절로 든 생각이 있을 것이다. 두 패턴을 적절히 결합하면 되지 않을까?

 이러한 생각으로 탄생한 것이 빌더(Builder)패턴이다. 

 원하는 객체를 바로 생성하는 대신 클라이언트는 모든 필수 매개변수를 갖는 생성자(또는 static factory method)를 호출하여 빌더 객체를 얻는다.

 그 다음 setter method를 호출하여 필요한 선택 매개변수들의 값을 설정한다.
 setter method를 이용하므로 불변 규칙(invariants, 매개 변수나 필드가 가질 수 있는 값의 범위나 타입 및 형태의 제한) 검사 할 수도 있다.

 마지막으로, 클라이언트는 매개변수가 없는 builder method를 호출하여 불변 객체를 생성한다.(세터 메소드를 갖지 않기 때문에 멤버변수의 값을 변경할 수 없는 객체)
  이때 builder method는 자신이 생성하는 객체의 클래스에 포함된 static member class이다.

 예제를 통해서 알아보자.



코드 아래의 메인함수에서 볼 수 있듯이 필수 매개변수는 빌더의 생성자에서, 선택 가능한 매개변수는 setter method를 두어 값을 설정한다. 마지막으로 builder method를 이용해서 원래 만들고자한 Example의 객체를 만든다.


 이러한 빌더 패턴의 단점도 물론 존재한다.
 우선 빌더를 생성 하므로 성능을 매우 중요시 하는 상황에서는 문제가 될 수도 있다.
 그리고 텔리스코핑 패턴보다 코드가 길어진다. 

 하지만 단점은 크지 않고 매개변수가 적다고해서 빌더없이 작성을 하다 향후에 매개변수가 추가될 경우 등을 생각한다면 빌더로 시작하는 것이 좋을 때가 많다.



 이러한 패턴은 많은 곳에서 볼 수 있지만, 필자가 자주 보는 곳은 안드로이드 프로그래밍의 AlertDialog 등을 생성할 때 많이 사용한다.
 http://developer.android.com/reference/android/app/AlertDialog.Builder.html

 많은 매개변수를 가지고 있는데 이를 생성자로 나눈다면 제목, 내용 등의 인자는 String이여서 혼동가능성이 매우 크고, 안드로이드 프레임워크에서 액티비티간의 접근, thread 안정성과 같은 문제가 발생하므로 한번에 생성을 하는 것이 좋은데(불변 객체로 만들지는 않는다.) 이럴 때는 JavaBeans 패턴 보다는 builder를 두어 제목, 내용, 버튼 리스너 등을 한번에 설정하여 dialog로 만들어 버리는 방법을(show() 메소드를 호출하면 AlertDialog를 리턴해 준다.)쓰는 것이다.(필자의 생각이다.)


더보기


신고
Posted by JAVA_HOME

댓글을 달아 주세요

2011.04.06 05:03

 주로 생성자를 private로 두어 외부에서 생성을 막은 다음 newInstance() 와 같은 메소드로 return 값으로 객체를 받는 방식이다.

 장점은
  
  1. 자신의 이름을 가지므로 객체의 생성시 만들어지는 객체를 잘 표현할 수 있게 되고 코드의 가독성도 높아진다.

  2. 동일한 객체를 사용시 매번 새로 생성하는 것을 막을 수 있다.

    이는 인스턴스 제어 클래스를 만들 수 있게 됨을 의미하고, 이를 이용해서 싱글톤 또는 인스턴스 생성 불가 클래스로 만들 수 있게 된다.

  3. 반환하는 객체의 클래스를 선택 할 수 있게 된다.

     
    이때 선택 가능한 클래스는 inner class로 많이 구현한다.
    이로 인해서 외부에서 볼 때 간결해지고, Version-up 등의  revision시 외부에 영향을 주지 않게 설계 가능한 장점이있다.
     
  4. 매개변수화 타입 인스턴스를 생성하는 코드를 간결하게 해준다.

     
     매개변수화 클래스의 생성자를 호출할 때는 타입 매개변수를 지정해줘야 하는데 static factory method를 이용하면 간결하게 표현할 수 있다.

 단점은
 

  1. public이나 protected 생성자가 없는 경우 서브 클래스를 가질 수 없다.

    하지만 상속(inheritance) 대신 컴포지션(composition)(항목 16)을 사용하게 된 다면 장점이 될 수도있다.
     
  2.  생성자와 달리 일반 static method와의 구별이 어렵다.

     
     생성자 대신 static factory method를 제공하는 클래스의 경우 인스턴스를 어떻게 생성하는지 알기 힘들 수 있다. 특히 javadoc 등으로 만든 API문서에서 구별하기란 쉽지 않다.


주로 사용하는 명칭은 다음과 같다.
  • valueOf - 자신의 매개변수와 같은 값을 갖는 인스턴스를 반환한다. 주로 타입을 변환할 때 사용하는 메소드이다.  

  •  of - valueOf를 줄인 형태의 이름이며, EnumSet에서 사용한다.

  • getInstance - 매개변수에 나타난 인스턴스를 반환하지만, 매개변수와 같은 값을 갖지 않을 수 도 있다. 싱글톤의 경우 getInstance는 매개변수가 없고 오직 하나의 인스턴스를 반환한다.
     
  • newInstance- getInstance와 유사하나 반환되는 각 인스턴스가 서로 다르다.

  • getType - getInstance와 유사하나 팩토리 메소드가 다른 클래스에 있을 때 사용한다.
    여기서 Type은 팩토리 메소드에서 반환되는 객체의 타입을 나타낸다.
     
  • newType - newInstance와 유사하나 팩토리 메소드가 다른 클래스에 있을 때 사용한다.
    여기서 Type은 팩토리 메소드에서 반환되는 객체의 타입을 나타낸다.
     

 모든 일이 그러하든 static factory method와 public 생성자는 각자 나름의 용도가 있으므로 상호간의 장점을 알고 적재적소에 사용하는 것이 중요하겠다.
신고
Posted by JAVA_HOME

댓글을 달아 주세요


티스토리 툴바