Java 等于原始速度与物体速度

Java equals primitive vs object speed

刚刚尝试测试使用 Objects.equals 与 Primitive 比较时 equals 的速度。如果有人需要代码:

import org.junit.Test;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.openjdk.jmh.runner.options.TimeValue;

import java.util.Objects;
import java.util.concurrent.TimeUnit;

class BaseEquals {
    byte bytePrim;
    short shortPrim;
    int intPrim;
    long longPrim;
    float floatPrim;
    double doublePrim;
    boolean booleanPrim;
    char charPrim;

    BaseEquals() {
        bytePrim = 1;
        shortPrim = 1;
        intPrim = 1;
        longPrim = 1;
        floatPrim = 1.0f;
        doublePrim = 1.0d;
        booleanPrim = true;
        charPrim = '1';
    }
}

class EqualsObjects extends BaseEquals {

    @Override
    public int hashCode() {
        return Objects.hash(bytePrim,
                shortPrim,
                intPrim,
                longPrim,
                floatPrim,
                doublePrim,
                booleanPrim,
                charPrim);
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        }
        if (!(obj instanceof EqualsObjects)) {
            return false;
        }
        EqualsObjects eo = (EqualsObjects)obj;
        return Objects.equals(bytePrim, eo.bytePrim)
                && Objects.equals(shortPrim, eo.shortPrim)
                && Objects.equals(intPrim, eo.intPrim)
                && Objects.equals(longPrim, eo.longPrim)
                && Objects.equals(floatPrim, eo.floatPrim)
                && Objects.equals(doublePrim, eo.doublePrim)
                && Objects.equals(booleanPrim, eo.booleanPrim)
                && Objects.equals(charPrim, eo.charPrim);
    }
}

class EqualsPrimitives extends BaseEquals {

    @Override
    public int hashCode() {
        return Objects.hash(bytePrim,
                shortPrim,
                intPrim,
                longPrim,
                floatPrim,
                doublePrim,
                booleanPrim,
                charPrim);
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        }
        if (!(obj instanceof EqualsPrimitives)) {
            return false;
        }
        EqualsPrimitives eo = (EqualsPrimitives)obj;
        return bytePrim == eo.bytePrim
                && shortPrim == eo.shortPrim
                && intPrim == eo.intPrim
                && longPrim == eo.longPrim
                && Float.compare(floatPrim, eo.floatPrim) == 0
                && Double.compare(doublePrim, eo.doublePrim) == 0
                && booleanPrim == eo.booleanPrim
                && charPrim == eo.charPrim;
    }
}

public class EqualsTests {

    @State(Scope.Benchmark)
    public static class MyState {
        EqualsObjects eo1;
        EqualsObjects eo2;
        EqualsPrimitives ep1;
        EqualsPrimitives ep2;

        @Setup
        public void setup() throws Throwable {
            eo1 = new EqualsObjects();
            eo2 = new EqualsObjects();
            ep1 = new EqualsPrimitives();
            ep2 = new EqualsPrimitives();
        }
    }

    @Benchmark
    public void equalsObject(MyState state) throws Throwable {
        boolean b1 = state.eo1.equals(state.eo2);
        boolean b2 = state.eo2.equals(state.eo1);
    }

    @Benchmark
    public void equalsPrimitive(MyState state) throws Throwable {
        boolean b1 = state.ep1.equals(state.ep2);
        boolean b2 = state.ep2.equals(state.ep1);
    }

    @Test
    public void launch() throws RunnerException {
        Options options = new OptionsBuilder()
                .include(this.getClass().getName() + ".*")
                .mode(Mode.AverageTime)
                .timeUnit(TimeUnit.MICROSECONDS)
                .warmupTime(TimeValue.seconds(1))
                .warmupIterations(5)
                .measurementTime(TimeValue.seconds(5))
                .measurementIterations(10)
                .threads(2)
                .forks(1)
                .shouldFailOnError(true)
                .shouldDoGC(true)
                .build();
        new Runner(options).run();
    }
}

我最后看到的是这样的结果:

Benchmark                    Mode  Cnt  Score    Error  Units
EqualsTests.equalsObject     avgt   10  0.026 ±  0.001  us/op
EqualsTests.equalsPrimitive  avgt   10  0.011 ±  0.001  us/op

您认为值得使用原始比较以获得更快的等于方法(可能忽略代码中的其他操作),或者使用 Objects.equals 以获得统一代码(不要考虑使用 Double.compare 和 Float.compare 分别用于 double 和 float 基元,== 用于其他基元)在 equals 方法中?

使用包装器而不是原语应该总是有原因的。基本上,你应该使用原语,但有时你需要一个包装器。

不同之处在于包装器可以是 null 并且原始值始终设置为其初始值。这意味着,如果您想拥有自己的初始状态,或者您想知道收到的 int 是 0 还是不存在,您肯定会使用包装器。

毫不奇怪,比较原语比比较包装器更快。调用 equals 的成本与调用任何其他方法的成本一样。无论如何,您的测试还应该比较比较大数字时的区别。现在,我们只能说,比较原始的比比较包装的更快。

Integer缓存数字从-128到127。这变化很大。

您必须使用 equals 方法,因为“==”检查原始类型之间的值相等性或相等的对象标识(即操作数是否是同一实例,而不仅仅是逻辑上相等)。

这全部计算为真:

42 == 42 // primitive values
int i = 42, j = 42; i == j // primitive values
Integer i = new Integer(42); i == 42 // auto-unboxing
Integer i = 42, j = 42; i == j // cached interned Integer instance

但这计算结果为 false,与您的预期相反:

Integer i = new Integer(42); Integer j = new Integer(42); i == j // not cached, different objects
Integer i = new Integer("42"); Integer j = new Integer("42"); i == j

仅当您要比较基本类型或希望实际检查引用相等性以查看两个操作数是否为同一实例时才使用 ==。即使对于作为一个操作数的基本类型和作为另一个操作数的包装类型,最好不要这样做,因为如果包装变量为空,自动拆箱会导致空指针异常。可以说 == 也可以用于枚举常量,但这往往会导致……争论。

可以在它们的字节码输出中看到两种代码之间的差异。

原始值比较只需一条 if_icmpne 指令即可完成,仅此而已。

参见 bytePrim == eo.bytePrim

的说明
20: astore_2
21: aload_0
22: getfield      #3                  // Field bytePrim:B
25: aload_2
26: getfield      #3                  // Field bytePrim:B
29: if_icmpne     246                 

另一方面,对象比较 (Object.equals) 需要在比较发生之前将基元装箱到它们的对象等价物(即 int 到 Integer、byte 到 Byte、char 到 Character 等)。一旦两个原语都被装箱,就会调用额外的 invokestatic 指令 (Objects.equals) 来完成比较(它在内部使用 null 检查等进行原语比较)

Objects.equals(bytePrim, eo.bytePrim)

的说明
21: aload_0
22: getfield      #3                  // Field bytePrim:B
25: invokestatic  #4                  // Method java/lang/Byte.valueOf:(B)Ljava/lang/Byte;
28: aload_2
29: getfield      #3                  // Field bytePrim:B
32: invokestatic  #4                  // Method java/lang/Byte.valueOf:(B)Ljava/lang/Byte;
35: invokestatic  #30                 // Method java/util/Objects.equals:(Ljava/lang/Object;Ljava/lang/Object;)Z
38: ifeq