2016년 11월 9일 수요일

[자바] Effective Java 4장 - 클래스와 인터페이스2

클래스와 인터페이스2

6. 추상클래스 대신 인터페이스를 사용한다.

  • 인터페이스의 필요성 : 자바는 다중상속이 불가능한 대신, 인터페이스를 이용하여 구현해야 할 메서드를 규정할 수 있다. 인터페이스는 다중 구현이 가능하다.
  • 인터페이스의 장점
    • 메인 자료형을 보조할 서브 자료형으로 이용가능하다.(상속관계에 상관없이 인터페이스를 추가할 수 있으므로)
    • 상속에 얽매이지 않는 자료형을 구현할 수 있도록 한다.
    • 추상클래스보다는 타입결합이 약하기 때문에 의존성 주입 관계에 적절하다.
  • 인터페이스 사용시 주의점
    • 인터페이스에서 정의되는 모든 접근권한은 public이기 때문에, API로써 한번 공개되면 수정이 거의 불가능하다.
  • 추상 골격 구현 : 제공하고자 하는 인터페이스와, 그 인터페이스의 핵심 기능을 미리 구현한 추상클래스를 제공하는 방법으로, 개발자는 그 추상클래스에 부족한 부분만 채움으로써 완전한 기능을 하는 클래스를 구현할 수 있다.
  • 자바 8버전부터는 인터페이스에 default 메서드를 이용하면 메서드 구현부를 제공할 수 있다.

7.인터페이스는 자료형을 정의할 때만 사용한다.

  • 인터페이스를 사용하는 목적 : 해당 클래스의 객체가 어떤 일을 수행하는지를 클라이언트에게 알리기 위한 목적
  • 잘못된 인터페이스 사용의 예
    • 상수 인터페이스 - 인터페이스 내부에 static final 필드만 존재하는 인터페이스

8.태그 달린 클래스 대신 클래스 상속을 활용한다.

  • 태그 달린 클래스? : 하나의 클래스가 2가지 이상의 기능으로 동작할 때, 어떤 기능으로 동작할지를 필드값으로 구분하는 클래스
  • 태그 달린 클래스의 단점
    • 여러 기능이 혼합되어 있어서 코드 가독성이 떨어진다.
    • 여러 기능을 위해 많은 필드가 존재하기 때문에, 한 객체가 자신의 기능에 불필요한 필드를 가지므로 메모리 낭비다.
    • 새로운 기능을 추가하려면 소스파일을 수정해야 한다.
  • 클래스 상속을 통해 기능단위로 클래스를 분리하면 태그 클래스의 모든 단점을 제거할 수 있다.

9.전략을 표현하고 싶을 때는 함수 객체를 사용한다.

  • 자바에서는 함수포인터의 역할을 객체가 대신한다.
    • 필요한 메서드를 정의한 인터페이스를 준비하고, 그 인터페이스를 구현한 객체를 전달
  • 전략 패턴 : 어떤 기능을 구현할 때, 중요한 메인 로직을 상황에 따라 변경할 수 있도록 함수(메서드)를 전달받아 그 함수에게 메인 로직을 위임하는 패턴
  • ex : 배열의 정렬방법을 compare라는 메서드에 위임
Arrays.sort(stringArray, new Comparator<String>() {
    @Override
    public int compare(String s1, String s2) {
        return s1.length() - s2.length();
    }
});

10.중첩 클래스는 가능하면 static으로 선언한다.

  • 외부 클래스에 종속되는 중첩 클래스가 아니라면 static으로 선언해야 한다.
  • non-static 중첩 클래스는 외부 클래스의 참조를 유지해야 하기 때문에, 성능&공간 측면에서 static 중첩 클래스보다 안좋다.
  • ex 외부클래스 참조 예시
class A {
    class B {
        public void test() {
            // A.this 문법으로 외부 클래스의 객체를 참조
            A.this.print();
        }
    }
    
    void print() {
        System.out.println("class A");
    }
}

