2010년 2월 26일 금요일

[소개] 소프트웨어, 누가 이렇게 개떡 같이 만든거야

저자 : 데이비드 S. 플랫 역자 : 윤성준
출판사 : 인사이트 ISBN : 978-89-91268-39-5
가격 : ₩14,000

[목차]

펼쳐두기..


[가을생각]
올해들어 읽은 책중에 가장 최악의 책이었다. 물론 읽는 사람을 흡입하는 작가의 말초적인 단어선택때문인지 하루밤을 꼬박새우게도 한 책이다. 1장, 2장을 넘기면서 강한 자극을 받고 있었는데 갑자기 다른 길로 새버렸다. 나도 한마디해야 겠다. "책, 누가 이렇게 개떡 같이 적은거야" 작가는 이걸로 인세를 받아먹겠지.. -,.-;;


2010년 2월 24일 수요일

[소개] 구글을 지탱하는 기술

저자 : 나시다 케이스케 역자 : 김성훈
출판사 : 멘토르 ISBN : 978-89-6305-004-1
가격 : ₩18,000

[목차]

펼쳐두기..


[가을생각]
본 책은 평소 검색엔진에 관심이 많은 나에게 가볍게 접근할 수 있는 책이었지만 방향성과 거시적 관점의 견해를 알려주는 좋은 책이었다. 다소 무거운 주제가 있기는 했지만 그렇다고 소스 코드 분석하는 수준은 아니라서 화장실 갈때나 지하철에서 조금 집중하면 쉽게 알 수 있는 내용이었다. 그래서 인지 2틀간 그냥 쉽게 읽혀진 책이었다.

이책의 의미를 굳이 말하라고 한다면 ... 구글은 개발자가 세운 개발자의 왕국이 아닐까...라는 생각을 해본다. 물론 상장된 기업이므로 그와 관련된 많은 일도 하고 있겠지만 개발자의, 개발자를 위한, 개발자에 의한 회사라는 생각이 든다. 국내에는 대주주의, 대주주를 위한, 대주주에 의한 회사가 많을 뿐이지만...

돈을 많이 벌고, 못벌고의 문제는 아니다. 기업의 냄새가 그렇다는 거지... 멋도 모르고 지껄인 하루다..

2010년 2월 23일 화요일

[03] 모든 객체에 공통적인 메소드

$8. equals 메소드를 오버라이딩 할 때는 보편전 계약을 따르자

인스턴스의 동일여부를 판정하는 equals 메소드의 오버라이딩은 간단한 것 같지만, 잘못되는 경우가 많아서 의도한 결과를 초래할 수 없는 경우가 많다. 이때에는 equals 메소드를 오버라이드 하지 말고 상속받은 그대로 사용하는 것이 좋다. 다음 조건 중 하나라도 만족하면 그렇게 하는 것이 좋다.

  • 클래스의 각 인스턴스가 본래부터 유일한 경우
          인스턴스가 갖는 값보다는 활동하는 개체임을 나타내는 것이 더 중요한 스레드와 같은 클래스
          가 여기에 해당한다.
  • 두 인스턴스가 논리적으로 같은지 검사하지 않아도 되는 클래스의 경우
          java,util.Random 클래스에서는 두 개의 Random 인스턴스가 같은 난수열을 만드는지 확인하기
         위해 equals 메소드를 오버라이딩 할 수 도 있겠지만 논리적으로 의미가 없다.
  • 수퍼 클래스에서 equals 메소드를 이미 오버라이딩 했고, 그 메소드를 그대로 사용해도 좋은 경우
          Set 인터페이스를 구현하는 대부분의 클래스들은 AbstractSet에 구현된 equals를 상속받아 사
          용하며, List의 경우는 AbstractList로 부터, Map의 경우는 AbstractMap에서 상속 받아 사용한           다.
  • private이나 패키지 전용 클래스라서 이 클래스의 equals 메소드가 절대 호출되지 않아야 할 경우
          우연히 호출될 수 있는 그런 상황에서는 다음과 같이 equals 메소드를 반드시 오버라이딩 해서
          호출되지 않도록 해야 한다.

          @Override public boolean equals(Object o) {
throw new AssertionError();
          }

그렇다면 Object.equals를 언제 오버라이드 해야 좋을까? 객체 참조만으로 인스턴스의 동일 여부를 판단하는 것이 아니라, 인스턴스가 갖는 값을 비교하여 논리적으로 같은지 판단할 필요가 있는 클래스로써, 자신의 수퍼클래스에서 equals 메소드를 오버라이드하지 않았을 때이다. 일반적으로 값 클래스가 여기에 해당된다. 하지만 위와 같은 경우이더라도 equals 메소드를 오버라이드 하지 않는 케이스가 있다. 바로 반드시 하나의 객체만 존재하도록 인스턴스 제어를 사용하는 클래스이다. 열거형이 이런 부류에 속하는데, 이런 클래스들의 경우는 논리적인 일치와 객체 참조 일치가 동일한 의미가 된다.

equals 메소드를 오버라이드 할 때는 이 메소드의 보편적 계약을 따라야 한다. 다음 내용은 계약의 내용이다.

