我有以下Java类:
import org.apache.commons.lang3.builder.EqualsBuilder;
public class Animal {
private final String name;
private final int numLegs;
public Animal(String name, int numLegs) {
this.name = name;
this.numLegs = numLegs;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Animal animal = (Animal)o;
return new EqualsBuilder().append(numLegs, animal.numLegs)
.append(name, animal.name)
.isEquals();
}
}
以及以下Spock测试:
import spock.lang.Specification
class AnimalSpec extends Specification {
def 'animal with same name and numlegs should be equal'() {
when:
def animal1 = new Animal("Fluffy", 4)
def animal2 = new Animal("Fluffy", 4)
def animal3 = new Animal("Snoopy", 4)
def notAnAnimal = 'some other object'
then:
animal1 == animal1
animal1 == animal2
animal1 != animal3
animal1 != notAnAnimal
}
}
然后,当运行coverage时,第一个语句animal1 == animal1
不会到达equals(o)
方法:
为什么Groovy / Spock没有运行第一条语句?我假设进行了微优化,但是当我犯错(如
)时@Override
public boolean equals(Object o) {
if (this == o) {
return false;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Animal animal = (Animal)o;
return new EqualsBuilder().append(numLegs, animal.numLegs)
.append(name, animal.name)
.isEquals();
}
测试仍然是绿色的。为什么会这样?
在星期日的早晨编辑: 我进行了更多测试,发现这甚至不是一项优化,但在运行此测试时甚至会导致大量调用的开销:
class AnimalSpec extends Specification {
def 'performance test of == vs equals'() {
given:
def animal = new Animal("Fluffy", 4)
when:
def doubleEqualsSignBenchmark = 'benchmark 1M invocation of == on'(animal)
def equalsMethodBenchmark = 'benchmark 1M invocation of .equals(o) on'(animal)
println "1M invocation of == took ${doubleEqualsSignBenchmark} ms and 1M invocations of .equals(o) took ${equalsMethodBenchmark}ms"
then:
doubleEqualsSignBenchmark < equalsMethodBenchmark
}
long 'benchmark 1M invocation of == on'(Animal animal) {
return benchmark {
def i = {
animal == animal
}
1.upto(1_000_000, i)
}
}
long 'benchmark 1M invocation of .equals(o) on'(Animal animal) {
return benchmark {
def i = {
animal.equals(animal)
}
1.upto(1_000_000, i)
}
}
def benchmark = { closure ->
def start = System.currentTimeMillis()
closure.call()
def now = System.currentTimeMillis()
now - start
}
}
我希望这个测试能够成功,但是我跑了好几次,但是从来没有绿色...
1M invocation of == took 164 ms and 1M invocations of .equals(o) took 139ms
Condition not satisfied:
doubleEqualsSignBenchmark < equalsMethodBenchmark
| | |
164 | 139
false
当更多调用增加到1B时,优化就变得可见了:
1B invocation of == took 50893 ms and 1B invocations of .equals(o) took 75568ms
答案 0 :(得分:10)
存在此优化是因为以下表达式:
animal1 == animal1
Groovy转换为以下方法调用:
ScriptBytecodeAdapter.compareEqual(animal1, animal1)
现在,如果我们看一下this method's source code,我们会发现,在第一步中,此方法使用了很好的旧Java对象引用比较-如果表达式的两边都指向同一个引用,则简单地返回true
和equals(o)
或compareTo(o)
(在比较实现Comparable<T>
接口的对象的情况下)方法不会被调用:
public static boolean compareEqual(Object left, Object right) {
if (left==right) return true;
Class<?> leftClass = left==null?null:left.getClass();
Class<?> rightClass = right==null?null:right.getClass();
// ....
}
在您的情况下,left
和right
变量都指向相同的对象引用,因此方法中的第一次检查匹配,并且true
被返回。
如果在此位置放置断点(ScriptBytecodeAdapter.java
行685),则会看到调试器已到达该点,并且它从此方法的第一行返回true
。
作为一个不错的练习,您可以看以下示例。这是一个简单的Groovy脚本(称为Animal_script.groovy
),它使用Animal.java
类并进行对象比较:
def animal1 = new Animal("Fluffy", 4)
def animal2 = new Animal("Fluffy", 4)
def animal3 = new Animal("Snoopy", 4)
println animal1 == animal1
如果您对其进行编译并在IntelliJ IDEA中打开Animal_script.class
文件(以便可以将其反编译为Java),您将看到类似以下内容:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import groovy.lang.Binding;
import groovy.lang.Script;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.runtime.callsite.CallSite;
public class Animal_script extends Script {
public Animal_script() {
CallSite[] var1 = $getCallSiteArray();
}
public Animal_script(Binding context) {
CallSite[] var2 = $getCallSiteArray();
super(context);
}
public static void main(String... args) {
CallSite[] var1 = $getCallSiteArray();
var1[0].call(InvokerHelper.class, Animal_script.class, args);
}
public Object run() {
CallSite[] var1 = $getCallSiteArray();
Object animal1 = var1[1].callConstructor(Animal.class, "Fluffy", 4);
Object animal2 = var1[2].callConstructor(Animal.class, "Fluffy", 4);
Object animal3 = var1[3].callConstructor(Animal.class, "Snoopy", 4);
return var1[4].callCurrent(this, ScriptBytecodeAdapter.compareEqual(animal1, animal1));
}
}
如您所见,animal1 == animal1
在Java运行时中被视为ScriptBytecodeAdapter.compareEqual(animal1, animal1))
。
希望有帮助。