이런 클래스는 데이터 필드를 직접 조작할 수 있어서 캡슐화의 이점을 누릴수가 없다. 불변식도 강제할 수 없고, 필드를 사용하는 순간에 어떤 동작이 실행되도록 만들수도 없다. 이러한 클래스는 private필드와 public 접근자 메서드(getter)로 바꿔야 한다.

 선언된 패키지 밖에서도 사용 가능한 클래스에는 접근자 메서드를 제공하라. 그래야 클래스 내부 표현을 자유로이 수정할 수 있게 된다. 

하지만 package-private 클래스나 private 중첩 클래스는 데이터 필드를 공개하더라도 잘못이라 말할 수 없다. 클래스의 내용을 제대로 기술하기만 한다면, 접근자 메서드보다는 시각적으로 깔끔하다. private 중첩 클래스의 경우는, 그 클래스의 바깥 클래스 외부의 코드는 아무 영향도 받지 않을 것이기 떄문이다.


요약

public 클래스는 변경 가능 필드를 외부로 공개하면 안된다. 변경 불가능 필드인 경우에는 외부로 공개하더라도 많이 위험하진 않겠지만, 그럴 필요가 없다고 생각된다. 하지만 package-private나 private로 선언된 중첩 클래스의 필드는 그 변경 가능 여부와는 상관없이 외부로 공개하는 것이 바람직할 때도 있다.


잘 설계된 모듈과 그렇지 못한 모듈을 구별 짓는 가장 중요한 속성 하나는 모듈 내부의 데이터를 비롯한 구현 세부사항을 다른 모듈에 잘 감추었느냐는 것이다. 정보은닉과 캡슐화가 잘 되어진 모듈은 모듈사이의 의존성을 낮춰서, 각자 개별적으로 개발 및 변경이 가능하다. 또한 다른 모듈에 영향을 끼칠 걱정 없이 디버깅을 진행할 수 있기 때문에 좋은 성능을 낸다고 할 순 없지만, 효과적인 성능튜닝을 할 수 있는 방법이다.


접근제어는 클래스와 인터페이스, 그리고 그 멤버들의 접근 권한을 규정한다. 접근 제어의 적절한 사용은 정보 은닉 원칙을 실현하는 핵심적인 부분이다.

각 클래스와 멤버는 가능한 한 접근 불가능하도록 만들어라

다시말해서 개발 중인 소프트웨어의 정상적인 동작을 보장하는 한도 내에서 가장 낮은 접근 권한을 설정하라는 원칙이다. 최상위 레벨 클래스나 인터페이스는 가능한 package-private(default)로 선언해야 한다. package-private로 선언하면 API의 일부가 아니라 구현 세부사항에 속하게 되므로, 다음번 릴리즈에 클라이언트 코드를 깨뜨릴 걱정없이 자유로이 변경,삭제,대체가 가능하다.

객체 필드는 절대로 PUBLIC으로 선언하면 안된다.

그 필드에 관계된 불변식을 강제할 수 없다. 필드가 변경될 때 특정한 동작이 실행되도록 할 수도없으므로, 변경 가능 PUBLIC 필드를 가진 클래스는 다중 스레드에 안전하지 않다. 그러나 상수들이 핵심적 부분을 구성한다고 판단되는 경우, 해당 상수들을 public static final 필드들로 선언하여 공개할 수 있다. 이런 필드들은 반드시 기본 자료형 값들을 갖거나, 변경 불가능 객체를 참조해야 한다.

public static final 배열 필드를 두거나, 배열 필드를 반환하는 접근자를 정의해서는 안된다.

해결법1. public으로 선언되었던 배열은 private로 바꾸고 변경이 불가능한 public 리스트를 하나 만드는 것.

해결법2.배열을 private로 선언하고, 해당 배열을 복사해서 반환하는 public 메서드를 하나 추가하는 것.


요약

접근 권한은 최대한 낮추고 최소한의 public API를 설계한 다음, 다른 모든 클래스,인터페이스, 멤버는 api에서 제외하라. public static final 필드를 제외한 어떤 필드도 public 필드로 선언하지 마라. 그리고 public static final필드가 참조하는 객체는 변경 불가능 객체로 만들어라.



compareTo 메소드는 Comparable 인터페이스를 구현하는 유일한 메소드이다. Comparable인터페이스를 구현하는 클래스의 객체들은 자연적 순서를 갖게 된다.Comparable을 구현한 객체들의 배열을 정렬하는 것은 아래처럼 아주 간단하다.


 마찬가지로 Comparable 인터페이스를 구현한 객체들은 검색하거나 최대/최소치 계산하기도 간단하다. 그러므로