2016년 11월 7일 월요일

[자바] Effective Java 4장 - 클래스와 인터페이스1

클래스와 인터페이스1

1. 클래스와 멤버의 접근권한은 최소화하라

  • 정보은닉 : 필요한 정보 외의 모든 구현 세부사항을 다른 모듈에서 접근하지 못하도록 감추는 것
  • 정보은닉은 왜 중요한가?
    • 모듈간 의존성이 낮아진다. -> 재사용, 리팩토링이 쉽다.
    • 효율적인 성능튜닝을 할 수 있다. -> 모듈별로 모니터링하기 쉽다.
  • 클래스에서의 정보은닉 : 불필요한 멤버필드 & 메서드를 접근권한을 통해 감추는 것
    • private : 최상위 레벨 클래스 내부에서만 접근 가능.
    • package-private(default) : 같은 패키지 내의 아무 클래스나 접근 가능. 자바에서는 명시적으로 접근권한을 설정하지 않으면 기본적으로 이 권한이 주어진다.
    • protected : 해당 클래스 및 그 하위 클래스에서만 접근 가능
    • public : 어디에서든 접근 가능
  • 멤버필드는 절대로 public 권한으로 선언하면 안된다.
  • 꼭 공개가 필요한 멤버필드는 public static final 을 이용해 공개하려는 변수를 상수로 선언하는 것을 고려해본다. (단, 기본타입에 한해서... 참조타입은 상수이지만 값을 변경할 수 있다.)

2. 클래스 안에는 public 필드를 두지 말고 접근자 메서드를 사용한다.

  • pulbic 멤버필드는 정보은닉의 이점을 누릴 수 없다.
  • 멤버필드를 private / protected로 변경하고, public gettersetter 접근 메서드를 이용하면 정보은닉의 이점을 100% 누릴 수 있다.
  • package-private / private 중첩 클래스의 멤버필드는 경우에 따라서 public으로 선언하여 코드가독성을 높이기도 한다.
    • 이 중첩클래스들은 바깥의 클래스만 접근이 가능하기 때문에, 정보은닉을 어기는 사례가 아님.

3. 변경 가능성을 최소화한다.

  • 변경 불가능(immutable) 클래스 : 객체를 수정할 수 없는 클래스(ex String 클래스)
  • 변경 불가능 클래스의 이점
    • 설계&구현이 쉽다.
    • 오류 가능성이 적다.
    • 멀티 쓰레드에서 동시성 접근에 대한 걱정을 안해도 된다.
  • 변경 불가능 클래스의 단점
    • 값마다 객체를 새로 생성해야 한다.
  • 변경 불가능 클래스 만드는 방법
1. 객체 상태를 변경하는 setter 메서드를 제공하지 않는다.
2. 상속 받을 수 없도록 한다. class를 선언할 때 final 키워드를 이용하면 상속금지 클래스가 된다.
3. 모든 필드를 final로 선언한다.
4. 모든 필드를 private으로 선언한다.
5. 변경 가능 필드를 제공해야 한다면, 복사본을 만들엉 제공한다.

4. 상속하는 대신 구성한다.

  • 상속 : 클래스를 재사용하기 위해 해당 클래스의 자식 클래스로 구현하는 방법. 부모 클래스로 부터 private 이외의 필드, 메서드를 물려받을 수 있다.
  • 상속의 단점
    • 부모 클래스가 변경되면, 그 여파가 자식클래스까지 미친다.
class A {
    protected int age;
    public int getAge() {
        return age;
    }
}

class B extends A {
    @Override
    public int getAge() {
        age += 5;
        return getAge();
    }
}
  • 구성 : 물려받고자 하는 클래스의 기능을 이용하기 위해 새로운 클래스 내부에 해당 클래스를 private 필드로 선언하고, 이용하고자 하는 메서드를 호출하여 결과를 반환하도록 래핑(Wrapping)하는 방법
  • 구성의 단점
    • callback 구현방식과 함께 사용하기에는 적합하지 않다.
