Item11. equals를 재정의시에는, hashCode도 같이 재정의하라.

hashCode method는 해싱(hashing) 기법에 사용되는 해시함수를 구현한 것이다.

hashCode가 반환해주는 결과값(해시코드값)은 hashTable,hashMap내 실제값이 저장되는 위치를 알려주는 일종의 인덱스로 사용된다.

Object 명세에 보면 다음과 같이 기술되어 있다.

(https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#hashCode())

1
2
3
4
5
6
7
8
9
10
11
12

1. equals 비교에 사용되는 정보가 변환되지 않았다면,
app이 실행되는 동안 객체의 hashCode값은 항상 같아야 함

2. equals(Object)가 두 객체를 같다고 판단했다면,
두 객체의 hashCode는 똑같은 값을 반환해야 한다.

=> 논리적으로 같은 객체는 같은 해쉬코드값을 반환해야 함

3. equals(Object)가 두 객체를 다르다고 판단했더라도,
두 객체의 hashCode 값이 다를 필요는 없음

  • Java HashMap은 hash함수 적용할떄, hashCode값을 사용함으로 다른 두 객체가 hashCode값이 같다면 두 객체는 같은 인덱스로 해쉬됨(collision)으로 성능상 단점이 있음.
    (Jdk 8 hashMap:해시 키 값 충돌 시 separtate chaining 방식 사용)
Read more

Item10. equals는 일반 규칙을 지켜 재정의하라

Java에서 최상위 class인 Object의 final이 아닌 methode들 (equals, hashCode, toString ,clone, finalize)는 모두 overriding을 염두에 두고 설계된 것이라, overriding시 지켜야 하는 일반 규약이 명확하다.

1
public boolean equals(Object obj);

이 중에서 equals method는 == 연산자와 마찬가지로 참조변수의 참조값을 비교하도록 정의되어 있는데, 다음과 같은 상황에서는 overriding 하지 않는 것이 좋다.

1
2
3
4
5
6
7
1. 각 인스턴스가 고유한 경우  ex) Thread와 같이 객체가 값을 표현하는 목적이 아닌 경우

2. 인스턴스가 논리적으로 동일한지 검사할 일이 없는 경우

3. 상위 class에서 재정의한 equals가 하위 클래스에도 적합한 경우

4. 클래스가 private 이거나, package-private이고, equals method가 호출될 일이 없는 경우
Read more

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

코딩을 하다보면 close method를 통해 반환해주어야 하는 리소스가 많다(java.sql.Connection, java.io 관련 리소스 ex) InputStream,OutputStream ) , 개발자가 실수로 리소스 정리를 깜빡하는 경우가 있다.

주로 자원을 반환할떄 사용하던 try- finally 구문은 다음과 같다.

1
2
3
4
5
6
7
8
9

static String firstLineOfFile(String path) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
try{
return br.readLine();
} finally {
br.close();
}
}

위 try - finally 의 단점은 다음과 같다.

  • 중첩 try - finally 문 작성으로 코드가 장황해지고, 디버깅이 힘들어질수 있다.
Read more

Item8. finalizer와 cleaner 사용을 피해라

Java는 두가지 객체 소멸자를 제공한다.

  1. finalizer

  2. cleaner

여기서 객체 소멸자란 생성자와 정반대의 개념으로, 객체가 소멸될떄(GC될때)
자동으로 호출되는 함수이다.

Java 9에서부터는 finalizer를 사용 자제해야 할 API로 지정하고, cleaner를 그 대안으로 소개하였다. 하지만 책에서는 cleaner또는 finalizer의 사용을 피하라고 얘기하고 있다,.그렇다면 finalizer와 cleaner는 왜 사용하면 안될까?

1
2
3
4
5
@Override // deprecated 
protected void finalize() throws Throwable {
super.finalize();
}

단점 1. finalizer와 cleaner는 즉시 수행된다는 보장이 없다.

객체의 소멸을 GC가 담당하므로, finalizer와 cleaner를 언제 수행할지의 역할은 전적으로 GC 알고리즘에 달려있다. 즉 GC 구현마다 차이가 있다.

위와 같은 특징들을 보면 당연히 finalizer와 cleaner로는 제때 실행되어야 하는 작업은 절대 할 수 없다.

단점 2. finalizer는 instance 반납을 지연시킬 수도 있다.

finalizer 쓰레드는 우선순위가 낮아서 , 언제 실행될지 모른다. -> 계속 대기 된다면 GC되지 않고, OutOfMemoryException이 발생할수도 있다.

단점 3. finalize 동작 중에 발생한 에러는 무시되며, 처리할 작업이 남았더라도, 그 순간 종료된다.

중간에 예외가 터지면, 객체가 훼손된 상태로 남아있을수도 있다.

단점 4. 성능 문제

AutoCloseable 객체를 만들고, try-with-resoucre 로 자원 반납을 하는데 걸리는 시간은 12ns 인데 비해, Finalizer는 550ns 거의 약 50배가 걸렸다. cleaner도 60ns로, 5배정도 느리다.

그렇다면 finalizer나 cleaner를 어떻게 대체해주어야 하는가?

  • 반환할 자원 class에 AutoCloseable 을 구현해주고, client에서 instance를 다 쓰고 나면 close method을 호출해주면 된다.

finalizer와 cleaner의 적절한 용도

  1. 자원 소유자가 close method를 깜빡하고 호출하지 못하였을때, 안전망 역할을 해준다.

  2. 자바 객체가 아닌 native peer 회수

cleaner 사용예시 - 리소스 반환을 위한 안전망 역할

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41


public class Room implements AutoCloseable{


private static final Cleaner cleaner = Cleaner.create();



private static class State implements Runnable{
int numJunkPiles; //

public State(int numJunkPiles) {
this.numJunkPiles = numJunkPiles;
}

@Override
public void run() {
System.out.println("clean room");
numJunkPiles = 0;
}
}

private final State state;

private final Cleaner.Cleanable cleanable;

public Room(int junk) {
state = new State(junk);
cleanable = cleaner.register(this,state);
}


@Override
public void close() throws Exception {
// 자원 사용하는 client가 리소스를 깜빡하고 반환하지 못하였을때, cleaner가 안전망 역할로써 이를 반환해준다 (gc될떄 호출됨 )
cleanable.clean();
}
}


위 예시처럼 Room client가 명시적으로 close를 호출하는것을 깜빡하였다면 room이 GC에 의해 회수될때 cleaner가 state의 run method를 호출해줌으로서 , 리소스를 회수하는 역할을 해준다. (안전망 역할)

하지만 이조차도 try-with-resouces 블록으로 감싸면 필요하지 않다.

예를 들면 다음과 같다.

1
2
3
4
5
6
7

public static void main(String[] args) throws Exception {
try(Room myRoom = new Room(8)){
new Room(99);
System.out.println(" try - with -resource " );
}
}

정리하면 java 8 : finalizer / java 9 이후 : cleaner는 안전망 역할이나 네이티브 피어 객체 회수용 정도로만 사용하고, 이후에는 사용하지 말고, 대신 try-with-resource로 안전하게 자원을 반환하자는 내용이다.