【问题标题】:Why doesn't java.lang.Number implement Comparable? [duplicate]为什么 java.lang.Number 不实现 Comparable? [复制]
【发布时间】:2009-01-26 17:32:52
【问题描述】:

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

发布讨论更新:

感谢所有有用的回复。我最终做了some more research about this topic

为什么 java.lang.Number 没有实现 Comparable 的最简单解释是出于对可变性的担忧。

回顾一下,java.lang.NumberAtomicIntegerAtomicLongBigDecimalBigIntegerByteByteDoubleFloatInteger 的抽象超类型、LongShort。在该列表中,AtomicIntegerAtomicLong 不实现 Comparable

四处挖掘,我发现在可变类型上实现Comparable 不是一个好习惯,因为在比较期间或之后对象可能会发生变化,从而使比较结果变得无用。 AtomicLongAtomicInteger 都是可变的。 API 设计者有先见之明,不要让Number 实现Comparable,因为它会限制未来子类型的实现。事实上,AtomicLongAtomicInteger 是在 java.lang.Number 最初实现很久之后才添加到 Java 1.5 中的。

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

【问题讨论】:

标签: java sorting comparable


【解决方案1】:

值得一提的是以下表达式:

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

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

此外,对于真正的原始类型(floatdouble),确定两个值是否相等是很棘手的,并且必须在可接受的误差范围内完成。试试这样的代码:

double d1 = 1.0d;
double d2 = 0.0d;
for (int i=0; i<10; i++) {
  d2 += 0.1d;
}
System.out.println(d2 - d1);

你会留下一些小的差异。

回到制作NumberComparable的问题。你将如何实施它?使用doubleValue() 之类的东西不会可靠地做到这一点。请记住 Number 子类型是:

  • Byte;
  • Short;
  • Integer;
  • Long;
  • AtomicInteger;
  • AtomicLong;
  • Float;
  • Double;
  • BigInteger;和
  • BigDecimal

您能否编写一个可靠的compareTo() 方法,该方法不会演变为一系列 if instanceof 语句? Number 实例只有六个可用的方法:

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

所以我猜 Sun 做出了(合理的)决定,即 Numbers 只是 Comparable 对他们自己的实例。

【讨论】:

  • 谢谢克莱图斯。恕我直言,应该有一个中间数字类型,如 RealNumber(见上文)封装 Float、Double、Long、Integer、Short)。否则,您将缺乏多态性,最终导致这些类型及其相应的未装箱原始类型的代码重复。
  • Sun 可以将 Number 抽象化并包含 Comparable 作为接口。然后,所有特定的子类都可以在没有任何 instanceof 的情况下正确实现其类型的可比较性。我认为这是 Number 类型没有 Comparable 的缺点。
  • @Jason 那会更令人困惑,因为那时您希望能够比较数字,但它并不总是有效。
  • 这可能更令人困惑......或者对于了解系统的专家用户来说可能不是。对于专家用户来说,它也会更简单。在 Collections API 中也进行了同样的权衡。存在许多对某些底层实现没有意义的抽象方法。 Sun 没有从 API 中排除方法并要求程序员检查实现的类型,而是选择了方法的抽象版本,并建议在实现不支持该方法时抛出异常。请参阅List.setMap.putSet.remove 和更多。
【解决方案2】:

有关答案,请参阅 Java bugparade bug 4414323。你也可以从comp.lang.java.programmer找到讨论

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

所有“数字”都没有可比性; 可比较假设总排序为 数字是可能的。这甚至不是 浮点数为真;钠 (不是数字)不小于, 大于,也不等于任何 浮点值,甚至它本身。 {Float, Double}.compare 施加总计 订购不同于订购 浮点“

【讨论】:

  • 埃迪:谢谢你的链接。我同意错误报告说 Java 需要一个 RealNumber 类型,它是所有数字类型的超类,具有相应的原始数字类型(例如 Float 等)。没有数字,在 Java 中处理数字仍然很困难。
  • 这是另一个(有点不满意)解释:forums.sun.com/thread.jspa?threadID=5306968
  • 我们有没有机会直接从文章中引用来编辑这个答案?它足够短,应该可以毫无问题地放在这里。
  • Sun 可以选择一个程序员可以覆盖的合理默认值。这不是借口。当涉及到空字符串或空引用时,字符串也存在同样的问题。空引用是大于还是小于字符串“foo”? Sun 选择了一个有意义的顺序。
【解决方案3】:

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

【讨论】:

    【解决方案4】:

    要尝试解决原始问题(对数字列表进行排序),一种选择是声明扩展 Number 并实现 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));
    }
    

    【讨论】:

      【解决方案5】:

      很可能是因为比较数字的效率相当低 - 每个数字可以适合进行此类比较的唯一表示是 BigDecimal。

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

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

      【讨论】:

      • 不是这样的。并不是所有的数字在总排序中都可以相互比较。
      • 继续 - java.lang 中 Number 的所有子类都是有理数;有一个实数的总排序;有什么问题?
      • NaN 不能与任何其他值一起排序。不小于,不大于,不等于任何值,包括它自己。
      • 现有的可比较工作的实现已经解决了这个问题(IIRC 将 NaN 置于 -Infinity 之下)。这也是反对 Double 或 Float 实施 Comparable 的论据,但他们确实如此。
      • 是的,仅仅因为 NaN 而不允许比较每个数字有点疯狂。能够比较每个数字该有多酷,对于使用其他语言的人来说这甚至是一个问题有多奇怪? :)
      【解决方案6】:

      您可以使用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);
      

      【讨论】:

        【解决方案7】:

        编写你自己的比较器

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

        【讨论】:

        • 这对于某些浮点数将无法正常工作。浮点数的内部表示可能会导致两个浮点数 A 和 B 在它们应该相等时不相等。
        【解决方案8】:

        对于不同类型的数字没有标准比较。 但是,您可以编写自己的 Comparator 并使用它来创建 TreeMap、TreeSet 或 Collections.sort(List, Comparator) 或 Arrays.sort(Number[], Comparator);

        【讨论】:

          【解决方案9】:

          为什么这是个坏主意? :

          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 实现,并在 Integer 等某些类中实现它,而在 AtomicInteger 等其他类中抛出 UnsupportedException。

          【讨论】:

            【解决方案10】:

            我的猜测是,通过不实现 Comparable,它为实现或不实现它的类提供了更大的灵活性。所有常见的数字(Integer、Long、Double 等)都实现了 Comparable。只要元素本身实现 Comparable,您仍然可以调用 Collections.sort。

            【讨论】:

              【解决方案11】:

              查看类层次结构。包装类如 Long、Integer 等实现 Comparable,即 Integer 与整数相当,long 与 long 相当,但不能混合使用。至少对于这种泛型范式。我猜这回答了你的问题“为什么”。

              【讨论】:

                【解决方案12】:

                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) 方法中使用哪个值?

                【讨论】:

                  猜你喜欢
                  • 2019-05-02
                  • 1970-01-01
                  • 2019-10-11
                  • 1970-01-01
                  • 2011-01-22
                  • 1970-01-01
                  • 2012-07-20
                  • 2014-09-21
                  相关资源
                  最近更新 更多