class A {
    private int age;
    public int getAge() {
        return age;
    }
}

class B {
    private A a;
    public getAge() {
        return a.getAge() + 5;
    }
}
  • 즉, 상속이 꼭 맞는 경우가 아니라면 구성 방법을 고려한다.

5. 상속을 위한 설계와 문서를 갖추거나, 그럴 수 없으면 상속을 금지한다.

  • 일반적으로 상속 가능 클래스를 설계하는 것 보다, 구성 방법을 고려하거나, 가능하다면 변경 불가능 클래스로 만들어 상속을 금지하는 것이 훨씬 쉽다.
  • 상속 클래스를 작성하려면 적절한 개수의 protected 멤버를 유지한다.
    • protected 멤버가 너무 많으면 관리하기 힘들다.
    • protected 멤버가 너무 적으면 상속하는 의미가 별로 없는 클래스가 된다.
  • 상속을 허용하는 클래스를 작성할 때의 주의사항
1. 생성자에서 재정의 가능 메서드를 호출하면 안된다. 상속관계에서 부모클래스의 생성자가 자식클래스의 생성자보다 먼저 불리기 때문에, 재정의 가능 메서드의 값이 하위 클래스 생성자에 의존하게 되면 문제가 생긴다.
2. Cloneable, Serializable 인터페이스를 구현하는 클래스라면, clone(), readObject() 메서드도 생성자와 같은 역할을 하므로, 위의 규칙을 지켜야 한다.
3.  상속 가능한(public / protected) 메서드가 내부적으로 어떻게 동작하는지 문서화해야 한다. 자식클래스가 그 메서드를 재정의할 때, 그 문서를 보고 부모 메서드의 행동을 가늠하여 구현되기 때문이다.

2016년 11월 4일 금요일

[자바] Effective Java 3장 - 모든 객체의 공통 메서드

모든 객체의 공통 메서드

1. equals를 재정의할 때는 일반 규약을 따른다.

  • equals() : 주어진 객체를 비교하여 그 객체가 같은지 여부를 반환하는 메서드
  • 가장 확실한 방법은 equals() 메서드를 재정의하지 않는 것이다. 이 경우, 그 클래스의 객체는 유일한 객체가 된다.
  • equals() 메서드를 안써도 되는 조건(하나라도 만족한다면 재정의할 필요 없다.)
    • 각각의 객체가 고유하다.
    • 클래스에 논리적 동일성 검사방법이 있든지 없든지 상관없다.
    • 상위 클래스의 equals()를 하위 클래스에서도 잘 사용할 수 있다.
  • equals()메서드를 재정의할 필요가 있는 조건
    • 논리적 동일성을 지원해야 할 때(ex 문자열객체 비교)
    • 상위 클래스의 equals()가 하위 클래스에 맞지 않을 때
  • 대부분의 자바 API 클래스는 equals() 메서드가 재정의되어 있다고 가정하고 구현되어 있다.
  • equals()메서드를 재정의하기 위해 점검해야 할 규칙
1. == 연산자를 사용하여 equals()의 인자가 자기 자신인지 검사한다.
2. instanceof 연산자를 이용해 인자의 자료형이 정확한지 검사한다.
3. 2에서 검사한 인자를 정확한 자료형으로 변환한 후에 검사를 진행한다.
4. 현재 객체와 비교하고자 하는 객체의 필드 각각이 일치하는지 검사한다.(중요)
5. equals() 메서드를 구현할 때, hashCode()도 재정의하여 구현한다.

2. equals를 재정의할 때는 반드시 hashCode도 재정의한다.

  • hashCode() : 객체를 구분하는 해쉬값을 반환하는 메서드
  • equals() 메서드가 같다고 판정한 두 객체의 hashCode() 값은 반드시 같아야 한다.
    • equals() 메서드를 구현하고, hashCode() 메서드를 구현하지 않으면 자바의 해쉬기반 API에서 오동작을 일으키게 된다.
  • 해쉬함수를 만들 때, equals() 메서드에서 비교를 위해 사용된 필드를 이용하도록 한다. 그래야 같은 필드값일 때 해쉬값이 똑같이 나오게 된다.
  • hashCode()메서드를 재정의하기 위한 규약