equals 메소드는 동등 관계를 구현하며, 그것은
  • 재귀적이다
          : null이 아닌 모든 참조 값 x에 대해 x.equals(x)는 반드시 true를 반환해야 한다.
  • 대칭적이다
          : null이 아닌 모든 참조 값 x와 y에 대해, y.equals(x)가 true를 반환한다면 x.equals(y)도 반드             시 true를 반환해야 한다.
  • 이행적이다
          :  null이 아닌 모든 참조 값 x, y, z에 대해, 만일 x.equals(y)가 true를 반환하고 y.equals(z)가              true를 반환한다면 x.equals(z)도 반드시 true를 반환해야 한다.
  • 일관적이다
          : null이 아닌 모든 참조 값 x와 y에 대해, equals 메소드에서 객체 비교 시 사용하는 정보가 변               경되지 않는다면, x.equals(y)를 여러 번 호출하더라도 일관성 있게 true 또는 false를 반환해             야 한다.
  • null이 아닌 모든 참조 값 x에 대해, x.equals(null)은 반드시 false를 반환해야 한다.

상기와 같은 내용을 종합하여 양질의 equals 메소드 만드는 방법은 다음과 같다.

1. 객체의 값을 비교할 필요 없고 참조만으로 같은 객체인지 비교 가능하다면 == 연산자를 사용하자.

2. instanceof 연산자를 사용해서 전달된 인자가 올바른 타입인지 확인하자.

3. 인자 타입을 올바른 타입으로 변환한다.

4. 클래스의 "중요한(꼭 비교해야 하는)" 필드 각각에 대해서는 인자로 전달된 객체의 필드와 현재 객체(equals 메소드가 호출된)의 필드가 모두 같은지 빠뜨리지 말고 비교한다.
=> 이때 float, double 타입이 아닌 기본형 필드의 경우에는 == 연산자로 비교하며, float은
     float.compare메소드로 double은 double.compare 메소들를 사용한다.
     객체 참조 필드가 null 값을 가질 수 있는데, 이 경우 NullPointerException 예외의 발생을 막으려면
     (field == null ? o.field == null : field.equals(o.field)) 를 사용하거나
     (field == o.field || (field != null && field.equals(o.field))) 사용하자.  

5. 우리의 equals 메소드를 작성한 후에는 과연 그 메소드가 대칭적이며 이행적이고 일관성이 있는지 확인한다.

※유의
▶ equals 메소드를 오버라이드 할 때는 hashCode 메소드도 항상 오버라이드 한다.
▶ 너무 똑똑한 척 하지 마다. (너무 깊이 있게 들어가지 말라 !!!)
▶ equals 메소드의 인자 타입을 Object 대신 다른 타입으로 바꾸지 말자.

$9. equals 메소드를 오버라이드 할 때는 hashCode 메소드도 항상 같이 오버라이드 하자

equals 메소드를 오버라이드 하는 모든 클래스에는 반드시 hashCode 메소드도 오버라이드 해야 한다.






2010년 2월 22일 월요일

[소개] Head First Javascript


저자 : 마이클 모리슨 역자 : 홍형경

출판사 : 한빛미디어 ISBN : 978-89-7914-582-3
가격 : ₩28,000


[목차]

펼쳐두기..


2010년 2월 21일 일요일

[02] 객체의 생성과 소멸

$1. 생성자 대신 static 팩토리 메소드 사용을 고려하자.

클래스의 인스턴스를 생성하는 일반적인 방법은 public 생성자를 제공하는 것이지만, 보다 유용한 방법인 클래스에 public static 팩토리를 두는 것이다.

[java]
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}

상기와 같은 static 팩토리 메소드의 장단점은 다음과 같다.

1. static 팩토리 메소드의 한 가지 장점은, 생성자와 달리 자기 나름의 이름을 가질 수 있다.

자바 클래스는 생성자 이름이 클래스 이름과 같은 하나의 생성자만 가질 수 있다. 따라서 개발자들은 매개변수를 이용하여 여러 생성자를 만들곤 한다. 하지만 이는 좋은 방법이 아니다. 이와 같이 하나의 클래스에 동일한 시그너처를 갖는 여러 개의 생성자가 필요한 경우 생성자 대신 static 팩토리 메소드를 사용하되, 메소드 간의 차이점을 부각시키도록 신중하게 이름을 선정한다.

2. static 팩토리 메소드의 두 번째 장점은, 생성자와 달리 호출될 때마다 매번 새로운 객체를 생성할 필요가 없다.

동일한 객체가 자주 사용될 때, 특히 객체 생성시 자원이나 시간이 많이 든다면 이 방법은 프로그램 성능을 크게 향상시킬 수 있다.

static 팩토리 메소드는 여러 번 호출되더라도 이미 생성된 동일 객체를 반환할 수 있으므로, 클래스에서는 언제든지 인스턴스들의 존재를 직접 제어 할 수 있다. 인스턴스 제어 클래스라 불리는 이와 같은 클래스를 만드는 이유는 몇 가지가 있다.

