10. equals 는 일반 규약에 지켜 재정의하라

equals 는 될수 있으면 재정의를 하지말지만 재정의 할꺼면 다음 로직을 지키자!

  • equals 를 구현할 때는 4가지를 주의하자
    1) 대칭적인가
    2) 추이성이 있는가
    3) 일관성이 있는가
    4) null이 아닌

@Override 
public boolean equals(Object o) {
     // 1. 입력이 자기 자신의 참조인지 확인
     // 자기 자신이면 무조건 일치하니까 true 이다
	if (o == this) 
    	return true;
        
    // 2. instanceof 연산자로 입력이 올바른 타입인지 확인
    if ( !(o instanceof PhoneNumber))
		return false;
        
    // 3. 입력을 올바른 타입으로 형변환
    PhoneNumber pn = (PhoneNumber) o;
    
    // 4. 입력 객체와 자기 자신의 대응되는 핵심 필드들이 모두 일치하는지 확인한다.
    return pn.lineNum ==  lineNum && pn.prefix == prefix
    && pn.areaCode == areaCode;
 }
   	
	
}

위와 같은 논리로 equals 메서드를 작성하면 성공이다.

11. equals 를 재정의하거든 hashcode 도 재정의 해라

  • equals 를 재정의 할때는 hashcode 도 재정의 해야한다 안그러면 해쉬코드 일반 규약을 어기게 된다

hashcode 란?
: 객체의 주소값을 변환하여 생성한 객체의 고유한 방법

1) equals 비교에 사용되는 정보가 변경되지 않았다면 애플리케이션이 실행되는 동안 그 객체의 hashcode 메서드는 몇번을 호출해도 일관되게 항상 같은 값을 반환해야한다. 애플리케이션을 다시 실행하는 경우는 달라저도 상관 없다.

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

3) equals(Object) 가 두객체가 다르다고 판단했더라도 두객체의 hashcode가 서로 다른 값을 반환할 필요는 없다.




12. toString은 항상 재정의 하라

toString() : 객체의 내용을 문자열로 반환. 실전에서는 그객체가 가진 주요 정보 모두를 읽기 좋은 형태로 반환하는 것이 좋다.

포맷을 명시하든 아니든 의도는 명확히 밝히는것이 좋다.
또 포맷을 사용할 거면 정확하게 사용해야한다.


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

정적 유틸리티 클래스나 열거 타입(이미 자바에서 완벽한 toString 제공)은 따로 toString() 재정의가 필요

정적 유틸리티 클래스

public class NumberUtils {
  public static int add(int a, int b) {
    return a + b;
  }
}


13. clone 재정의는 주의해서 진행해라

clone : Cloneable은 복제해도 되는 클래스임을 명시하는 용도의 인터페이스. clone 메서드는 Cloneable을 구현한 클래스

  • 실무에서는 clone 메서드는 public으로 제공하며, 사용자는 당연히 복제가 제대로 이뤄지지 않을거라고 생각함
  • 이 상황을 해결하기 위해서 final 클래스를 제외한 클래스는 Cloneable을 확장시켜 사용하면 안된다. final 도 성능 관점에서 주의하여 사용해야한다.

모든 필드가 기본타입이나 불변객체를 참조한다면 이 객체는 완벽히 우리가 원하는 상태이지만 쓸데 없는 복사를 지양한다는 관점에서 보면 불변 클래스는 굳이 clone메서드를 제공하지 않는 게 좋다.

가변 상태를 참조하지 않는 클래스용 clone 메서드


@Override public PhoneNumber clone() {
	try {
    	return (PhoneNumber) super.clone();
    } catch ( CloneNotSupportedException e )  {
    	throw new AssertionError();
    }

}

가변 상태를 참조하는 클래스용 clone 메서드(재귀)
: 여기서 클론 메서드는 사실상 생성자와 같은 효과를 낸다. 즉, clone 은 원본 객체에 아무란 해를 끼치지 않는 동시에 복제된 객체의 불변식을 보장해야 된다.

stack의 clone 메서드는 제대로 동작하려면 스택 내부 정보를 복사해야하는데 가장 쉬운 방법은 elements 배열을 재귀적으로 하나 복사 해놓고 두개를 비교하는 것이다.


@Override public Stack clone() {
	try {
       Stack res = (Stack) super.clone();
       res.elements = elements.clone();
       return res;
       
    } catch ( CloneNotSupportedException e )  {
    	throw new AssertionError();
    }

}

문제는 위의 방법은 elements 필드가 final이면 앞서 방식은 작동 안됨. 그래서 일부 필드에서 final을 제거해야 될수 있음.

또 위방법은 연결 리스트 같이 이어저 있는 친구에게는 사용하면 안된다. 그런경우에는 아래처럼 사용한다.


@Override public HashTable clone() {

  try {
    HashTable res = ( HashTable) super.clone();
    res.buckets = new Entry[buckets.length];
    for ( int i = 0; i < buckets.length ; i++)
    	if ( buckets[i] != null )
        	res.buckets[i] = buckets[i].deepCopy();
    return res;
  } catch ( CloneNotSupportedException e )  {
    	throw new AssertionError();
  }
}

deepCopy를 재귀 호출 대신 반복자를 써서 순회하는 방향으로 수정


참고
깊은 복사 : 객체 참조 안하고 내용물 복사
얕은 복사 : 객체 참조 (같은 주소)



14. Comparable 을 구현할지 고려해라

comparable : 클래스의 인스턴스에게 순서가 존재할때 사용하는 객체. 손쉽게 정렬가능하다.

다르게 말하면 클래스의 인스턴스에게 순서가 존재하지 않은 경우 compareTo 메서드를 손쉽게 대체 가능하다.

객체의 성질에 따라 사용할지 말지를 고려하자.

profile
개발 로그 🍎 🍎 🍎

0개의 댓글

Powered by GraphCDN, the GraphQL CDN