1. 프로그램이 실행중일 때, 같은 객체의 hashCode메서드를 여러 번 호출하는 경우 언제나 동일 결과가 나와야 한다.(종료되었다가 재시작될 때에는 다른 값이 나와도 된다.)
2. equals 메서드가 같다고 판단한 두 객체의 hashCode메서드 결과는 같아야 한다.
3. equals 메서드가 다르다고 판단한 두 객체의 hashCode메서드 결과는 같아도 되고 달라도 되지만, 다를경우 해시테이블의 성능이 향상된다는 것을 기억한다.

3. toString은 항상 재정의한다.

  • toString() : 주어진 객체를 문자열로 반환하는 메서드.
  • Object.toString() 메서드는 "클래스명@해시코드" 형태이므로 유용하지 않다.
  • toString()메서드는 제약,규약이 존재하지 않지만, 잘 정의한 메서드는 클래스를 좀 더 유용하게 이용하도록 도와준다.
    • 객체 내의 중요 정보(필드)를 모두 담아 반환
    • 문자열의 내용을 하눈에 파악할 수 있도록
    • toString()메서드에서 제공하는 정보를 파싱용으로 이용하면 안되므로, 제공하는 모든 정보는 프로그래밍적으로 가져올 수 있도록 제공한다.

4. clone을 재정의할 때는 신중하게 한다.

  • clone() : 객체를 복사하여 반환하는 메서드. Cloneable인터페이스를 구현하지 않으면 CloneNotSupportedException을 던진다.
  • clone()메서드를 재정의하기 위한 규약
1. final 이 아닌 클래스에서 재정의할 때는 반드시 super.clone()을 호출해 얻은 객체를 반환해야 한다.
2. clone()을 구현할 때 원래 객체를 손상시키지 않도록 하고, 복사본의 불변식도 제대로 만족시켜야 한다.
3. 객체복사를 굳이 제공해야 한다면, clone()메서드 보다는 복사생성자 or 팩터리 메서드를 제공하는 방안을 검토한다.

5. Comparable 구현을 고려한다.

  • compareTo() : equals() 메서드와 비슷하게, 두 객체를 비교하여 순서를 반환하는 메서드이다. 이 메서드는 Comparable 인터페이스 내부에 존재한다.
  • 시스템에서 제공하는 정렬, 검색 등의 편의성 메서드들은 내부적으로 모두 compareTo() 메서드를 이용한다. 그러므로, 객체간의 순서가 중요한 클래스는 가급적 구현하는 것이 좋다.
  • compareTo() 메서드를 재정의하기 위한 규약
1. 객체간 비교순서를 바꿔도 객체간의 대소관계는 그대로 유지되어야 한다.
2. compareTo를 이용한 동치 검사 결과는 일반적으로 equals 메서드 실행 결과와 같아야 한다.

2016년 11월 3일 목요일

[자바] Effective Java 2장 - 객체의 생성과 삭제

객체의 생성과 삭제

1.생성자 대신 Static Factory 메서드를 이용할 수 있는지 고려하라

  • Static Factory 메서드 : 생성자 대신 객체를 생성해주는 static 메서드.
  • ex
public class A {
 public static A getInstance() {
  return new A();
 }
}
  • 장점
    • 생성자와는 달리 메서드의 이름을 지정해 줄 수 있어서 코드의 가독성이 올라간다.
    • 생성자처럼 매번 새로운 객체를 생성할 필요는 없다. 동일한 객체의 요청이 잦은 경우에는 객체를 생성해두고 캐싱한 후에 필요할 때마다 캐싱된 객체를 반환한다.
    • 생성자와는 달리 return 타입이 자유로워서 유연한 코드의 작성이 가능하다.
  • 단점
    • 모든 생성자를 private으로 감추고 Static Factory 메서드만 지원할 경우 서브클래스를 생성할 수 없다.
    • Static Factory 메서드가 다른 일반 Static 메서드와 확연히 구분되지 않는다.