인스턴스를 제어하면 싱글톤 또는 인스턴스 생성 불가 클래스로 만들 수 있으며, 또한 불변 클래스에서 두 개의 동일한 인스턴스가 생기지 않도록 해준다.

3. 자신의 클래스 인스턴스만 반환하는 생성자와 달리, static 팩토리 메소드는 자신이 반환하는 타입의 어떤 서브타입 객체도 반환할 수 있다.

접근 범위가 public이 아닌 클래스의 객체를 생성하고 반환할 수 있는 API가 있다. 이 기법은 static 팩토리 메소드의 반환 타입으로 인터페이스를 사용하는 인터페이스 기반 프레임워크에 적합하다.

1.5 버전에 추가된 java.util.EnumSet클래스에는 public 생성자는 없고, static 팩토리 메소드들만 있으며, 주어진 열거형 타입의 크기에 따라 둘 중 하나의 객체를 생성하고 반환한다. 즉 64개 이하의 요소수인 경우 RegularEnumSet 인스턴스를 반환하며, 65개 이상이면 JumboEnumSet 인스턴스를 반환한다.

이처럼 유연한 static 팩토리 메소드는 자바 데이터베이스 연결 API(JDBC)와 같은 서비스 제공자 프레임워크의 근간이 된다. 서비스 제공자 프레임워크(Service provider framework)는 세 가지 핵심 컨포넌트로 구성된다. 첫 번쨰는 제공자가 구현하는 서비스 인터페이스이며, 두 번째는 프레임워크 시스템에서 구현체를 등록하는데 사용하는 제공자 등록 API이며 끝으로, 서비스 인스턴스를 얻기 위해 클라이언트가 사용하는 서비스 접근 API가 있다. 또한 선택가능한 내 번쨰 요소는 서비스 제공자 인터페이스라는 것으로, 제공자가 자신의 서비스 구현체 인스턴스를 생성하기 위해 구현한다.

4. static 팩토리 메소드의 네 번째 장점은, 매개변수화 타입의 인스턴스를 생성하는 코드를 간결하게 해준다.

매개변수화 클래스의 생성자를 호출할 때는 타입 매개변수를 지정해야 한다.

[JAVA]
Map<String, List<String>> m = new HashMap<String, List<String>>();

이와 같이 코딩하면 타입 매개변수가 늘어나는 경우 타이핑할 분량이 많아지고 복잡해져서 매우 힘들어진다. 그러나 static 팩토리를 사용하면 컴파일러가 타입 매개변수를 해결하도록 할 수 있는데 이를 타입 추론이라고 한다.

만일 HashMap에서 다음의 static 팩토리 메소들를 제공한다고 가정해 보자

[JAVA]
public static <K, V> HashMap<K, V> newInstance() {
return new HashMap<K, V>();
}

위에서 선언한 메소드를 다음과 같이 간결한 형태로 사용할 수 있다.

[JAVA]
Map<String, List<String>> m = HashMap.newInstance();


5. static 팩토리 메소드의 가장 큰 단점은, 인스턴스 생성을 위해 static 팩토리 메소드만 갖고 있으면서 public이나 protected 생성자가 없는 클래스의 경우는 서브 클래스를 가질 수 없다.

자바 표준 컬렉션 프레임워크에 구현된 클래스의 어느 것도 서브 클래스를 가질 수 없다.

6. static 팩토리 메소드의 두 번째 단점은, 다른 static 메소드와 쉽게 구별 할 수 없다는 것이다.

static 팩토리 메소드의 공통 명칭 사용 예
  • valueOf - 자신의 매개변수와 같은 값을 갖는 인스턴스를 반환한다.
  • of - valueOf를 줄인 형태의 이름이며, EnumSet에서 사용한다.
  • getInstance - 매개변수에 나타난 인스턴스를 반환하지만, 매개변수와 같은 값을 갖지 않을 수 도 있다. 싱글톤인 경우 getInstance는 매개변수가 없고, 오직하나의 인스턴스만 반환한다.
  • newInstance - getInstance와 유사하나 반환되는 각 인스턴스가 서로 다르다.
  • getType - getInstance와 유사하나 팩토리 메소드가 다른 클래스에 있을 떄 사용한다.
  • newType - newInstance와 유사하나 팩토리 메소드가 다른 클래스에 있을 때 사용한다.

$2. 생성자의 매개변수가 많을 때는 빌더를 고려하자

static 팩토리 메소드와 생성자는 공통적인 제약이 있다. 즉, 선택 가능한 매개변수가 많아질 경우 신축성있게 처리하지 못한다는 것이다.

이를 처리하는 방법중 하나는 텔리스코핑 생성자 패턴을 사용하는 것이다.  하지만 이 방법은 매개변수가 많을 때는 클라이언트 코드 작성이 힘들고 코드의 가독성도 떨어진다.

[JAVA] : 텔리스코핑
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;

public NutritionFacts (int servingSize, int servings) {
this(servingSize, servings, 0);
}

public NutritionFacts (int servingSize, int servings, int calories) {
this(servingSize, servings, calories, 0);
}

public NutritionFacts (int servingSize, int servings, int calories, int fat) {
this(servingSize, servings, calories, fat, 0);
}