알파벳 순서나 값의 크기, 또는 시간적 선후관계처럼 명확한 자연적 순서를 따르는 값 클래스를 구현할 때는 Comparable 인터페이스를 구현할 것을 고려해봐야 한다.

compareTo 메소드는 이 객체의 값이 인자로 주어진 객체보다 작으면 음수를, 같으면 0을, 크면 양수를 반환한다.

compareTo 규약

-객체 참조를 비교하는 방향을 뒤집어도 객체 간 대소 관계는 그대로 유지되어야 한다. 즉 만일 첫 번째 객체가 두번째 객체봐 작다면, 두 번째 객체는 첫 번째 객체보다 큰 객체여야 한다. ( a.compareTo(b) 가 1이면 b.compareTo(a) 는 -1 이어야 한다.)


-첫 번째 객체가 두 번째 객체보다 크고 두 번째 객체가 세 번째 ㄱ개체보다 클 경우 첫 번째 객체는 반드시 세 번째 객체보다 커야 한다.

(a.compareTo(b) 가 1이고 , b.compareTo(c)가 1이면 , a.compareTo(c) 는 1이다.)


-비교결과 같다고 판정된 모든 객체 각각을 다른 어떤 객체와 비교할 경우, 그 비교 결과는 전부 통일해야 한다.


즉, 반사성, 대칭성, 추이성을 만족해야 한다는 것인데 이는 equals의 규약과 같다.




내용이 어렵다..나중에 다시 정리하도록 하자..


cloneable은 어떤 객체가 복제를 허용한다는 사실을 알리는데 쓰려고 고안된 인터페이스다. cloneable 인터페이스에는 어떠한 메소드도 없는데 그렇다면 대체 Cloneable이 하는 일은 무엇일까? protected로 선언 된 Object의 clone메서드가 어떻게 돚악할지 정하는 역할을 한다. Cloneable 인터페이스를 구현하면 해당 객체를 필드 단위로 복사한 객체를 반환한다. 구현하지 하지 않은 클래스라면 CloneNotSupportedException이 발생한다.

사실 인터페이스라는 것은 클라이언트에게 해당 클래스가 무슨 일을 할 수 있는지 알리는 것인데, Cloneable은 상위 클래스의 protected 메서드의 동작을 규정하는 역할을 하고 있으므로 좀 이상하다..

비 fianl 클래스에 clone을 재정의할 때는 반드시 super.clone을 호출해 얻은 객체를 반환해야 한다.
why? 만약 하위클래스에서 super.clone()을 호출하면 프로그래머는 하위 클래스 객체가 반환될 것이라고 생각할 수 있다. 하지만 이러한 기능을 제공하려면 상위 클래스에서도 다시 super.clone을 호출해 만든 객체를 적절히 변환해서 반환할 수 밖에 없다. 모든 상위클래스가 이런식으로 동작을 하게 된다면 결국 Object의 clone메서드를 호출하게 될 것이다.
>> 사실 why에 대한 답이 아닌 것같다...본인조차도 이해가 잘 안되는..추후에 다시 공부하였을 떄 정리할 수 있으면 하도록 해야지..

clone을 정의해야할 때 주의해야 할 점은 복재할 객체가 변경 가능 객체에 대한 참조필드를 가지고 있다면 일반적인 방법으로는 오동작하게 될 확률이 매우매우매우 높다. 

 위의 두 클래스가 있다고 했을 때, 단순히 super.clone을 해버리면 원본 객체와 복사 된 객체가 obj에 대한 같은 참조값을 가지게 되므로 문제가 생긴다. clone메소드 또 다른 형태의 생성자로 볼수 있다. 원래 객체를 손상시키는 일이 없도록 해야하고, 복사본의 불변식도 제대로 만족해야 한다. 

이러한 문제는 clone을 재귀적으로 호출하여 처리할 수 있다. 


정리

Cloneable을 구현하는 모든 클래스는 반환값 자료형이 자기 자신인 public clone 메서드를 재정의해야한다. 이 메서드는 처음에 super.clone을 호출해야 한다. 만일 클래스 내부 필드가 전부 기본형이거나 변경 불가능 객체에 대한 참조라면 필드를 수정할 필요는 없지만(일련 번호같은 필드는 제외) , 참조 필드에 대한 복사는 재귀호출을 이용하여 deepCopy를 하여야 한다. 
 또 다른 객체 복제를 지원하는 좋은 방법은, 복사 생성자나 복사 팩터리를 제공하는 것이다. 


java.lang.Object 클래스가 toString 메서드를 제공하긴 하지만, 이 메서드가 반환하는 문자열은 일반적으로는 사용자가 보려는 문자열이 아니다. 클래스 이름 다음에 @ 기호와 16진수로 표시된 해시 코드가 붙은 문자열의 형태이다. 