2.생성자 인자가 많을 때는 Builder 패턴 적용을 고려하라

  • Static Factory 메서드, 생성자는 선택적 인자가 많은 상황에 적절한 대응이 어렵다.
  • 대안
    1. 점층적 생성자 패턴
      • 모든 선택적 인자에 대한 생성자를 준비하여 필요한 생성자를 호출하도록 하는 방법.
      • 인자가 많아지면 코드 가독성이 떨어진다.
    2. 자바빈 패턴
      • 인자없는 생성자로 객체를 생성한 뒤 setter 메서드로 각 인자를 넣는 방법.
      • 1회성 함수 호출로 객체 생성을 완료할 수 없으므로 객체 일관성이 일시적으로 깨진다.
      • 변경 불가능(immutable) 객체를 만들 수 없는 단점이 있다.
    3. Builder 패턴
      • 별도의 빌더 객체가 선택적 인자들을 받아서 객체를 생성하도록 하는 방법
// Builder 패턴 예시
public class A {
 private final int requiredField1;
 private final int requiredField2;
 private final int optionalField1;
 private final int optionalField2;
 private final int optionalField3;

 public static class Builder {
  // 필수 멤버
  private final int requiredField1;
  private final int requiredField2;
  // 선택 멤버
  private int optionalField1 = 0;
  private int optionalField2 = 0;
  private int optionalField3 = 0;

  public Builder(int required1, int required2) {
   this.requiredField1 = required1;
   this.requiredField2 = required2;
  }

  public Builder optionalField1(int optional1) {
   this.optionalField1 = optional1;
   return this;
  }
  public Builder optionalField2(int optional2) {
   this.optionalField2 = optional2;
   return this;
  }
  public Builder optionalField3(int optional3) {
   this.optionalField3 = optional3;
   return this;
  }

  public A build() {
   return new A(this);
  }
 }

 public A(Builder builder) {
  this.requiredField1 = builder.requiredField1;
  this.requiredField2 = builder.requiredField2;
  this.optionalField1 = builder.optionalField1;
  this.optionalField2 = builder.optionalField2;
  this.optionalField3 = builder.optionalField3;
 }
}

// 이용
A obj = new A.Builder(1, 2).optionalField1(3).optionalField2(4).optionalField3(5).build();
  • Builder 패턴 장점
    • 인자에 불변식을 적용할 수 있다.
    • 빌더 객체는 여러개의 가변인자를 받을 수 있다.
    • Static Factory 메서드나 생성자보다 유연하다.
  • Builder 패턴 단점
    • 객체를 생성하기 위해 빌더 객체를 생성하는 오버헤드가 있다.
    • 빌더 패턴은 많은 코드를 요구하기 때문에 인자가 충분히 많은 상황에서 사용해야 한다.

3.싱글턴 패턴을 사용할 때, private 생성자나 enum 자료형으로 설계하라

  • 싱글턴 패턴의 단점
    • 싱글턴 객체를 Mock으로 대체하기가 어렵기 때문에 테스트가 어렵다.
    • 생성자를 private으로 만들더라도 리플렉션 API를 이용하면 private 생성자를 호출할 수 있다.
    • 직렬화에 대한 처리를 해주어야 한다.
//Private으로 선언된 생성자 호출하는 예제
public class PrivateInvoker {
 public static void main(String[] args) throws Exception {
  //리플렉션 setAccessible 메서드를 통해 private으로 선언된 생성자 호출 권한을 획득
  Constructor<?> con = Private.class.getDeclaredConstructor()[0];
  con.setAccessible(true);
  Private p = (Private) con.newInstance();
 }
}