public NutritionFacts (int servingSize, int servings, int calories, int fat, int sodium) {
this(servingSize, servings, calories, fat, sodium, 0);
}

public NutritionFacts (int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) {
this. servingSize = servingSize;
this. servings = servings;
this. calories = calories;
this. fat = fat;
this. sodium = sodium;
this. carbohydrate = carbohydrate;
}
}


매개변수가 많은 생성자의 두 번째 대안으로 자바빈즈 패턴이 있다. 이 패턴에서는 매개변수가 없는 생성자를 호출해서 객체를 생성한 후 세터 메소드를 호출해서 각각의 필수 필드와 선택 필드 값을 지정한다,

[JAVA] : 자바빈즈 패턴
public class NutritionFacts {
private int servingSize = -1;
private int servings = -1;
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;

public NutritionFacts() { }
public void setServingSize(int val) { servingSize = val; }
public void setServings(int val) { servings = val; }
public void setCalories(int val) { calories = val; }
public void setFat(int val) { fat = val; }
public void setSodium(int val) { sodium = val; }
public void setCarbohydrate(int val) { carbohydrate = val; }
}

자바빈즈 패턴은 심각한 단점을 갖고 있다. 여러 번의 메소드 호출로 나누어져 인스턴스가 생성되므로, 생성과정을 거치는 동안 자바빈 객체가 일관된 상태를 유지하지 못할 수 있다. 따라서 자바빈즈 패턴은 불변 클래스를 만들 수 있는 가능성을 배제하므로 스레드에서 안전성을 유지하려면 프로그래머의 추가적인 노력이 필요하다.

세번째 방법은 위의 두가지 방법을 결합한 빌더 패턴의 형태로써, 원하는 객체를 바로 생성하는 대신 클라이언트는 모든 필수 매개변수를 갖는 생성자를 호출하여 빌더 객체를 얻고, 빌더 객체의 세터 메소드를 호출하여 필요한 선택 매개변수들의 값을 설정한다. 끝으로 클라이언트는 매개변수가 없는 build 메소드를 호출하여 불변 객체를 생성한다.

[JAVA]
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;

public static class Builder {
private final int servingSize;
private final int servings;

private int calories = 0;
private int fat = 0;
private int carbohydrate = 0;
private int sodium = 0;

public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}

public Builder calories(int val) { calories = val; return this; }
public Builder fat(int val) { fat = val; return this; }
public Builder carbohydrate(int val) { carbohydrate = val; return this; }
public Builder sodium(int val) { sodium = val; return this; }

public NutritionFacts (Builder builder) {
return new NutritionFacts(this);
}

private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
}
}

생성자 대비 빌더의 또 다른 장점은, 여러개의 가변인자 매개변수를 가질 수 있다는 것이다. 또한 빌더 패턴은 유연성이 좋다. 하나의 빌더는 여러개의 객체를 생성하는데 사용될 수 있으며, 이러한 과정 중에 빌더의 매개변수는 다양하게 조정될 수 있다. 빌더를 사용하면 일부 필드의 값을 자동으로 설정할 수 있으며, 매개변수 값이 설정된 빌더는 훌륭한 추상 팩토리를 만든다. 즉, 클라이언트 코드에서는 그런 빌더를 메소드로 전달하여 그 메소드에서 하나 이상의 객체를 생성하게 할 수 있다.

생성자나 static 팩토리 메소드에서 많은 매개변수를 갖게 될 클래스를 설계할 때는 빌더 패턴이 좋은 선택이다. 특히 선택 매겨변수가 대부분인 경우가 그렇다.

$3. private 생성자나 enum 타입을 사용해서 싱글톤의 특성을 유지하자

싱글톤은 정확히 하나의 인스턴스만 생성되는 클래스이다.

자바 1.5 버전 이전에는 싱글톤을 구현하는 방법이 두 가지 있었으며, 두 방법 모두 생성자를 private으로 하고, 유일한 인스턴스에 접근할 수 있도록 public static 멤버를 외부에 제공한다.

그중 한 가지 방법에서는 다음과 같이 멤버가 final 필드이다.

[JAVA]
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis() { ... }

public void leaveTheBuilding() { ... }
}

private 생성자는 딱 한번만 호출되어 public static final 필드인 Elvis.INSTANCE를 초기화한다. 클라이언트는 이것을 변경할 수 있는 방법이 없다. 한 가지 예외가 있다면, 허가된 클라이언트가 AccessibleObject.setAccessible 메소드를 사용해서 private 생성자를 재귀적으로 호출할 수 있다는 것이다.

싱글톤을 구현하는 두 번쨰 방법은 static 팩토리 메소드를 public 멤버로 두는 것이다.

[JAVA]
public class Elvis {
private static final Elvis INSTANCE = new Elvis();
private Elvis() { ... }

public static Elvis getInstance() { return INSTANCE; }

public void leaveTheBuilding() { ... }
}

몇 번이 호출되건 Elvis.getInstance에서는 항상 같은 객체 참조를 반환하며, 또 다른 Elvis인스턴스는 절대 생성되지 않는다.

