sungsu9022 / study-effective-java-3e

study for effective java 3 edition
1 stars 0 forks source link

이펙티브 자바 3판 - 2. 객체 생성과 파괴~ 3.모든객체의 공통메소드(item11) #6

Open iamsunk opened 5 years ago

iamsunk commented 5 years ago

finalizer 와 cleaner 의 사용을 피하라

GC는 컨트롤 가능한가?

try - finally 보다 try-with-resource를 사용하라

static void copy(String src, String dst) throws IOException { try (InputStream in = new FileInputStream(src); OutputStream out = new FileOutputStream(dst)) { byte[] buf = new byte[BUFFER_SIZE]; int n; while ((n = in.read(buf)) >= 0) out.write(buf, 0, n); } }

## equals 는 일반 규약을 지켜 재정의하라
### 언제 재정의 해야하는가 
- 목표는 객체 식별성이 아니라 논리적 동치성을 확인해야 한다. 
- 상위 클래스가 논리적 동치성을 비교하도록 되어있지 않을 경우다. 
- 주로 값을 표현하는 클래스들이 해당한다.(ex: integer, String)
- 값 클래스라 하더라도 인스턴스가 둘 이상 만들어지지 않는(Enum) 의 경우도 재정의가 불필요하다. (스태틱도?)

- 대칭성
    * 상 하위 모두 호환 가능해야한다.
    *  caseinsensitiveString 은 string을 알고 있지만, 반대는 아니다.

public final class CaseInsenstiveString{ private final String s; public CaseInsensitiveString(String s){ this.s = Objects.requireNorNull(s); } }

