Comparing the Values of Two Generic Numbers in Java – 用 Java 比较两个通用数的值

最后修改: 2023年 11月 13日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.导言

Java’s versatility is evident in its ability to handle generic Number objects.

Java 的多功能性体现在其处理通用 Number 对象的能力上。

In this tutorial, we’ll delve into the nuances of comparing these objects, offering detailed insights and code examples for each strategy.

在本教程中,我们将深入探讨比较这些对象的细微差别,为每种策略提供详细的见解和代码示例。

2. Using doubleValue() Method

2.使用 doubleValue() 方法

Converting both Number objects to their double representation is a foundational technique in Java.

Number 对象转换为其 double 表示形式是 Java 中的一项基础技术。

While this approach is intuitive and straightforward, it’s not without its caveats.

虽然这种方法直观而简单,但也不是没有注意事项。

When converting numbers to their double form, there’s a potential for precision loss. This is especially true for large floating-point numbers or numbers with many decimal places:

将数字转换为 double 形式时,可能会损失精度。对于大浮点数或小数位数较多的数字,情况尤其如此:

public int compareDouble(Number num1, Number num2) {
    return Double.compare(num1.doubleValue(), num2.doubleValue());
}

We must be vigilant and consider the implications of this conversion, ensuring that the results remain accurate and reliable.

我们必须保持警惕,考虑这种转换的影响,确保结果仍然准确可靠。

3. Using compareTo() Method

3.使用 compareTo() 方法

Java’s wrapper classes are more than just utility classes for primitive types. The abstract class Number doesn’t implement the compareTo() method, but classes like Integer, Double, or BigInteger have a built-in compareTo() method.

Java的封装类不仅仅是原始类型的实用类。抽象类 Number 没有实现 compareTo() 方法,但是 Integer, Double, BigInteger 等类有内置的 compareTo() 方法。

Let’s create our custom compareTo() for type-specific comparisons, ensuring both type safety and precision:

让我们创建自定义 compareTo() 以进行特定类型比较,确保类型安全和精确:

// we create a method that compares Integer, but this could also be done for other types e.g. Double, BigInteger
public int compareTo(Integer int1, Integer int2) {
    return int1.compareTo(int2);
}

However, when working with several different types, we might encounter challenges.

然而,在与几种不同的类型打交道时,我们可能会遇到挑战。

It’s essential to understand the nuances of each wrapper class and how they interact with one another to ensure accurate comparisons.

必须了解每个封装类的细微差别以及它们之间的相互作用,以确保进行准确的比较。

4. Using BiFunction and Map

4.使用 BiFunctionMap

Java’s ability to seamlessly integrate functional programming with traditional data structures is remarkable.

Java 将函数式编程与传统数据结构无缝集成的能力非常出色。

Let’s create a dynamic comparison mechanism using BiFunction by mapping each Number subclass to a specific comparison function using maps:

让我们使用 BiFunction 创建一个动态比较机制,方法是使用 maps 将每个 Number 子类映射到一个特定的比较函数:

// for this example, we create a function that compares Integer, but this could also be done for other types e.g. Double, BigInteger
Map<Class<? extends Number>, BiFunction<Number, Number, Integer>> comparisonMap
  = Map.ofEntries(entry(Integer.class, (num1, num2) -> ((Integer) num1).compareTo((Integer) num2)));

public int compareUsingMap(Number num1, Number num2) {
    return comparisonMap.get(num1.getClass())
      .apply(num1, num2);
}

This approach offers both versatility and adaptability, allowing for comparisons across various number types. It’s a testament to Java’s flexibility and its commitment to providing us with powerful tools.

这种方法具有多功能性和适应性,允许对各种数字类型进行比较。这证明了 Java 的灵活性及其为我们提供强大工具的承诺。

5. Using Proxy and InvocationHandler

5.使用 ProxyInvocationHandler

Let’s look into Java’s more advanced features, like proxies combined with InvocationHandlers, which offer a world of possibilities.

让我们来了解一下 Java 更高级的功能,例如代理与 InvocationHandlers 的结合,它提供了一个充满可能性的世界。

This strategy allows us to craft dynamic comparators that can adapt on the fly:

这种策略使我们能够设计出动态的比较器,以便随时进行调整:

public interface NumberComparator {
    int compare(Number num1, Number num2);
}

NumberComparator proxy = (NumberComparator) Proxy
  .newProxyInstance(NumberComparator.class.getClassLoader(), new Class[] { NumberComparator.class },
  (p, method, args) -> Double.compare(((Number) args[0]).doubleValue(), ((Number) args[1]).doubleValue()));

While this approach provides unparalleled flexibility, it also requires a deep understanding of Java’s inner workings. It’s a strategy best suited for those well-versed in Java’s advanced capabilities.

虽然这种方法提供了无与伦比的灵活性,但它也要求对 Java 的内部运作有深入的了解。这种策略最适合那些精通 Java 高级功能的人。

6. Using Reflection

6.使用反射

Java’s Reflection API is a powerful tool, but it comes with its own set of challenges. It allows us to introspect and dynamically determine types and invoke methods:

Java 的 Reflection API 是一个功能强大的工具,但它也带来了一系列挑战。它允许我们内省和动态地确定类型并调用方法:

public int compareUsingReflection(Number num1, Number num2) throws Exception {
    Method method = num1.getClass().getMethod("compareTo", num1.getClass());
    return (int) method.invoke(num1, num2);
}

We must be careful with using Java’s Reflection because not all the Number classes have the compareTo() method implemented, so we might encounter errors, e.g., when using AtomicInteger and AtomicLong.

我们必须小心使用 Java 的 Reflection,因为并非所有的 Number 类都实现了 compareTo() 方法,因此我们可能会遇到错误,例如,在使用 AtomicIntegerAtomicLong 时。

However, reflection can be performance-intensive and may introduce potential security vulnerabilities. It’s a tool that demands respect and careful usage, ensuring its power is harnessed responsibly.

不过,反射可能会消耗大量性能,并可能带来潜在的安全漏洞。这是一个需要尊重和谨慎使用的工具,确保以负责任的方式利用它的力量。

7. Using Functional Programming

7.使用函数式编程

Java’s evolution has seen a significant shift towards functional programming. This paradigm allows us to craft concise and expressive comparisons using transformation functions, predicates, and other functional constructs:

Java在发展过程中已向函数式编程发生了重大转变。

Function<Number, Double> toDouble = Number::doubleValue;
BiPredicate<Number, Number> isEqual = (num1, num2) -> toDouble.apply(num1).equals(toDouble.apply(num2));

@Test
void givenNumbers_whenUseIsEqual_thenWillExecuteComparison() {
    assertEquals(true, isEqual.test(5, 5.0));
}

It’s an approach that promotes cleaner code and offers a more intuitive way to handle number comparisons.

这种方法可使代码更简洁,并提供一种更直观的方法来处理数字比较。

8. Using Dynamic Comparators with Function

8.使用函数动态比较器

Java’s Function interface is a cornerstone of its commitment to functional programming. By using this interface to craft dynamic comparators, we’re equipped with a flexible and type-safe tool:

Java 的 Function 接口是其致力于函数式编程的基石。通过使用该接口来制作动态比较器,我们获得了一个灵活且类型安全的工具:

private boolean someCondition;
Function<Number, ?> dynamicFunction = someCondition ? Number::doubleValue : Number::intValue;
Comparator<Number> dynamicComparator = (num1, num2) -> ((Comparable) dynamicFunction.apply(num1))
  .compareTo(dynamicFunction.apply(num2));

@Test
void givenNumbers_whenUseDynamicComparator_thenWillExecuteComparison() {
    assertEquals(0, dynamicComparator.compare(5, 5.0));
}

It’s an approach that showcases Java’s modern capabilities and its dedication to providing cutting-edge tools.

这种方法展示了 Java 的现代能力及其提供尖端工具的奉献精神。

9. Conclusion

9.结论

The diverse strategies for comparing generic Number objects in Java have unique characteristics and use cases.

Java 中用于比较通用 Number 对象的各种策略具有独特的特点和用例。

Selecting the appropriate method depends on the context and requirements of our application, and a thorough understanding of each strategy is essential for making an informed decision.

选择合适的方法取决于我们应用的背景和要求,而透彻了解每种策略对于做出明智的决定至关重要。

As always, the complete code samples for this article can be found over on GitHub.

与往常一样,本文的完整代码示例可在 GitHub 上找到