class Private {
 private Private() {
  System.out.println("이것은 private 생성자 입니다.");
 }
}
  • 싱글턴 패턴 구현방법 3가지
    1. private 생성자 + public static 필드
    2. private 생성자 + static factory 메서드
    3. enum 자료형(JDK 1.5부터 가능)
// 1.private 생성자 + public static 필드
public class SingletonClass {
 public static final SingletonClass INSTANCE = new SingletonClass();
 private SingletonClass() {
  ...
 }
 public void leaveTheBuilding() {
  ...
 }
}

// 2.private 생성자 + static factory 메서드
public class SingletonClass {
 private static final SingletonClass INSTANCE = new SingletonClass();
 public static SingletonClass getInstance() {
  return INSTANCE;
 }
 private SingletonClass() {
  ...
 }
 public void leaveTheBuilding() {
  ...
 }
}

// 3.enum 자료형
public enum SingletonEnum {
 INSTANCE;
 
 public void leaveTheBuilding() {
  ...
 }
}

4.객체 생성이 필요 없을 때는 private 생성자를 사용하라

  • static field들이 모여있는 클래스를 설계할 경우에는 객체를 생성할 필요가 없다.
  • 객체 생성을 못하게 하려면 private 생성자를 클래스에 명시적으로 선언해 준다.
  • 필요없는 생성자를 선언한 것이므로 Javadoc 등으로 주석처리를 해두면 좋다.
public class UtilityClass {
 // 기본 생성자가 자동 생성되지 않도록 하여 객체 생성 방지
 private UtilityClass() {
  throw new AssertionError();
 }
}

5.불필요한 객체는 만들지 말라

  • 기능적으로 동일한 객체는 필요할 때마다 만드는 것보다 캐싱하여 재사용하는 편이 낫다.
    • Static 초기화 블록을 이용할 것을 고려한다.
  • Wrapper 클래스의 Auto Boxing기능을 이용하면 객체가 추가로 생기게 된다.(자동 객체화)
    • 필요한 경우가 아니라면 자동객체화로 불필요한 객체가 생성되지 않도록 하자.
//Auto-Boxing
Integer intObject = 5;      //Integer객체가 생성됨.
  • 생성비용이 싼 객체라면 코드 가독성을 고려하여 Object Pool 사용을 자제한다.

6.유효기간이 지난 객체의 참조는 폐기하라

  • 자바의 메모리 누수 : 객체의 참조가 해제되지 않아서 가비지 컬렉터가 해당 객체를 수집하지 못하여 메모리에 남아있는 현상
    • 자바에서 한 객체가 누수될 경우, 그 객체뿐만 아니라 그 객체가 참조하고 있는 모든 객체가 함께 누수된다.
  • 해결방법
    1. 쓸 일 없는 객체의 참조는 무조건 null로 만든다.
    2. 해당 객체의 scope를 최대한 좁게 만들어서, 그 객체가 스스로 scope 밖으로 벗어나게 설계한다.
    3. WeakReference를 이용하거나, 내부적으로 WeakReference를 이용해 구현된 API를 이용한다.

7.finalize() 사용을 피하라

  • Java의 finalize는 C++의 destructor와 성격이 다르다.
    • C++ Destructor : 객체에 할당된 메모리를 해제하는 역할. 생성자의 정 반대
    • Java finalize : 객체가 GC에 의해 수집되기 직전에 마지막으로 호출될 메서드
  • finalize를 사용하면 안되는 이유
    1. 언제 실행될 지 예측할 수 없다. 심지어 실행되지 않을 수도 있다.
    2. 프로그램의 성능이 심각하게 저하된다.
  • finalize를 사용하기 적합한 곳
    1. 명시적 종료 메서드(close() 등) 호출을 잊을 경우를 대비할 경우
    2. 자바의 native 메서드를 통해 기능 수행을 위임하는 네이티브 객체의 경우
  • finalize를 사용해야할 상황이라면 상위 객체의 finalize를 반드시 호출해준다.