2

假设我有一个名为 的类Number,我打算对对象进行大量的相等比较Number。我担心泛型Number::equals(Object o)方法的“开销”(类比较等)。Number::isEqualTo(Number other)在这种情况下,提供一种方法是否有用,例如Number::equals(Object o)? 这是一种常见的模式吗?或者 JVM 当前是否优化得足够好以至于这样做没有优势?

这是一个代码示例:

public class Number {
    int _value;

    Number(int value) {
        _value = value;
    }

    @Override
    public boolean equals(final Object o) {
        if (o == this) return true;
        if (o == null) return false;
        if (o.getClass() != getClass()) return false;
        return isEqualTo((Number)o);
    }

    public boolean isEqualTo(final Number other) {
        return _value == other._value;
    }

    public static void main(String[] args) {
        Number one = new Number(1);
        Number two = new Number(2);
        if (!one.isEqualTo(two)) {
            System.out.println("fast comparison?");
        }
        if (!one.equals(two)) {
            System.out.println("slow comparison?");
        }
    }
}
4

5 回答 5

5

这两种方法有不同的语义:

  • equalsObject::equals具有合同规定的语义,而
  • isEqualTo具有仅适用于Number对象的语义

由于比较不是苹果对苹果,因此equals需要更多 CPU 周期是公平的。但是,您不太可能注意到差异。

像您这样的类实现Comparable<T>. 那里的语义要求进行排序检查,而不仅仅是相等性检查,而且不需要获取未知类的对象,从而可以节省 CPU 周期。

您应该有充分的理由提供平等的替代方案(例如,分析器运行指出equals(Object)瓶颈,由于更改而对可读性的感知改进,或者由于采用更多功能的接口而实现更丰富的语义)。这样做是为了减少几个 CPU 周期会过早优化。

于 2012-11-20T18:38:04.823 回答
3

具有最不利场景(equals总是调用isEqualTo)的快速微基准显示(以毫秒为单位):

等于:1014
isEqualTo:1010

底线:除非您的程序不做任何其他事情,否则这不会成为性能瓶颈,您应该坚持优化的第一原则:首先配置文件,然后优化需要优化的内容。

测试代码:

public class TestPerf {

    private static int NUM_RUN;
    private static List<Number> list = new ArrayList<>();

    public static void main(String[] args) {
        NUM_RUN = 100_000;

        for (int i = 0; i < 10000; i++) {
            list.add(new Number(i));
        }

        long sum = 0;
        System.out.println("Warmup");

        for (int i = 0; i < NUM_RUN; i++) {
            sum += method1(17);
            sum += method2(17);
        }

        System.gc();

        System.out.println("Starting");

        sum = 0;
        long start = System.nanoTime();
        for (int i = 0; i < NUM_RUN; i++) {
            sum += method1(17);
        }
        long end = System.nanoTime();
        System.out.println("equals: " + (end - start) / 1000000);

        System.gc();

        start = System.nanoTime();
        for (int i = 0; i < NUM_RUN; i++) {
            sum += method2(17);
        }
        end = System.nanoTime();
        System.out.println("isEqualTo: " + (end - start) / 1000000);

        System.out.println(sum);
    }

    private static int method1(int target) {
        int sum = 0;
        Number comparison = new Number(target);
        for (Number n : list) {
            if (n.equals(comparison)) sum++;
        }
        return sum;
    }

    private static int method2(int target) {
        int sum = 0;
        Number comparison = new Number(target);
        for (Number n : list) {
            if (n.isEqualTo(comparison)) sum++;
        }
        return sum;
    }

    public static class Number {

        int _value;

        Number(int value) {
            _value = value;
        }

        @Override
        public boolean equals(final Object o) {
            if (o == this) return true;
            if (o == null) return false;
            if (o.getClass() != getClass()) return false;
            return isEqualTo((Number) o);
        }

        public boolean isEqualTo(final Number other) {
            return _value == other._value;
        }
    }
}
于 2012-11-20T18:37:28.750 回答
1

这取决于您想在哪里使用比较方法。

也许您可以使用 Comparator 接口的不同实现?

这些可以用于例如。排序列表。

于 2012-11-20T18:30:57.923 回答
1

您甚至可以提供equals自身的重载:equals(Number). 如果您非常仔细地实现它(在行为上与 没有区别equals(Object)),您可以通过在某些情况下避免检查向下转换来实现微不足道的加速。请注意,您仍然需要检查a.getClass() == b.getClass(),因此差异非常小。

于 2012-11-20T18:32:27.327 回答
0

xx.isEqualTo.yy是“对象”级别的比较。它只是检查这两个对象是否引用同一个对象。

为特定类编写“方程式方法”总是更好。例如,在这种情况下,最佳比较很简单==

于 2012-11-20T18:33:58.643 回答