이렇게 구현된 싱글톤 클래스를 직렬화 가능하도록 하려면 어떻게 해야 할까? 우선, 클래스 선언부에 implements Serializable을 추가해야 한다. 그리고 싱글톤을 보장하기 위해 모든 인스턴스 필드를 transient로 선언해야 하며, readResolve 메소드를 추가해야 한다. 그렇지 않으면 직렬화된 인스턴스가 역직렬화 될 때마다 새로운 인스턴스가 생성된다.

[JAVA]
private Object readResolve() {
// 하나의 진짜 Elvis를 반환하고 가비지 컬렉터가 가짜 Elvis를 처리하도록 한다.
return INSTANCE;
}

자바 1.5 이후 버전에서는 싱글톤을 구현하는 세 번쨰 방법이 있다.

[JAVA]
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() { ... }
}

이 방법은 public 필드 방법과 기능적으로 동일하지만 더 간단하다. 그리고 복잡한 직렬화나 리플렉션 상황에서도 직렬화가 자동으로 지원되고, 인스턴스가 여러 개 생기지 않도록 확실하게 보장해 준다. 이방법이 싱글톤을 구현하는 가장 좋은 방법이다.

$4. private 생성자를 사용해서 인스턴스 생성을 못하게 하자

static 메소드와 static 필드만을 모아 놓은 클래스를 만들 필요가 종종 있을 것이다. 예를 들면 java.lang.Math나 java.util.Arrays 클래스처럼 산술 연산에 필요한 기본형 값이나 배열에 관련된 메소드들을 모아 놓는데 사용될 수 도 있으며, java.util.Collections처럼 특정 인터페이스를 구현하는 객체들에 사용되는 static 메소드를 모아 놓는데 사용될 수 있다.

이러한 클래스들은 인스턴스를 생성하지 못하게 설계되었다. 하지만 명시적으로 지정한 생성자가 없을 떄는 컴파일러가 디폴트 생성자를 만들어 준다. 우리가 의도하는 것은 인스턴스를 생성할 수 없게 하는것이다. 이를 위해 간단한 이디엄이 있다. 즉, 디폴트 생성자는 우리가 명시적으로 지정한 생성자가 전혀 없을 때만 자동으로 만들어진다. 따라서 우리가 private 생성자를 정의하면 인스턴스 생성이 불가능한 클래스를 만들 수 있다.

[JAVA]
public class UtilityClass {
private UtilityClass() {
throw new AssertionError();
}
}

명시적으로 정의한 생성자가 private이므로 이 클래스 외부에서는 생성자 호출이 불가능하다.

위의 클래스의 단점은 서브 클래스를 만들 수 없다는 부작용이 있다. 서브 클래스 인스턴스를 생성할 때 생성자를 호출하면, 수퍼 클래스의 디폴트 생성자가 호출된다. 그러나 위 클래스의 서브 클래스를 만들면 수퍼 클래스의 생성자가 private이라 호출이 불가능해지므로 컴파일 시에 에러가 발생한다.

$5. 불필요한 객체의 생성을 피하자

기능적으로 동일한 객체를, 필요할 때마다 매번 새로 생성하기보다는 하나의 객체를 재사용하는 것이 좋을 때가 많다.

해서는 안될 극단적인 예로 다음 코드가 있다.

[JAVA]
String s = new String("stringette");

위의 코드는 String 인스턴스를 불필요하게 생성한다. 개선된 코드는 아래와 같다.

[JAVA]
String s = "stringette";

여기서는 실행될 때마다 새로운 인스턴스를 생성하지 않고 하나의 String 인스턴스를 사용하며, 같은 JVM에서 실행되는 어떤 코드에서도 동일한 문자열 리터럴을 갖도록 재사용 된다.

불변 클래스의 불필요한 객체 생성을 막으려면 생성자보다는 static 팩토리 매소드를 사용하는 것이 좋다.

[JAVA]
public class Person {
private final Date birthDate;

// 다른 부분은 생략
// 절대 이렇게 하지 말라
public boolean isBabyBoomer() {
// 불필요한 객체 생성
Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
...
...
return ...
}
}

개선된 코드는 아래와 같다.

[JAVA]
public class Person {
private final Date birthDate;

// 다른 부분은 생략
static {
Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
}

public boolean isBabyBoomer() {
return ...
}
}


$6. 쓸모 없는 객체 참조를 제거하자

자바와 같이 가비지 컬렉션을 자동으로 해주는 언어로 프로그램을 개발하면 객체들이 사용하던 메모리가 자동으로 회수된다고 생각하여 메모리 관리를 전혀 고려하지 않아도 된다는 생각을 가질 수 있다. 그러나 이러한 생각은 잘못된 것이다.

