레이블이 Effective인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Effective인 게시물을 표시합니다. 모든 게시물 표시

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 getter, setter 접근 메서드를 이용하면 정보은닉의 이점을 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 메서드 실행 결과와 같아야 한다.