Item16. public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라
다음과 같이 캡슐화되지 않은 class는 필드를 외부에서 직접 접근이 바로 가능하니 작성하지 않는 것이 좋다. (모듈간 독립성이 떨어짐)
1 | public class Point { |
다음과 같이 캡슐화되지 않은 class는 필드를 외부에서 직접 접근이 바로 가능하니 작성하지 않는 것이 좋다. (모듈간 독립성이 떨어짐)
1 | public class Point { |
불변 클래스란 객체 생성 후에 객체의 내부 값을 수정할 수 없는 클래스이다. 불변 객체에 간직된 정보는 고정되어 객체 소멸 시점까지 변경되면 안된다.
자바 String,primitive type boxing 클래스, BigInteger,BigDecimal 이 여기에 속한다.
불변 객체를 사용하는 이유는 다음과 같다.
객체의 상태를 변경하는 메소드 (ex) setter) 를 제공하지 않는다
class를 확장할 수 없도록 한다. 대표적으로 class를 final로 선언하면 된다.
모든 필드를 final로 선언한다.
( 어떤 method도 객체의 상태 중 외부에 비치는 값을 변경할 수 없다. )
모든 필드를 private로 선언한다. 불변 필드인 경우에는 public final로 선언하여도 불변 객체는 되지만, API를 수정하기 힘들어진다.
자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다. 클래스 내부에 가변 필드가 있다면, client에서 그 가변 필드의 참조값을 가지고 수정할수 있으므로 생성자/접근자/readObject method에서 모두 방어적 복사를 수행해야 한다.
잘 설계된 컴포넌트는 모든 내부 구현을 완벽히 숨겨, 구현과 API를 깔끔하게 분리한다.
오직 API를 통해서만 다른 컴포넌트와 소통하며, 서로 구체적인 동작 방식은 개의치 않는다.
시스템을 구성하는 컴포넌트를 서로 독립시켜서 개발/테스트/적용/수정 분석을 개별적으로 할 수 있게 해준다
여러 컴포넌트를 병렬로 개발 가능함으로, 개발 속도를 높임
각 컴포넌트를 빨리 파악 및 디버깅이 관리하고, 다른 컴포넌트로 교체하는 작업이 손쉽기 때문에 유지보수 비용이 덜 든다
직접적으로 성능을 높여주지는 않지만, 성능최적화에 도움을 준다. 완성된 시스템을 프로파일링해 최적화할 컴포넌트를 정하고, 해당 컴포넌트만 최적할 수 있기 때문이다.
SW 재사용을 높임
시스템 개발 난이도를 낮춘다. 개별 컴포넌트단위로 나누어 개발하고 테스트할 수 있기 때문이다.
compareTo는 단순 동치성 비교에 더해 순서까지 비교할 수 있으며, generic하다.
1 | public interface Comparable<T> { |
comparable을 구현했다는 것은 그 클래스의 인스턴스들에 순서가 있음을 뜻한다. 그래서 Comparable을 구현한 객체들의 배열은 다음과 같이 정렬이 가능하다.
1 |
|
순서가 명확한 값 class를 작성한다면 Comparable 인터페이스 구현을 고려하자.
compareTo method의 일반 규약은 다음과 같다.
compareTo method는 주어진 객체와 this 객체의 순서를 비교한다. this 객체가 주어진 객체보다 작으면 음의 정수 , 같으면 0 , 크면 양의 정수를 반환하고 이 객체와 비교할수 없는 타입의 객체가 주어지면 ClassCastException을 던진다. 1. Comparable을 구현한 class는 모든 x,y에 대해서 sgn(x.compareTo(y)) == -sgn(y.compareTo(x)) 2. Comparable을 구현한 class는 추이성을 보장해야 한다. ( x.compareTo(y) > 0 && y.compareTo(z) > 0)이면 x.compareTo(z) > 0이다. 3. Comparable을 구현한 class는 모든 z에 대해 x.compareTo(y) == 0 이면, sgn(x.compareTo(z)) == sgn(y.compraeTo(z)) 다. 4. 부가적으로 다음의 규약은 필수는 아니나 지키면 좋다.(x.compareTo(y) == 0) == x.equals(y) 여야 한다. 만약 위 식이 성립하지 않는다면 이 클래스의 순서는 equals method와 일관되지 않음을 명시해야 한다.
compareTo규약을 지키지 못하면 비교를 활용하는 TreeSet, TreeMap, Collections,Array등의 클래스에서 제공해주는 API가 정상적으로 작동하지 않을 수도 있다.
Object class에는 clone() method가 protected로 선언되어 있고, 객체를 복사하려면 java.langCloneable Interface를 implement하여 복사할 수 있다.
1 | package java.lang; |
(https://docs.oracle.com/javase/7/docs/api/java/lang/Cloneable.html)
Cloneable은 아무 method도 없는 marker interface지만 ,Object의 clone method의 동작 방식을 결정한다.
Cloeanble을 구현한 class에서부터 만들어진 객체에서 clone을 호출하면 객체의 필드들을 복사한 객체를 반환하도록 하고, 그렇지 않은 객체에서는 CloneNotSupportedException을 던진다.
Object toString 은 별도로 overrriding 하지 않는 경우에는 class명@16진수_해시코드 를 반환한다.
1 | getClass().getName() + '@' + Integer.toHexString(hashCode()) |
Object 명세에 보면 toString은 간결하며, 사람이 읽기 쉬운 형태의 정보를 반환해야 하며,
toString을 overrding한 모든 하위 클래스에서도 toString 을 재정의해야 하며, 해당 객체가 가진 주요 값 필드를 반환하는게 바람직하다.
(https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html)
toString을 재정의하면 다음과 같은 장점을 가지고 있다.
선택사항 : toString 사용시 포맷을 문서화 여부
hashCode method는 해싱(hashing) 기법에 사용되는 해시함수를 구현한 것이다.
hashCode가 반환해주는 결과값(해시코드값)은 hashTable,hashMap내 실제값이 저장되는 위치를 알려주는 일종의 인덱스로 사용된다.
Object 명세에 보면 다음과 같이 기술되어 있다.
(https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#hashCode())
1 |
|
Java에서 최상위 class인 Object의 final이 아닌 methode들 (equals, hashCode, toString ,clone, finalize)는 모두 overriding을 염두에 두고 설계된 것이라, overriding시 지켜야 하는 일반 규약이 명확하다.
1 | public boolean equals(Object obj); |
이 중에서 equals method는 == 연산자와 마찬가지로 참조변수의 참조값을 비교하도록 정의되어 있는데, 다음과 같은 상황에서는 overriding 하지 않는 것이 좋다.
1 | 1. 각 인스턴스가 고유한 경우 ex) Thread와 같이 객체가 값을 표현하는 목적이 아닌 경우 |
코딩을 하다보면 close method를 통해 반환해주어야 하는 리소스가 많다(java.sql.Connection, java.io 관련 리소스 ex) InputStream,OutputStream ) , 개발자가 실수로 리소스 정리를 깜빡하는 경우가 있다.
주로 자원을 반환할떄 사용하던 try- finally 구문은 다음과 같다.
1 |
|
위 try - finally 의 단점은 다음과 같다.
Java는 두가지 객체 소멸자를 제공한다.
finalizer
cleaner
여기서 객체 소멸자란 생성자와 정반대의 개념으로, 객체가 소멸될떄(GC될때)
자동으로 호출되는 함수이다.
Java 9에서부터는 finalizer를 사용 자제해야 할 API로 지정하고, cleaner를 그 대안으로 소개하였다. 하지만 책에서는 cleaner또는 finalizer의 사용을 피하라고 얘기하고 있다,.그렇다면 finalizer와 cleaner는 왜 사용하면 안될까?
1 | // deprecated |
객체의 소멸을 GC가 담당하므로, finalizer와 cleaner를 언제 수행할지의 역할은 전적으로 GC 알고리즘에 달려있다. 즉 GC 구현마다 차이가 있다.
위와 같은 특징들을 보면 당연히 finalizer와 cleaner로는 제때 실행되어야 하는 작업은 절대 할 수 없다.
finalizer 쓰레드는 우선순위가 낮아서 , 언제 실행될지 모른다. -> 계속 대기 된다면 GC되지 않고, OutOfMemoryException이 발생할수도 있다.
중간에 예외가 터지면, 객체가 훼손된 상태로 남아있을수도 있다.
AutoCloseable 객체를 만들고, try-with-resoucre 로 자원 반납을 하는데 걸리는 시간은 12ns 인데 비해, Finalizer는 550ns 거의 약 50배가 걸렸다. cleaner도 60ns로, 5배정도 느리다.
자원 소유자가 close method를 깜빡하고 호출하지 못하였을때, 안전망 역할을 해준다.
자바 객체가 아닌 native peer 회수
1 |
|
위 예시처럼 Room client가 명시적으로 close를 호출하는것을 깜빡하였다면 room이 GC에 의해 회수될때 cleaner가 state의 run method를 호출해줌으로서 , 리소스를 회수하는 역할을 해준다. (안전망 역할)
하지만 이조차도 try-with-resouces 블록으로 감싸면 필요하지 않다.
예를 들면 다음과 같다.
1 |
|
정리하면 java 8 : finalizer / java 9 이후 : cleaner는 안전망 역할이나 네이티브 피어 객체 회수용 정도로만 사용하고, 이후에는 사용하지 말고, 대신 try-with-resource로 안전하게 자원을 반환하자는 내용이다.