为什么 java.lang.Number 没有实现 Comparable?

有人知道为什么 java.lang.Number不实现 Comparable吗?这意味着你不能用 Collections.sortNumber进行排序,这在我看来有点奇怪。

讨论后更新:

感谢所有有帮助的回答。我最后做了 关于这个话题的更多研究

为什么 java.lang.Number 没有实现 Comparable,最简单的解释是基于可变性的考虑。

回顾一下,java.lang.NumberAtomicIntegerAtomicLongBigDecimalBigIntegerByteDoubleFloatIntegerLongAtomicInteger0的抽象超级类型。在这个列表中,AtomicIntegerAtomicLong不实现 AtomicInteger3。

深入研究之后,我发现在可变类型上实现 Comparable并不是一个好的实践,因为对象可能在比较期间或比较之后发生变化,从而使比较结果变得无用。AtomicLongAtomicInteger都是可变的。API 设计人员预先考虑过不让 Number实现 Comparable,因为这会限制未来子类型的实现。事实上,AtomicLongAtomicInteger是在最初实现 java.lang.Number很久之后在 Java 1.5中添加的。

除了可变性,这里可能还有其他考虑因素。Number中的 compareTo实现必须将所有数值提升到 BigDecimal,因为它能够容纳所有的 Number子类型。在数学和性能方面,这种提升的含义对我来说有点不清楚,但是我的直觉认为这种解决方案是不合理的。

36591 次浏览

很可能是因为比较数字的效率相当低——允许这种比较的唯一适合每个 Number 的表示就是 BigDecimal。

相反,Number 的非原子子类实现了 Compable 本身。

原子比较是可变的,所以不能实现原子比较。

我的猜测是,由于没有实现 Comparable,因此它为实现类提供了更大的灵活性来实现它或不实现它。所有常见的数字(整数、长数、双数等)都实现了可比性。您仍然可以调用 Collections.sort,只要元素本身实现了 Compaable。

为了在数量上实现可比性,您必须为每个子类对编写代码。相反,只允许子类实现可比较性更容易。

查看类层次结构。诸如 Long、 Integer 等包装类实现了 Compaable,也就是说,一个 Integer 与一个整数相当,一个 Long 与一个 Long 相当,但是不能混合使用。至少在这个泛型范例中是这样的。我想这回答了你的问题“为什么”。

有关答案,请参阅 Java bug 检阅 窃听器4414323。您还可以找到来自 程序员的讨论

引用 Sun 对2001年 bug 报告的回应:

并非所有的“数字”都具有可比性; 可比性假定总的顺序为 数字是可能的,这不是偶数 浮点数真; (非数字)不小于, 大于或等于任何 浮点值,甚至它自己。 { Float,Double } 排序与排序不同 “ <”和“ =”的浮点数 操作员。此外,目前 实现,Number 的子类 只能与其他情况相比较 同班同学。还有其他 例如复数 存在标准的总订单, 虽然一个可以被定义 短的,不论是否是 数字是可比的,应该留作 这个子类的决定。

byte(原语)是 int(原语)。原语一次只有一个值。
语言设计规则允许这样做。

int i = 255


// down cast primitive
(byte) i == -1

Byte不是 IntegerByteNumberIntegerNumberNumber对象可以同时具有多个值。

Integer iObject = new Integer(255);
System.out.println(iObject.intValue());   // 255
System.out.println(iObject.byteValue());  // -1

如果 ByteIntegerIntegerNumber,那么在 compareTo(Number number1, Number number2)方法中使用哪个值?

值得一提的是下面这句话:

new Long(10).equals(new Integer(10))

总是 false,这往往会在某些时候绊倒每个人。因此,你不仅不能比较任意的 Number,而且你甚至不能确定它们是否相等。

另外,对于实际的基元类型(floatdouble) ,确定两个值是否相等是一件棘手的事情,必须在可接受的误差范围内完成。试试这样的代码:

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.

您能否编写一个可靠的 compareTo()方法,它不会转换成一系列 if instanceof 语句?Number实例只有六种可用的方法:

  • byteValue();
  • shortValue();
  • intValue();
  • longValue();
  • floatValue(); 及
  • doubleValue().

所以我猜 Sun 做出了(合理的)决定,即 Number对于它们自己的实例来说只是 Comparable

对于不同类型的 Number 没有标准的比较。 然而,您可以编写自己的比较器,并使用它来创建 TreeMap < Number,Object > 、 TreeSet < Number > 或 Collections.sort (List < Number > ,Compator)或 Arrays.sort (Number [] ,Compator) ;

可以使用 变形使用其 NumberCompator 类比较数字。

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);

写你自己的比较器

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() + ", ");
}
}
}

为什么这不是个好主意? :

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 类实现 Compaable,省略 compareTo 实现,并在一些类(如 Integer)中实现它,而在另一些类(如 AtomicInteger)中抛出 Unsupport tedException。

要尝试解决原始问题(对数字列表进行排序) ,一个选项是声明一个泛型类型的列表,扩展 Number 并实现 Compaable。

比如:

<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));
}