Java.lang.Number
がComparable
を実装しない理由を誰もが知っていますか?これは、Collections.sort
でNumber
sをソートできないことを意味します。
ディスカッション後の更新:
有益な回答をありがとう。最終的に このトピックに関するさらに詳しい調査 を行いました。
Java.lang.NumberがComparableを実装しない理由の最も簡単な説明は、可変性の懸念に根ざしています。
少しレビューすると、Java.lang.Number
は、AtomicInteger
、AtomicLong
、BigDecimal
、BigInteger
、Byte
、Double
、Float
、Integer
、Long
、およびShort
の抽象スーパータイプです。そのリストで、AtomicInteger
とAtomicLong
はComparable
を実装しません。
掘り下げてみると、比較中または後にオブジェクトが変更されて比較結果が役に立たなくなる可能性があるため、可変型にComparable
を実装することはお勧めできません。 AtomicLong
とAtomicInteger
は両方とも可変です。 API設計者は、将来のサブタイプの実装に制約があるため、Number
にComparable
を実装させないという先見の明を持っていました。確かに、Java.lang.Number
が最初に実装されてから長い間、Java 1.5にAtomicLong
とAtomicInteger
が追加されました。
可変性とは別に、ここでもおそらく他の考慮事項があります。 compareTo
のNumber
実装は、すべての数値をBigDecimal
にプロモートする必要があります。これは、Number
サブタイプをすべて収容できるためです。数学とパフォーマンスの面でのこのプロモーションの意味は、私には少しわかりませんが、私の直感では、その解決策は気が狂っています。
次の式に言及する価値があります。
_new Long(10).equals(new Integer(10))
_
は常にfalse
であり、ある時点で全員をつまずかせる傾向があります。したがって、任意のNumber
sを比較できないだけでなく、それらが等しいかどうかを判断することさえできません。
また、実際のプリミティブ型(float
、double
)では、2つの値が等しいかどうかを判断するのは難しいため、許容できる誤差の範囲内で行う必要があります。次のようなコードを試してください:
_double d1 = 1.0d;
double d2 = 0.0d;
for (int i=0; i<10; i++) {
d2 += 0.1d;
}
System.out.println(d2 - d1);
_
わずかな違いが残ります。
Number
Comparable
を作成する問題に戻ります。どのように実装しますか? doubleValue()
のようなものを使用すると、確実に実行されません。 Number
サブタイプは次のとおりです。
Byte
;Short
;Integer
;Long
;AtomicInteger
;AtomicLong
;Float
;Double
;BigInteger
;そしてBigDecimal
。一連のif instanceofステートメントに委ねられない信頼できるcompareTo()
メソッドをコーディングできますか? Number
インスタンスには、6つのメソッドしか使用できません。
byteValue()
;shortValue()
;intValue()
;longValue()
;floatValue()
;そしてdoubleValue()
。ですから、SunはNumber
sは自分自身のインスタンスに対してのみComparable
であるという(合理的な)決定を下したと思います。
答えについては、Java bugparade bug 441432 を参照してください。 comp.lang.Java.programmer から議論を見つけることもできます。
2001年のバグレポートに対するSunの回答から引用するには:
すべての「数字」は比較できません。 compareは、数値の完全な順序付けが可能であることを前提としています。これは、浮動小数点数にも当てはまりません。 NaN(非数)は、それ自体でさえ、浮動小数点値より小さくも、大きくも、等しくもなりません。 {Float、Double} .compareは、浮動小数点「<」および「=」演算子の順序とは異なる合計順序を課します。さらに、現在実装されているように、Numberのサブクラスは同じクラスの他のインスタンスとのみ比較できます。複素数のように、標準の合計順序が存在しない場合もありますが、定義することもできます。要するに、Numberのサブクラスが比較可能かどうかは、そのサブクラスの決定として残す必要があります。
同等の数を実装するには、すべてのサブクラスペアのコードを記述する必要があります。代わりに、サブクラスに同等の実装を許可する方が簡単です。
おそらく、数値を比較するのはかなり非効率的だからです-そのような比較を可能にするためにすべての数値が適合する唯一の表現はBigDecimalです。
代わりに、Numberの非アトミックサブクラスはComparable自体を実装します。
アトミックなものは可変なので、アトミックな比較を実装できません。
元の問題の解決(数値のリストのソート)を試みるためのオプションは、数値を拡張してComparableを実装する汎用タイプのリストを宣言することです。
何かのようなもの:
<N extends Number & Comparable<N>> void processNumbers(List<N> numbers) {
System.out.println("Unsorted: " + numbers);
Collections.sort(numbers);
System.out.println(" Sorted: " + numbers);
// ...
}
void processIntegers() {
processNumbers(Arrays.asList(7, 2, 5));
}
void processDoubles() {
processNumbers(Arrays.asList(7.1, 2.4, 5.2));
}
Transmorph を使用して、NumberComparatorクラスを使用して数値を比較できます。
NumberComparator numberComparator = new NumberComparator();
assertTrue(numberComparator.compare(12, 24) < 0);
assertTrue(numberComparator.compare((byte) 12, (long) 24) < 0);
assertTrue(numberComparator.compare((byte) 12, 24.0) < 0);
assertTrue(numberComparator.compare(25.0, 24.0) > 0);
assertTrue(numberComparator.compare((double) 25.0, (float) 24.0) > 0);
assertTrue(numberComparator.compare(new BigDecimal(25.0), (float) 24.0) > 0);
なぜこれが悪い考えだったのでしょうか? :
abstract class ImmutableNumber extends Number implements Comparable {
// do NOT implement compareTo method; allowed because class is abstract
}
class Integer extends ImmutableNumber {
// implement compareTo here
}
class Long extends ImmutableNumber {
// implement compareTo here
}
別のオプションは、クラスNumberがComparableを実装することを宣言し、compareTo実装を省略し、AtomicIntegerなどの他のクラスでUnsupportedExceptionをスローしながら、Integerなどの一部のクラスで実装することでした。
さまざまなタイプの数値について、標準的な比較はありません。ただし、独自のコンパレータを作成し、それを使用してTreeMap <Number、Object>、TreeSet <Number>またはCollections.sort(List <Number>、Comparator)またはArrays.sort(Number []、Comparator)を作成できます。
独自のコンパレータを書く
import Java.math.BigDecimal;
import Java.math.BigInteger;
import Java.util.ArrayList;
import Java.util.Collections;
import Java.util.Comparator;
import Java.util.concurrent.atomic.AtomicInteger;
import Java.util.concurrent.atomic.AtomicLong;
public class NumberComparator implements Comparator {
@SuppressWarnings("unchecked")
@Override
public int compare(Number number1, Number number2) {
if (((Object) number2).getClass().equals(((Object) number1).getClass())) {
// both numbers are instances of the same type!
if (number1 instanceof Comparable) {
// and they implement the Comparable interface
return ((Comparable) number1).compareTo(number2);
}
}
// for all different Number types, let's check there double values
if (number1.doubleValue() < number2.doubleValue())
return -1;
if (number1.doubleValue() > number2.doubleValue())
return 1;
return 0;
}
/**
* DEMO: How to compare apples and oranges.
*/
public static void main(String[] args) {
ArrayList listToSort = new ArrayList();
listToSort.add(new Long(10));
listToSort.add(new Integer(1));
listToSort.add(new Short((short) 14));
listToSort.add(new Byte((byte) 10));
listToSort.add(new Long(9));
listToSort.add(new AtomicLong(2));
listToSort.add(new Double(9.5));
listToSort.add(new Double(9.0));
listToSort.add(new Double(8.5));
listToSort.add(new AtomicInteger(2));
listToSort.add(new Long(11));
listToSort.add(new Float(9));
listToSort.add(new BigDecimal(3));
listToSort.add(new BigInteger("12"));
listToSort.add(new Long(8));
System.out.println("unsorted: " + listToSort);
Collections.sort(listToSort, new NumberComparator());
System.out.println("sorted: " + listToSort);
System.out.print("Classes: ");
for (Number number : listToSort) {
System.out.print(number.getClass().getSimpleName() + ", ");
}
}
}
byte
(プリミティブ)はint
(プリミティブ)です。プリミティブには、一度に1つの値しかありません。
言語設計ルールによりこれが許可されます。
_int i = 255
// down cast primitive
(byte) i == -1
_
Byte
はInteger
ではありません。 Byte
はNumber
であり、Integer
はNumber
です。 Number
オブジェクトは、同時に複数の値を持つことができます。
_Integer iObject = new Integer(255);
System.out.println(iObject.intValue()); // 255
System.out.println(iObject.byteValue()); // -1
_
Byte
がInteger
であり、Integer
がNumber
である場合、compareTo(Number number1, Number number2)
メソッドで使用する値はどれですか。
私の推測では、Comparableを実装しないことにより、クラスを実装してより柔軟に実装できるかどうかが決まります。すべての一般的な数値(整数、Long、Doubleなど)はComparableを実装します。要素自体がComparableを実装している限り、Collections.sortを呼び出すことができます。
クラス階層を調べます。 Long、IntegerなどのラッパークラスはComparableを実装します。つまり、Integerは整数に相当し、longはlongに相当しますが、それらを混在させることはできません。少なくともこのジェネリックパラダイムでは。私はあなたの質問に「なぜ」答えると思います。