"toString() 메소드는 사람이 읽기 쉽도록 간략하지만 유용한 정보를 제공해야 한다"

라고 명시가 되어있다. 앞서 말한 형태는 일반적으로 해당하지 않다고 생각이 된다. 그리고 toString을 잘 만들어 놓으면 클래스를 좀 더 쾌적하게 사용할 수 있다. toString 메서드는 println이나 printf 같은 함수 등에 객체가 전달되면 자동으로 호출되므로 편리하게 사용할 수 있다.

가능하다면 toString 메서드는 객체 내의 중요 정보를 전부 담아 반환하면 좋지만, 객체가 너무 커서 문자열로 변환하기 적합하지 않다면 요약정보로 작성하여도 된다. toString 메서드를 재정의 할 때 중요하게 생각해야 할 부분이 있다

toString이 반환하는 문자열의 형식을 문서의 반환할 것인가 , 말 것 인가의 문제

 문자열을 형식에 규정해두면, 문자열로부터 객체를 만드는 정적 팩터리 메서드나 생성자 등을 이용하여 편리하게 사용할 수 있지만 ,  해당 클래스가 널리 쓰인다고 가정했을 때 규정을 바꾸지 못한다는 단점이 있다. 이미 수많은 프로그래머가 문자열을 파싱하여 사용하고 있을 터인데 , 문자열의 규정을 바꿔버리면 이 문자열을 이용했던 수많은 프로그램이 오동작하게 될 것이기 때문이다.

toString 메서드는 어떤 의도로 작성되었는지 문서에 남겨두어야 한다. 그리고 toString에 포함되어 반환되는 정보들은 전부 프로그래밍을 통해서 가져올 수 있도록 하여야 한다(메서드를 통하여..)


equals 메서드를 재정의하는 클래스는 반드시 hashCode 메서드도 재정의 해야 한다. 그렇지 않으면 hashCode의 일반 규약을 어기게 되므로 해시기반 컬렉션과 함께 사용하면 오동작하게 된다.

실행중에 hashCode를 여러 번 호출하는 경우, equals가 사용하는 정보들이 변경되지 않았다면, 언제나 동일한 정수가 반환되어야 한다.

equals 메서드가 같다고 판정한 두 객체의 hashCode 값은 같아야 한다.

equals 메스더가 다르다고 판정한 두 객체의 hashCode 값은 꼭 다를 필요는 없다.


쓸만한 hashCode 만드는 법

1. 0이 아닌 상수를 result라는 이름의 int 변수에 저장한다.
2. 객체 안에 있는 모든 중요 필드 f에 대해서 아래의 절차를 시행한다.
A).
1)필드가 boolean이면 (f ? 1 : 0)을 계산한다.
2)필드가 byte,char,short,int 중 하나면 (int)f 를 계산한다.
3)필드가 long이면 (int)(f^f>>>32)를 계산한다.
4)필드가 float이면 Float.floatToIntBits(f) 를 계산
5)필드가 double이면 Double.doubleToLongBits(f) 를 계산하고 그 결과로 얻은 long 값을 위의 절차 3) 을 따라서 해시 코드로 변환한다.
6)필드가 객체 참조이고 equals 메서드가 해당 필드의equals 메서드를 재귀적으로 호출하는 경우에는 해당 필드의hashCode 메서드를 재귀적으로 호출하여 해시 코드를 계산한다.
7)필드가 배열인 경우에는 배열의 각 원소가 별도 필드인 것처럼 계산한다.
B). 절차 A에서 계산된 해시코드 c를 result에 다름과 같이 결합한다.
result = 31 * result + c
4.구현이 끝났다면 동치 관계에 있는 객체의 해시 코드값이 똑같이 계산되는 지 비교.


hashCode를 명시적으로 42와 같이 지정하지 않는 이유는?

해당 클래스의 모든 객체가 같은 해시코드를 갖기때문에 전부 같은 버킷에 해시되므로, 해시 테이블의 성능이 엄청나게 안좋아진다.


equals 메서드를 재정의하지 않아도 되는 경우.

1.각각의 객체가 고유하다. 

-값 대신 활성 개체를 나타내는 Thread 같은 클래스가 이 조건에 부합한다.

2.클래스의 논리적 동일성 검사 방법이 있건 없건 상관없다. 

-Random클래스는 두 객체가 같은 난수열을 만드는지 검사하는 equals를 재정의 할 수 있었지만, 사용자의 입장에서 의미치 않은 것이라 판단하여 재정의하지 않았다.

3.상위 클래스에서 재정의한 equals가 하위 클래스에서 사용하기에도 적당하다