@Override public boolean equals(Object o) { if (o instanceof CaseInsensitiveString) return s.equalsIgnoreCase( ((CaseInsensitiveString) o).s); if (o instanceof String) // 한 방향으로만 작동한다! return s.equalsIgnoreCase((String) o); return false; }

@Override public boolean equals(Object o) { return o instanceof CaseInsensitiveString && ((CaseInsensitiveString) o).s.equalsIgnoreCase(s); }


- 추이성
    * 하위 상속 클래스와 같고 상속하는 클래스와 그 하위의 클래스와 같다면, 최상위와 최 하위도 같아야 한다.
    * 아래는 좌표멤버를 멤버로(x,y) 같는 Point클래스, 상속하는 멤버(Color) 를 갖는 상속 클래스이다. 이 경우 equals의 동작은 어떠할가. 동작은 하지만 색상 정보를 놓치니 용납할 수 없다.

public class Point { private final int x; private final int y;

public Point(int x, int y) {
    this.x = x;
    this.y = y;
}

@Override public boolean equals(Object o) {
    if (!(o instanceof Point))
        return false;
    Point p = (Point)o;
    return p.x == x && p.y == y;
}

}

public class ColorPoint extends Point { private final Color color;

public ColorPoint(int x, int y, Color color) {
    super(x, y);
    this.color = color;

}

대칭성 실패 @Override public boolean equals(Object o) { if (!(o instanceof ColorPoint)) return false; return super.equals(o) && ((ColorPoint) o).color == color; }

추이성 실패 @Override public boolean equals(Object o) { if (!(o instanceof ColorPoint)) return false; ColorPoint cp = (ColorPoint) o; return cp.point.equals(point) && cp.color.equals(color); }

@Override public boolean equals(Object o) { if (o == null || o.getClass() != getClass()) return false; Point p = (Point) o; return p.x == x && p.y == y; }


- 작은 결론 1 : 하위 클래스를 구체화 하면서 모든 규칙을 지킬 수 없다. 
- 그럼 방법은? 상속 대신 컴포지션을 활용하라 
    - 상위 클래스 타입의 변수를 멤버로 두고 그것을 반환하는 뷰 메소드를 public으로 작성한다.
    - 아무 값도 갖지 않는 클래스를 베이스로 두고 확장한다.(베이스 클래스를 인스턴스하지 않기 때문에 위배되지 않는다)

public class ColorPoint { private final Point point; private final Color color;

public ColorPoint(int x, int y, Color color) {
    point = new Point(x, y);
    this.color = Objects.requireNonNull(color);
}

/**
 * 이 ColorPoint의 Point 뷰를 반환한다.
 */
public Point asPoint() {
    return point;
}

@Override public boolean equals(Object o) {
    if (!(o instanceof ColorPoint))
        return false;
    ColorPoint cp = (ColorPoint) o;
    return cp.point.equals(point) && cp.color.equals(color);
}

}

- equlas 의 판단에 가변적인 자원이 사용되지 않도록 한다.
    * java.net.URL의 equals가 예다. URL과 ip의 호스트를 비교하는데 이때 네트웍을 통하게 된다.
- 모든 객체는 null 이 아니어야한다.
    * 동치성 검사를 위해 적절한 형변환 후 값을 비교한다.
    * instanceof는 비교하는 객체가 null인지 검사한다.(그래서 ==null 을 할 필요 없다)

- primitive type (float, double 제외) 는 "==" 연산자로 비교하고, 레퍼런스 타입 필드는 equals 로 float,double은 compare 메소드로 비교한다. 
- 성능이 걱정된다면 cost가 적을 것 같은 필드부터 비교한다. 
- 상태(lock)등을 연산하지 말자. (논리적 상태만을 비교하자)
- 캐쉬된 값을 저장하는 파생클래스 변수가 있을 경우 활용하자.

- 완벽한 equals 를 보여주는 코드

public final class PhoneNumber { private final short areaCode, prefix, lineNum;

public PhoneNumber(int areaCode, int prefix, int lineNum) {
    this.areaCode = rangeCheck(areaCode, 999, "지역코드");
    this.prefix   = rangeCheck(prefix,   999, "프리픽스");
    this.lineNum  = rangeCheck(lineNum, 9999, "가입자 번호");
}

private static short rangeCheck(int val, int max, String arg) {
    if (val < 0 || val > max)
        throw new IllegalArgumentException(arg + ": " + val);
    return (short) val;
}

@Override public boolean equals(Object o) {
    if (o == this)
        return true;
    if (!(o instanceof PhoneNumber))
        return false;
    PhoneNumber pn = (PhoneNumber)o;
    return pn.lineNum == lineNum && pn.prefix == prefix
            && pn.areaCode == areaCode;
}

}

- 주의사항
    * Object 타입 이외의 파라메터로 구현하지 말자.(이것은 재정의가 아니라 다중정의다.)
- 좋은 툴(라이브러리
    * 구글의 AutoValue가 있다. 알아서 해준다. ide에서도 해준다( 결국 ide, 괜찮은 lib을 쓰자)

## equals 를 정의할땐 hashcode도 정의하자

### Hashmap, hashSet등의 원소로 사용될 경우 문제가 발생한다.

HashMap<PhonNumber,String> m = new Hashmap<>(); m.put(new PhoneNumber(707, 867, 5309),"jenny"); m.get(new PhoneNumber(707, 867, 5309));

what happened?
- return null
- 넣을때 한번, 꺼낼때 한번 객체를 생성했다. 하지만 이들은 논리적 동치이다. 둘의 해쉬가 다르기 때문이다.
- PhoneNumber 클래스에 dashcode 를 적절하게? 구현해준다.

@override public int hashcode(){ return 42;}

- 이렇게 하면 O(1) 이 아니라 O(n) 이 된다. 모든 해쉬가 같기 때문에~ . 결국 다른 객체에 다른 해쉬를 반환하도록 하는것이 성능을 고려하는 측면이다.
- 왠지 나만 모를 것 같은 내용 (https://ratsgo.github.io/data%20structure&algorithm/2017/10/25/hash/) 
- 해쉬함수 만드는 방식 
   *
- 위 PhoneNumber 클래스 해시함수 예시

@Override public int hashCode() { int result = Short.hashCode(areaCode); result = 31 result + Short.hashCode(prefix); result = 31 result + Short.hashCode(lineNum); return result; }

@Overrride public int hashCode(){ return Object.hash(lineNum,prefix,areaCode); } // 나라면 그냥 이렇게 쓸듯 하지만 성능이 아쉽다고 한다.

- 성능을 고려하는 목저으로 핵심필드를 빼고 hashcode를 정의하지 말자.(해시테이블 성능을 놓치게된다)

## toString을 항상 재정의하라
### 항상 적합한 문자열을 반환하지 않는다.
- PhoneNumber@adbbb (클래스이름@16진수해쉬코드) 를 반환한다.
- 하위 클래스에서는 간결하고 읽기 쉬운(핵심필드 들을) 형태로 toString을 정의해주는 것이 좋다.
- 문제(디버깅)에 용이하게 만든다.
- 모든 핵심 필드들을 출력하는 것이 좋다.
/**
 * 이 전화번호의 문자열 표현을 반환한다.
 * 이 문자열은 "XXX-YYY-ZZZZ" 형태의 12글자로 구성된다.
 * XXX는 지역 코드, YYY는 프리픽스, ZZZZ는 가입자 번호다.
 * 각각의 대문자는 10진수 숫자 하나를 나타낸다.
 *
 * 전화번호의 각 부분의 값이 너무 작아서 자릿수를 채울 수 없다면,
 * 앞에서부터 0으로 채워나간다. 예컨대 가입자 번호가 123이라면
 * 전화번호의 마지막 네 문자는 "0123"이 된다.
 */

@Override public String toString() { return String.format("%03d-%03d-%04d", areaCode, prefix, lineNum); }



- 하위 클래스에서 상위클래스의 적절한 toString이 있다면 말고 없다면 꼭 구현해주어야한다.