[JAVA]
public class Stack {
private Object[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;

public Stack() {
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}

public void push(Object e) {
ensureCapacity();
elements[size++] = e;
}

public Object pop() {
if (size == 0)
throw new EmptyStackException();
return elements[--size];
}
// 배열에 요소를 저장하는데 필요한 공간을 확인하고 배열이 커질 필요가 있을 때는
        // 그 크기를 2배로 늘린다.
private void ensureCapacity() {
if (elements.length == size)
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}

상기 코드는 메모리 누출이 생긴다. 만일 스택이 커졌다가 줄어들면 스택에서 꺼냈던 객체들은 가비지 컬렉션되지 않을 것이다.  이렇듯 가비지 컬렉션이 지원되는 프로그래밍 언어에서 생기는 메모리 누출(의도하지 않은 객체 유지)은 우리가 모르는 사이에 진행된다.

이와 같은 문제에 대한 해결책은 매우 간단하다. 즉, 쓸모 없는 참조를 null로 만드는 것이다. 앞의 Stack 클래스에서는 스택에서 요소를 꺼내는 즉시로 그 요소의 참조가 쓸모 없게 된다.

[JAVA]
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null;  // 쓸모없는 참조를 제거한다.
return result;
}

쓸모없는 참조를 null 값으로 바꾸면 부수적인 장점이 생긴다. 이런 참조들이 나중에 실수로 사용된다면 NullPointerException 예외가 생긴다.

하지만 이와 같이 처리하는 것이 반드시 바람직하지 않다. 꼭 필요할 때만 예외적으로 행해야 한다. 이를 판단하는 기준으로는 자신의 메모리를 스스로 관리하는 부분에서만 사용한다. 즉 자신의 메모리를 자기가 관리한다면, 프로그래머는 항상 메모리 누출에 주의해야 한다.

메모리 누출이 흔히 생기는 또 다른 근원은 캐시(clone)이다. 객체 참조를 케시에 저장하면 저장했다는 것을 잊어 버리고 객체가 더 이상 필요 없을 때까지 캐시에 내버려 두기 쉽다. 이런일은 백그라운드 스레드(Timer 또는 ScheduledThreadPoolExecutor)로 처리하거나, 또는 새 EldestEntry 메소드를 사용하자.

메모리 누출의 세 번째 근원은 리스너와 콜백이다. 만일 클라이언트가 콜백을 등록하되 말소는 하지 않는 API를 구현한다면 콜백은 계속 누적될 것이다. 콜백이 신속하게 가비지 콜렉션 되도록 하는 가장 좋은 방법은 약한 참조만을 저장 유지하는 것이다. 예들 들어 WeakHashMap의 키로만 콜백을 저장한다.

메모리 누출은 잘 드러나지 않으므로 코드를 철저하게 검사하거나 힙 프로파일러와 같은 디버깅 도구의 도움을 받아야만 원인을 찾을 수 있다. 따라서 정답은 코드를 잘짜는 것이다.

$7. 파이널라이저의 사용을 피하자

파이널라이저는 예측 불가에다가 위험하기도 하고 일반적으로는 불필요하다. 하지만 몇가지 적합한 용도가 있다.

파이널라이저는 신속하게 실행된다는 보장이 없기 때문에 실행시간이 매우 중요한 작업을 절대 하지 말아야 한다. 예를 들면 파이널라이저에서 파일을 닫는 것은 매우 심각한 실수이다.

파이널라이저가 얼마나 빨리 실행되는가는 주로 가비지 컬렉션 알고리즘에 달려있으며, 그 알고리즘은 JVM 종류에 따라 다양하다. 또한 클래스에 파이널라이저를 사용하면 간혹 인스턴스들의 메모리 회수와 재활용이 지연될 수 있다.

이를 해결하기 위한 System.gc와 System.runFinalization 메소드들을 사용하고픈 유혼에 빠지지 말자. 만일 파이널라이즈를 하는 동안 catch 되지 않은 예외가 발생하면 그 예외는 무시되고 그 객체의 파이널라이즈는 종결된다.

파이널라이즈를 사용하면 엄청난 성능 저하가 발생한다. 그러면 파일이나 스레드처럼 종결 작업이 필요한 자원을 갖는 객체들의 클래스에서는 파이널라이저 대신 무엇을 써야 할까? 작업이나 자원을 정상적으로 종료하는 메소드만 별도로 추가하면 된다. 그리고 더 이상 필요 없는 각 인스턴스에 대해서 그 클래스의 클라이언트가 종료 메소드를 호출하도록 하면 된다. 이 때 주의 할것은 각 인스턴스에서 자신의 종료 여부를 유지 관리해야 한다. 즉, 종료 메소드에서는 해당 객체가 더 이상 유효하지 않다는 것을 private 필드에 기록해야 하며, 다른 메소드에서 이 필드 값을 확인하여 객체 종료후 호출되었다면 IllegalStateException 예외를 발생시켜야 한다.

가급적 종료 메소드는 try-finally와 함께 사용하여 확실하게 실행되도록 하자.

그렇다면, 파이널라이저는 어떤 경우에 사용하면 좋을까? 첫번째는 생성된 객체를 갖고 있는 코드에서 그 객체의 종료 메소드 호출을 빠뜨렸을 경우에 "안전망"의 역할을 하는 것이다. 즉, 클라이언트가 종료 메소드 호출에 실패하는 그런 경우를 대비해서 파이널라이저를 사용한다. 두번째는 네이티브 피어와 관련이 있다. 네이티브 피어는 네이티브 메소드를 통해 일반 자바 객체가 자신의 일을 위임하는 네이티브 객체이다. 네이티브 피어는 일반 자바 객체가 아니므로, 그것과 연관된 자바 피어 객체가 소멸되면 가비지 컬렉터가 알지 못하며 재활용 할 수도 없다. 따라서 이런 경우 파이널라이저가 적합할 수 있다.







[01] 개요

이펙티브 자바는 총 78개의 항목으로 구성되며, 자바의 기본 라이브러리인 java.lang, java.util, 그리고 java.util.concurrent와 java.io의 효과적인 사용에 대해서 설명한다.

대다수의 규칙은 다음 몇 가지 기본 원리를 근간으로 한다.

