【问题标题】:Comparing the values of two generic Numbers比较两个通用数字的值
【发布时间】:2010-04-21 13:19:54
【问题描述】:

我想与T extends Number 类型的变量进行比较。现在我想知道这两个变量中的哪一个大于或等于另一个。不幸的是我还不知道确切的类型,我只知道它将是java.lang.Number 的子类型。我该怎么做?

编辑:我使用TreeSets 尝试了另一种解决方法,它实际上适用于自然排序(当然它有效,Number 的所有子类都实现了Comparable,除了 AtomicInteger 和 AtomicLong) .因此我会丢失重复的值。使用Lists 时,Collection.sort() 将不接受我的列表,因为绑定不匹配。很不满意。

【问题讨论】:

  • 嗯 - 这是相关的:stackoverflow.com/questions/480632/…
  • 这不应被标记为“泛型”。在这种情况下,“泛型”不是指 Java 泛型。
  • Google Collections 有一个 TreeMultiSet,可让您对内容进行排序而不会丢失重复项。

标签: java generics numbers comparable


【解决方案1】:

这应该适用于所有扩展 Number 的类,并且与它们自己是 Comparable 的。通过添加 & Comparable,您可以删除所有类型检查,并与 Sarmun 答案相比免费提供运行时类型检查和错误抛出。

class NumberComparator<T extends Number & Comparable> implements Comparator<T> {

    public int compare( T a, T b ) throws ClassCastException {
        return a.compareTo( b );
    }
}

【讨论】:

  • 这个答案有什么缺点吗?否则它可能应该被选为最佳答案。
  • 它要求源集合是特定类型,所以它不适用于抽象 Number 类。
  • 这仅在一个的 compareTo 方法实际接受另一个时才有效。如果使用 Long 和 Double 调用它会抛出 ClassCastException。
【解决方案2】:

一个可行的(但脆弱的)解决方案是这样的:

class NumberComparator implements Comparator<Number> {

    public int compare(Number a, Number b){
        return new BigDecimal(a.toString()).compareTo(new BigDecimal(b.toString()));
    }

}

尽管如此,它仍然不是很好,因为它依赖于 toString 返回一个可由 BigDecimal 解析的值(标准 Java Number 类会这样做,但 Number 合约不需要)。

七年后编辑:正如 cmets 中所指出的,(至少?)toString 可以产生三种特殊情况,您需要考虑:

【讨论】:

  • 这不太可能,但如果我创建 Number 的子类并且没有以正确的方式覆盖 toString,它可能会导致 NumberFormatException。
  • 希望程序员知道是否会遇到除标准编号之外的任何 Number 后代。
  • Zeeks!这是一个非常糟糕的解决方案。虽然我不能马上想到一个更好的,所以我不会投反对票。 :-(
  • @user949300 正如我所说,这是一个有效(但脆弱)的解决方案,但不是很好 - 所以,我同意这是一个坏的解决方案!但是整个前提是有缺陷的,通常不应该使用任意的Numbers。这里最好的可能是将类型参数进一步限制为T extends Number &amp; Comparable&lt;T&gt;
  • "toString 返回一个可由BigDecimal 解析的值(标准Java Number 类会这样做) 这并不完全正确,因为DoubleFloat 可以返回 3 个特殊值(NaNInfinity-Infinity),它们BigDecimal 所支持。
【解决方案3】:

在问了similar question 并在这里研究了答案之后,我想出了以下内容。我认为它比 gustafc 给出的解决方案更高效、更健壮:

public int compare(Number x, Number y) {
    if(isSpecial(x) || isSpecial(y))
        return Double.compare(x.doubleValue(), y.doubleValue());
    else
        return toBigDecimal(x).compareTo(toBigDecimal(y));
}

private static boolean isSpecial(Number x) {
    boolean specialDouble = x instanceof Double
            && (Double.isNaN((Double) x) || Double.isInfinite((Double) x));
    boolean specialFloat = x instanceof Float
            && (Float.isNaN((Float) x) || Float.isInfinite((Float) x));
    return specialDouble || specialFloat;
}

private static BigDecimal toBigDecimal(Number number) {
    if(number instanceof BigDecimal)
        return (BigDecimal) number;
    if(number instanceof BigInteger)
        return new BigDecimal((BigInteger) number);
    if(number instanceof Byte || number instanceof Short
            || number instanceof Integer || number instanceof Long)
        return new BigDecimal(number.longValue());
    if(number instanceof Float || number instanceof Double)
        return new BigDecimal(number.doubleValue());

    try {
        return new BigDecimal(number.toString());
    } catch(final NumberFormatException e) {
        throw new RuntimeException("The given number (\"" + number + "\" of class " + number.getClass().getName() + ") does not have a parsable string representation", e);
    }
}

【讨论】:

  • 请注意,应该使用 BigDecimal.valueOf 而不是构造函数,特别是对于 Float/Double 创建,否则您将在转换中得到 base2 浮点错误。
  • 感谢您的评论!我不确定你的观点是否有效。 double 构造函数的 Javadoc 说:“[...],请注意,此构造函数提供了精确的转换;它与使用 Double.toString(double) 方法然后使用 @987654324 将 double 转换为 String 的结果不同@构造函数。”确实,精确的转换通常会产生意想不到的结果(例如,对于 0.1 文字)。但由于我们对这些数字的来源一无所知,我认为处理它们的唯一明智方法是对此一无所知。
【解决方案4】:

一种可能适合您的解决方案是不使用T extends Number,而是使用T extends Number &amp; Comparable。这种类型意味着:“T 只能设置为实现两个接口的类型。”

这使您可以编写适用于所有可比数字的代码。静态类型且优雅。

这与 BennyBoy 提出的解决方案相同,但它适用于各种方法,不仅适用于比较器类。

public static <T extends Number & Comparable<T>> void compfunc(T n1, T n2) {
    if (n1.compareTo(n2) > 0) System.out.println("n1 is bigger");
}

public void test() {
    compfunc(2, 1); // Works with Integer.
    compfunc(2.0, 1.0); // And all other types that are subtypes of both Number and Comparable.
    compfunc(2, 1.0); // Compilation error! Different types.
    compfunc(new AtomicInteger(1), new AtomicInteger(2)); // Compilation error! Not subtype of Comparable
}

【讨论】:

    【解决方案5】:

    最“通用”的 Java 原始数是双精度数,所以使用简单

    a.doubleValue() > b.doubleValue()
    

    在大多数情况下应该足够了,但是......在将数字转换为双精度时存在一些微妙的问题。例如,使用 BigInteger 可以实现以下操作:

        BigInteger a = new BigInteger("9999999999999992");
        BigInteger b = new BigInteger("9999999999999991");
        System.out.println(a.doubleValue() > b.doubleValue());
        System.out.println(a.doubleValue() == b.doubleValue());
    

    结果:

    false
    true
    

    虽然我认为这是非常极端的情况,但这是可能的。不——没有通用的 100% 准确的方法。 Number 接口没有像exactValue() 这样的方法,可以将其转换为能够完美表示数字而不会丢失任何信息的类型。

    实际上,拥有这样完美的数字通常是不可能的 - 例如,使用有限空间的任何算术来表示数字 Pi 是不可能的。

    【讨论】:

    • 请注意,这也不适用于 Long ,因为它们也不能使用 Double 完全表示。
    【解决方案6】:

    这个呢?绝对不是很好,但它处理了所有提到的必要情况。

    public class SimpleNumberComparator implements Comparator<Number>
        {
            @Override
            public int compare(Number o1, Number o2)
            {
                if(o1 instanceof Short && o2 instanceof Short)
                {
                    return ((Short) o1).compareTo((Short) o2);
                }
                else if(o1 instanceof Long && o2 instanceof Long)
                {
                    return ((Long) o1).compareTo((Long) o2);
                }
                else if(o1 instanceof Integer && o2 instanceof Integer)
                {
                    return ((Integer) o1).compareTo((Integer) o2);
                }
                else if(o1 instanceof Float && o2 instanceof Float)
                {
                    return ((Float) o1).compareTo((Float) o2);
                }
                else if(o1 instanceof Double && o2 instanceof Double)
                {
                    return ((Double) o1).compareTo((Double) o2);
                }
                else if(o1 instanceof Byte && o2 instanceof Byte)
                {
                    return ((Byte) o1).compareTo((Byte) o2);
                }
                else if(o1 instanceof BigInteger && o2 instanceof BigInteger)
                {
                    return ((BigInteger) o1).compareTo((BigInteger) o2);
                }
                else if(o1 instanceof BigDecimal && o2 instanceof BigDecimal)
                {
                    return ((BigDecimal) o1).compareTo((BigDecimal) o2);
                }
                else
                {
                    throw new RuntimeException("Ooopps!");
                }
    
            }
    
        }
    

    【讨论】:

    • 就像你说的,不好,不应该在这里:)
    • @keni 但是你有更好的解决方案吗?
    • 它实际上并没有处理所有必要的情况,当然不会比@BennyBoy 和 Lii 多很多,并且会遇到 Pr0methean 指出的相同问题。是的,而且更冗长,而且还没有接近修改。没有冒犯的意思。
    【解决方案7】:

    这应该适用于所有扩展 Number 的类,并且可以与它们自己比较。

    class NumberComparator<T extends Number> implements Comparator<T> {
    
        public int compare(T a, T b){
            if (a instanceof Comparable) 
                if (a.getClass().equals(b.getClass()))
                    return ((Comparable<T>)a).compareTo(b);        
            throw new UnsupportedOperationException();
        }
    }
    

    【讨论】:

      【解决方案8】:
      if(yourNumber instanceof Double) {
          boolean greaterThanOtherNumber = yourNumber.doubleValue() > otherNumber.doubleValue();
          // [...]
      }
      

      注意:instanceof 检查不是必需的 - 取决于您想要比较它们的确切程度。你当然可以简单地一直使用.doubleValue(),因为每个号码都应该提供here列出的方法。

      编辑:如 cmets 中所述,您将(始终)必须检查 BigDecimal 和朋友。但是他们提供了.compareTo() 方法:

      if(yourNumber instanceof BigDecimal && otherNumber instanceof BigDecimal) { 
          boolean greaterThanOtherNumber = ((BigDecimal)yourNumber).compareTo((BigDecimal)otherNumber) > 0;
      } 
      

      【讨论】:

      • BigDecimal 或其他超出双打范围的类型呢?
      • 只需添加一个 if(yourNumber instanceof BigDecimal && otherNumber instanceof BigDecimal){ boolean GreaterThanOtherNumber = yourNumber.compareTo(otherNumber) > 0; }
      【解决方案9】:

      您可以简单地使用Number's doubleValue() 方法来比较它们;但是您可能会发现结果不够准确,无法满足您的需求。

      【讨论】:

      • 我觉得这样太冒险了
      • BigInteger 和 BigDecimal 也实现了 Number。这行不通。
      • 没错,它可能是有风险的,它可能会失败。除了我提到的精度/准确性问题之外,还有更多警告。但如果他不需要处理大的 BigDecimal,它可能适合 OP 的需求。
      【解决方案10】:

      假设您有一些方法,例如:

      public <T extends Number> T max (T a, T b) {
         ...
         //return maximum of a and b
      }
      

      如果您知道只有整数、长整数和双精度可以作为参数传递,那么您可以将方法签名更改为:

      public <T extends Number> T max(double a, double b) {
         return (T)Math.max (a, b);
      }
      

      这适用于字节、短、整数、长和双精度。

      如果您假定可以传递 BigInteger 或 BigDecimal 或浮点数和双精度数的混合,那么您无法创建一种通用方法来比较所有这些类型的参数。

      【讨论】:

        【解决方案11】:

        如果您的 Number 实例是从不原子的(即 AtomicInteger),那么您可以执行以下操作:

        private Integer compare(Number n1, Number n2) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
        
         Class<? extends Number> n1Class = n1.getClass();
         if (n1Class.isInstance(n2)) {
          Method compareTo = n1Class.getMethod("compareTo", n1Class);
          return (Integer) compareTo.invoke(n1, n2);
         }
        
         return -23;
        }
        

        这是因为所有非原子Numbers 都实现了 Comparable

        编辑

        由于反思,这很昂贵:我知道

        编辑 2

        这当然不包括您想要将小数与整数或其他类似的情况进行比较...

        编辑 3

        这假设没有未实现 Comparable 的自定义 Number 后代(感谢 @DJClayworth)

        【讨论】:

        • 这假设没有自定义的 Number 后代。
        • @DJClayworth:够真实的。这些并不是我上面提到的唯一限制。但为什么会有“数字的自定义后代”?
        • Number 的自定义后代的一个示例是 org.apache.commons.math.fraction.BigFraction,我在许多程序中都使用它。
        猜你喜欢
        • 2013-03-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-05
        • 2013-05-07
        • 1970-01-01
        相关资源
        最近更新 更多