-대부분의 List클래스

4.클래스가 private or package-private로 선언되어있고 equals를 호출할 일이 없다.

-혹시 호출할 수도 있잖아..? 재정의하는게 나을지도..

equals 메서드를 재정의 해야할 때

1.논리적 동일성의 개념을 지원하는 클래스.

2.상위 클래스의 equals가 하위 클래스의 필요를 충족치 못할 때.

예를 들어, 값 클래스(Integer , Date같은..) 와 같은 클래스들이 적합하다.

equals 메서드는 동치 관계를 구현한다

  • 반사성 : 모든 객체는 자신과 같아야 한다.  x.equals(x) 는 true
  • 대칭성 : 두 객체에게 서로 같은지 물으면 같은 답이 나와야 한다. x.equals(y) == y.equals(x) 
  • 추이성 : x.equals(y) 가 같고, y.equals(z) 가 같으면 x.equals(z)는 같아야 한다.
  • 일관성 : x.equals(y) 가 같다고 판단되었으면, 추후 변경되지 않는 한 계속 같아야 한다.


멋진 equals 메서드를 구현하기 위한 지침.

  1.  == 연산자를 사용하여 equals의 인자가 자기 자신인지 검사하라.
  2. instanceof 연산자를 사용하여 인자의 자료형이 정확한지 검사하라.
  3. equals의 인자를 정확한 자료형으로 변환하라. 위의 instanceof를 통과하면 반드시 성공한다.
  4. 중요 필드 각각이 인자로 주어진 객체의 해당 필드와 일치하는지 검사한다.
  5. equals 메서드 구현을 끝냈다면, 대칭성, 추이성, 일관성의 세 속성이 만족하는 지 검토하라.



finalize() 메소드란?

JVM이 메모리 누수를 방지하기위해 실행하는 GC가 수행될 때 더 이상 사용하지 않는 자원에 대한 정리작업을 할 때 호출하는 메소드. 이러한 종료자의 치명적인 단점이 있다. 

바로 즉시 실행되리라는 보장이 전혀 없다는 것.

 따라서 긴급한 작업을 종료자 안에서 처리하면 안된다!!. 예를 들어 파일을 닫도록 하는 작업, 자원을 반납하는 작업 등.. System.gc , System.runFinalization 같은 메서드는 종료자가 실행 될 가능성을 높여주긴 하지만 여전히 보장하진 않는다. ( 종료자는 자바명세서에도  즉시 실행되어야 한다는 구문도 없지만, 반드시 실행되어야 한다는 구문도 없다. ㅎㄷㄷ...)

종료자의 문제점은 이 뿐만이 아니다.

 일반적으로는 uncaught 예외가 던져지면 스레드는 종료되고 StackTrace가 표시되지만, 종료자 안에서는 아니다. 경고문 조차 발생하지 않는다..

또한, 종료자를 사용하면 프로그램 성능이 심각하게 떨어진다. (객체 메모리 해제 등의 이유로..)

그럼 자원의 반환 / 삭제는 어떻게?

명시적인 종료 메소드를 하나 정의하고, 객체 내에서 유효하지 않은 객체임을 표시하는 private 필드를 하나 두고, 모든 메서드 맨 앞에 해당 필드를 검사하는 코드를 두어서 유효하지 않은 객체면 예외를 던지도록 한다. 이런 명시적인 종료 메서드는 보통 객체 종료를 보장하기 위해 try/catch과 함께 쓰인다.

정말 정말 종료자는 쓸 곳이 없는 녀석인가..!? ( 이것도 1번 빼고 딱히..?)

1. 명시적 종료 메서드 호출을 잊을 경우에 대비하는 안전망의 역할. 종료자는 그런 자원을 발견하게 될 경우 반드시 경고 메세지를 로그로 남겨서 코드가 잘못되어있음을 알려야 한다.

2.네이티브 피어와 연결된 객체를 다룰 때 사용될 수도 있다. 

3.하위 클래스에서 상위 클래스 종료자를 재정의 하면서 상위 클래스 종료자 호출을 잊으면, 상위 클래스 종료자는 절대 호출되지 않는다. 따라서 아래와 같이 처리할 수 있다.


위와 같이 상위 클래스의 종료자 호출을 하지 않았을 때의 문제를 방지하는 한 가지 방법은, 종료되어야 하는 모든 객체마다 여벌의 객체를 하나 더 만드는 것이다.



위와 같은 익명클래스를 종료보호자라고 부른다.


요약

자원 반환에 대한 최종적 안전장치를 구현하거나, 그다지 중요하지 않은 네이티브 자원을 종료시키는 것이 아니라면 종료자는 사용하지 말자


+ Recent posts