  • 명확함과 단순함이 가장 중요하다.
  • 모듈은 정해진 기능대로 수행되어야 하며, 가급적 크기를 작게 만들되 너무 작아지지 않게 한다.
  • 소스 코드를 복사 후 수정하여 다시 사용하는 형태가 아니고 기존 코드를 그대로 재사용해야 하며, 모듈간의 종속성은 최소화해야 한다.
  • 에러는 가능한 발생된 즉시 검출되어야 하며, 이상적으로는 컴파일 시점이 좋다.

이러한 규칙을 무작정 따라 하기보다는 타당한 이유를 갖고 어길 줄도 알아야 한다. 대부분의 다른 분야가 그렇듯, 프로그래밍 기술의 습득 또한 규칙을 먼저 배운 다음 그 규칙을 언제 깰 것인지 아는 것이 중요하다.

이책의 웹사이트인 http://java.sun.com/docs/books/effective에 방문하면 각 예제의 소스 코드를 받아서 컴파일과 실행을 해 볼 수 있다.




2010년 2월 8일 월요일

[02] 주요 개념

$1. 전체구조

 

안드로이드 오픈 소스 소프트웨어 스택을 구성하는 주요 계층과 구성요소를 포함한 전반적 시스템 아키텍처를 살펴보자.

 

 

각 층은 그 바로 아래 계층에서 제공하는 서비스를 이용한다.

 

1) 리눅스 커널

 

안드로이드는 리눅스 커널이라는 견고하고 검증된 토대 위에 구축되며, 이는 내부의 메모리관리, 프로세스 관리, 네트워킹, 운영체제 시스템 서비스 등에 이용된다.

 

2) 내장 라이브러리

 

커널 바로 위에 내장 라이브러리가 있다. 이 공유 라이브러리는 모두 C 또는 C++로 쓰여 있고 핸드폰에 사용되는 특정 하드웨어를 위해 컴파일되어 핸드폰 공급업체에 의해 폰에 미리 설치된다.

 

  • surface manager
    안드로이드는 매우 간단한 복합 윈도 매니저를 사용한다.
  • 2D와 3D 그래픽
    안드로이드에서는 2차원요소와 3차원 요소가 하나의 사용자 인터페이스에서 결합될 수 있다.
  • 미디어 코덱
    안드로이드는 동영상을 재생할 수 있고, 오디오 녹음과 재생이 가능한데 AAC, AVC, H.263, MP3, MPEG-4등의 다양한 포맷을 지원한다.
  • SQL 데이터베이스
    안드로이드는 경량의 SQLite 데이터베이스 엔진을 갖추가 있다. 이는 파이어폭스와 애플사의 아이폰에도 사용되는 데이터베이스다.
  • 브라우저 엔진
    HTML 콘텐츠를 신속하게 디스플레이하기 위해 안드로이드는 Webkit라이브러리를 사용한다. 이는 구글 크롬 브라우저, 애플의 사파리, 아이폰, 노키아 S60등의 플랫폼에 동일하게 사용되는 엔진이다.

3) 안드로이드 런타임

 

커널위에는 안드로이드 런타임인 달빅(Dalvik) VM과 코어 자바 라이브러리 등이 포함된다. 달빅 VM은 모바일 다바이스용으로 최적화해 구현된 구글의 자바 격이다.

 

달빅은 구글의 댄 본스타인이 만든 VM이다. 달릭이란 결국 낮은 메모리용으로 최적화된 자바 VM이다. 여러개의 VM 인스턴스가 한 번에 실행 가능하며, 내부 운영체제의 장점으로 보안과 프로세스의 격리가 가능하다. 달빅이라는 이름은 본스타인 조상이 살던 아이슬란드의 한 물고기 잡는 마을의 이름을 딴 것이다.


달빅과 전형적인 자바와의 차이점은 다음과 같다.

 

  • 달빅은 .dex파일을 실행시키는데 이 파일은 .class와 .jar파일이 컴파일 시점에 변환된 것이다. .dex파일은 클래스 파일보다 콤팩트하고 효율성이 높기 때문에 안드로이드가 사용될 적은 메모리와 베터리로 작동하는 환경에 적합하다.
  • 안드로이드에 들어 있는 코어 자바 라이브러리는 자바 스탠더드 에디션 라이브러리와 자바 모바일 에디션 라이브러리와는 다르지만 중복되는 부분이 상당히 있다.

4) 애플리케이션 프레임워크

 

