Closed zpqmdh closed 2 months ago
Q1.
BigDecimal
클래스에서의compareTo
와equals
가 일관되지 않는데 어떻게 정의되어 있는지 궁금합니다.A1.
@Override public boolean equals(Object x) { if (!(x instanceof BigDecimal)) return false; BigDecimal xDec = (BigDecimal) x; if (x == this) return true; if (scale != xDec.scale) return false; long s = this.intCompact; long xs = xDec.intCompact; if (s != INFLATED) { if (xs == INFLATED) xs = compactValFor(xDec.intVal); return xs == s; } else if (xs != INFLATED) return xs == compactValFor(this.intVal);
return this.inflated().equals(xDec.inflated());
}
- `BigDecimal` 내부 구현 코드중 일부입니다. 두 메서드 사이에는 책에서 서술한 것 처럼 일관성이 없습니다. 즉 `compareTo`에는 0이 반환되는 데이터가, `equals`를 사용하면 true가 아닐 수 있습니다.
- 가장 주목해야 할 점은 equals의 다음 코드 부분입니다.
```java
if (!(x instanceof BigDecimal))
return false;
BigDecimal xDec = (BigDecimal) x;
if (x == this)
return true;
if (scale != xDec.scale)
return false;
...
equals
메서드는 scale
이라는 데이터를 가지고 비교를 수행합니다. 이때 scale
은 소숫점을 기준으로 우측, 즉 소수점 아래가 몇 자리인지를 나타냅니다. 즉 BigDecimal
클래스의 equals
는 소숫점 자리가 다르면 두 수가 동일하다 해도 false
가 반환됩니다.2.0
과 2.00
을 비교하면 false가 나오는 것을 통해 확인이 가능합니다. public int compareTo(BigDecimal val) {
// Quick path for equal scale and non-inflated case.
if (scale == val.scale) {
long xs = intCompact;
long ys = val.intCompact;
if (xs != INFLATED && ys != INFLATED)
return xs != ys ? ((xs > ys) ? 1 : -1) : 0;
}
int xsign = this.signum();
int ysign = val.signum();
if (xsign != ysign)
return (xsign > ysign) ? 1 : -1;
if (xsign == 0)
return 0;
int cmp = compareMagnitude(val);
return (xsign > 0) ? cmp : -cmp;
}
해당 코드는 BigDecimal
의 compareTo
메서드입니다. 보시면 먼저 두 데이터의 scale
이 동일한지 확인만 한 뒤에 동일하지 않아도 이후에 검사를 수행합니다.
compareMagnitude
메서드에서 다시 비교를 수행합니다. compareMagnitude
의 경우 상당히 복잡한 과정을 거쳐 비교 과정을 수행해 소수점 자리수가 다르더라도 동등한지를 확실하게 비교하게 됩니다.compareTo의 경우 메서드 설명 명세에서 equals의 예시를 들며 소수점 자리가 다르더라도 동일하게 취급한다는 설명을 붙이고 있습니다.
Compares this BigDecimal with the specified BigDecimal. Two BigDecimal objects that are equal in value but have a different scale (like 2.0 and 2.00) are considered equal by this method.
Q2.
HashSet
은equals
비교를,TreeSet
은compareTo
비교를 한다고 적혀있는데 실제로 그런지 궁금합니다!A2.
- 그렇습니다. 사실 이는
HashSet
과TreeSet
의 성질을 생각해보면 유추해낼 수 있는 것이기도 합니다.TreeSet
을 먼저 생각해보면TreeSet
은 "순서를 유지하는 집합"이며 특정 비교 기준을 사용하여 그 기준에 맞도록 원소들을 순서대로 정렬하여 보관합니다.- 즉 순서가 존재하기 때문에 equals로 비교를 하기에는 명확한 한계가 존재한다고 할 수 있습니다.
- Java에서 정의된
TreeMap
,TreeSet
은 내부적으로 Red-Black-Tree 라고 하는 BinarySearchTree(이하 BST) 자료구조를 기반으로 구현되었습니다. BST에서 데이터가 들어가기 위해서는 먼저 들어가는 데이터가 트리의 원소보다 큰지 작은지에 대한 대소비교를 수행해야 하므로 이 과정에서compareTo
가 필수적으로 활용되어야 합니다.- 아래는
TreeMap
에서의containsKey
구현 코드입니다.TreeSet
은TreeMap
의containsKey
를 사용하여contains
연산을 수행하기 때문에 같이 보셔도 무방합니다.(이는HashSet
과HashMap
도 동일합니다.)
public boolean containsKey(Object key) {
return getEntry(key) != null;
}
final Entry<K,V> getEntry(Object key) {
// Offload comparator-based version for sake of performance
if (comparator != null)
return getEntryUsingComparator(key);
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
Entry<K,V> p = root;
while (p != null) {
int cmp = k.compareTo(p.key);
if (cmp < 0)
p = p.left;
else if (cmp > 0)
p = p.right;
else
return p;
}
return null;
}
HashSet
, HashMap
은 순서를 유지하지 않습니다. 이는 순서에 관계없이 동일한 데이터가 존재하는지 확인만 하면 된다는 것을 의미합니다. 따라서 값이 동일한지만 검사한다면 나머지는 상관이 없게 됩니다.HashSet
, HashMap
의 경우에는 동일성 비교를 hashCode
를 통해 먼저 수행합니다. 즉 두 객체의 해시 코드를 비교하여 동일한지를 먼저 확인합니다. 만약 Hash Collision이 발생하여 여러개의 데이터가 하나의 버킷에 존재한다면 그 이후 equals
를 사용하여 키를 비교하게 됩니다. 이 과정에서 equals
가 사용되게 됩니다.아래는 HashSet
, HashMap
에 사용되는 containsKey
구현 코드입니다.
public boolean containsKey(Object key) {
return getNode(hash(key), key) != null;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
책 90쪽에 의하면
BigDecimal
클래스는compareTo
와equals
가 일관되지 않다고 합니다.BigDecimal
클래스에서의compareTo
와equals
메서드가 어떻게 정의되어 있는지 궁금합니다.HashSet
과TreeSet
의 비교 방식 차이가 궁금합니다.HashSet
은equals
비교를,TreeSet
은compareTo
비교를 한다고 적혀있는데 실제로 그런지 궁금합니다!