static void copy(String src, String dst) throws IOException {
InputStream in = new FileInputStream(src);
try {
OutputStream out = new FileOutputStream(dst);
try {
byte[] buf = new byte[BUFFER_SIZE];
int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
} finally {
out.close();
}
} finally {
in.close();
}
}
이 경우 어떤 문제에 의해서 close에서도 문제가 생기다면? 두번째(close)예외의 메시지만 준다. 그래서 문제 파악을 힘들게 만든다.( 그런데 장치의 고장이라고 하는데 구체적으로 어떤 상황일가. 파일 인풋 아웃풋일텐데. 예상으론 통신시 갑작스런 오류? / 디스켓...? )
아래는 try with resource 로 고친 코드다
static String firstLineOfFile(String path) throws IOException {
try (BufferedReader br = new BufferedReader(
new FileReader(path))) {
return br.readLine();
}
}
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 == 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"이 된다.
*/
finalizer 와 cleaner 의 사용을 피하라
GC는 컨트롤 가능한가?
이 대안은 그럼 무엇?
// 청소가 필요한 자원. 절대 Room을 참조해서는 안 된다! private static class State implements Runnable { int numJunkPiles; // Number of junk piles in this room
}
// 방의 상태. cleanable과 공유한다. private final State state;
// cleanable 객체. 수거 대상이 되면 방을 청소한다. private final Cleaner.Cleanable cleanable;
public Room(int numJunkPiles) { state = new State(numJunkPiles); cleanable = cleaner.register(this, state); }
@Override public void close() { cleanable.clean(); } }
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); } }
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); }
public class Point { private final int x; private final int y;
}
public class ColorPoint extends Point { private final 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; }
public class ColorPoint { private final Point point; private final Color color;
}
public final class PhoneNumber { private final short areaCode, prefix, lineNum;
}
HashMap<PhonNumber,String> m = new Hashmap<>(); m.put(new PhoneNumber(707, 867, 5309),"jenny"); m.get(new PhoneNumber(707, 867, 5309));
@override public int hashcode(){ return 42;}
@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); } // 나라면 그냥 이렇게 쓸듯 하지만 성능이 아쉽다고 한다.
@Override public String toString() { return String.format("%03d-%03d-%04d", areaCode, prefix, lineNum); }