어플리케이션을 만들 때 사용될 높은 수준의 빌딩 블록이 여기에서 제공된다. 필요시 개발자가 이를 확장시킬 수 있으며, 프레임워크의 가장 중요한 부분은 아래와 같다.

 

  • activity manager
    애플리케이션의 생명주기를 제어하고 사용자 내비게이션을 위해 'back-stack'을 유지한다.
  • content manager
    주소록과 애플리케이션 사이에서 공유되는 정보를 요약한다.
  • resource manager
    리소스란 프로그램의 코드 외 모든 부분인데 이를 관리하낟.
  • location manager
    안드로이드 폰은 항상 자신의 위치를 파악하고 있다.
  • notification manager
    도착 메시지, 약속, 근접성 알림 등의 사건을 사용자에게 방해되지 않는 방식으로 전달한다.
안드로이드의 강력한 특징 중 하나는 모든 애플리케이션이 동일한 수준으로 실행된다는 것이다. 이말은 시스템 애플리케이션이 개발자가 사용하는 것과 동일한 공개 API를 거쳐야 한다는 뜻인데, 원한다면 안드로이브가 기본 시스템 애플리케이션 대신 개발자가 만든 애플리케이션을 사용하게 만들 수 있다.


5) 애플리케이션

 

안드로이드 아키텍쳐의 최상위 계층이며, 최종사용자는 오직 이 어플리케이션만을 보게 된다.

 

$2. 살아있네!

 

안드로이드에는 foregraund 애플리케이션이 하나며, 상태 줄을 제외한 전체 스크린을 차지한다. 사용자가 애플리케이션을 실행하면, 안드로이드는 그 프로그램을 시작한 후, 포어그라운드에 놓는다. 거기에서 사용자는 다른 애플리케이션을 호출하거나 같은 애플리케이션 내의 다른 화면을 호출한다. 이 모든 프로그램과 화면은 시스템의 액티비티 매니저에 의해 애플리케이션 스택에 기록한다.

 

1) 프로세스는 애플리케이션과 다른다.

 

내부적으로 개별 사용자 인터페이스 화면은 Activity 클래스에 의해 표현된다. 액티비티는 생명주기를 갖고 있으며, 어플리케이션은 한 개 이상의 액티비티와 이것을 담을 리눅스 프로세스를 합친 것이다.

 

주의할것은 안드로이드상에서, 애플리케이션은 해당 프로세스가 죽여졌을 때조차도 살아 있을 수 이따. 즉, 액티비티의 생명주기가가 프로세스의 생명주기와 긴밀하게 결합되어 있지 않다. 프로세스란 액티비티를 자유롭게 담을 수 있는 그릇, 즉 컨테이너이다.

 

2) 액티비티 생명주기

 

안드로이드의 액티비티는 아래 그림의 여러 상태중 하나에 놓여 있다. 이는 개발자가 조정할 수 없으며 시스템에 의해 관리된다. 하지만 개발자는 상태가 변경될 때 onXX() 메서드가 호출되는 방식으로 알 수 있다.

 

 

Activity 클래스에서 해당 메소드를 적당하게 오버라이드하면, 안드로이드는 이를 적당한 시기에 호출할 것이다.

 

$3. 구성요소

 

1) 액티비티

 

액티비티란 사용자 인터페이스 화면을 일컫는다. 애플리케이션은 프로그램의 여러 단계를 다루기 위해 한 개 이상의 액티비티를 정의한다.

 

2) 인텐트

 

인텐트란 구체적인 행동을 설명하는 메커니즘이다. 안드로이드에서 거의 모든 것이 인텐트를 거치므로 구성요소를 대체하거나 재사용할 수 있게 해 준다.

 

3) 서비스

 

서비스란 유닉스 데몬처럼 사용자와의 직접적인 상호작용 없이 배경에서 실해되는 작업이다. 안드로이드에는 많은 내장 서비스와 그 서비스에 쉽게 액세스하게 해 주는 간편한 API가 제공된다.

 

4) 콘텐츠 제공자

 

콘텐츠 제공자란 데이터의 읽기와 쓰기를 위해 데이터가 사용자 API로 포장된 것이다.

 

$4. 리소스 사용하기

 

리소스란 프로그램에 필요한 지역화된 텍스트와 비트맵 등 코드 외의 작은 정보다. 안드로이드 리소스 컴파일러는 파일이 어느 하위 디렉토리에 있는지와 파일의 형식에 따라 처리한다. 예를 들어 PNG와 JPG형식의 비트맵은 res/drawable 디렉토리에 놓여야 하며 화면 레이어를 설명하는 XML 파일은 res/layout 디렉토리에 놓여야 한다.

 

$5. 안전과 보안

 

하드웨어는 한 프로세스가 다른 프로세스의 메로리에 액세스하는 것을 막고, 각 애플리케이션은 사용자 ID를 할당받아 애플리케이션이 만든 파일을 다른 애플리케이션이 읽거나 쓸 수 없다.

 

애플리케이션이 설치되면 패키지 매니저가 인증에 따라 권한을 허거하거나 제한하는데, 필요 시에는 사용자가 확인할 수 있다.

 

안드로이드는 AndroidManifest.xml 파일을 설정하여 시스템 전 영역의 액세스를 제한 할 수 있다.