在使用C#和Java等语言访问实例字段,属性和方法时,this
关键字是可选的。
我最近一直在做各种语言的最佳实践研究,并注意到许多地方建议在方法中创建实例字段的本地引用,因为它更有效。最近提到的是Android教程。
在我看来,如果指定this._obj
,它应该与局部变量一样高效。这是正确的还是与使用this
一样“昂贵”?
答案是否从Android Dalvik VM,标准Java变为C#?
public class test {
private Object[] _obj;
protected void myMethod() {
Object[] obj = _obj;
// Is there an appreciable differnce between
for(int i = 0; i < obj.length; i++) {
// do stuff
}
// and this?
for(int i = 0; i < this._obj.length; i++) {
// do stuff
}
}
}
答案 0 :(得分:3)
至少对于标准Java,存在一些小的差异。
我稍微修改了你的例子:
public class test {
private Object[] _obj;
protected void myMethodLocal() {
Object[] obj = _obj;
// Is there an appreciable differnce between
for(int i = 0; i < obj.length; i++) {
// do stuff
}
}
protected void myMethodMember() {
// and this?
for(int i = 0; i < this._obj.length; i++) {
// do stuff
}
}
}
因此myMethodLocal()
会将_obj
缓存到本地变量中,而myMethodMember()
会使用类成员_obj
。
现在,让我们反编译(使用 javap ):
protected void myMethodLocal();
Code:
0: aload_0
1: getfield #2; //Field _obj:[Ljava/lang/Object;
4: astore_1
5: iconst_0
6: istore_2
7: iload_2
8: aload_1
9: arraylength
10: if_icmpge 19
13: iinc 2, 1
16: goto 7
19: return
protected void myMethodMember();
Code:
0: iconst_0
1: istore_1
2: iload_1
3: aload_0
4: getfield #2; //Field _obj:[Ljava/lang/Object;
7: arraylength
8: if_icmpge 17
11: iinc 1, 1
14: goto 2
17: return
不详细说明,后一个示例必须在每次循环迭代时访问_obj
字段,而第一个示例已经将其缓存在本地引用中,只需要访问本地引用。
这与速度差异有什么关系?
不多。
虽然访问本地引用和类引用之间的区别意味着在诸如Python之类的语言中更多,但对于Java,您实际上并不需要担心。保持代码的可读性和可维护性比纠正这样的细节要重要得多。
(另外,上面的字节码没有考虑JIT编译器可能会做什么,无论如何)。
如果您通过函数获取实例字段,例如getObj()
,我会将其插入变量中,因此您无需每次调用getObj()
你希望使用相同领域的时间。
另外,作为一个小注释,您可能应该调用您的类Test
而不是test.
Java倾向于使用Upper Camel Case作为类名。
答案 1 :(得分:2)
不,效率绝对没有变化。请记住,在许多语言中,几个等效表达式将减少到底层字节码或汇编中的相同语句或高级语言转换为的任何语句。
您提到的语言和虚拟机的答案是统一的。
必要时使用它,例如当方法参数与实例变量同名时。
除非CPU周期(或内存等)是最优先考虑的事项,否则价值清晰度低于表达能力较强但语言语法更有效。
答案 2 :(得分:1)
this
关键字用于提高可读性,最重要的是使变量名称明确无误。它对性能没有任何影响。
答案 3 :(得分:0)
在现代PC上,由于缓存而不管语言如何,这可能没有任何区别 - 如果内存位置缓存在片上染色上,那么它就没有任何区别。
答案 4 :(得分:0)
我怀疑使用局部变量只是一个引用(值在堆栈上),而使用成员变量是2引用(引用 this ,它在堆栈上,然后引用变量本身,它在堆上)
根据系统的不同,堆或堆栈访问可能会更快。
但是就像Jonathon所说的那样,除非速度非常重要,否则不要为此烦恼。它只会降低可读性,而且性能可以忽略不计。
答案 5 :(得分:0)
理论上,没有。访问“this._obj.Length”最终会生成如下代码:
mov eax, [ecx + offset_of_obj]
mov eax, [eax + offset_of_length]
其中“obj.length”最终生成如下代码:
mov eax, [esp + offset_of_obj]
mov eax, [eax + offset_of_length]
在实践中,也许,但可能不是。几乎每个x86调用约定都只有3个临时寄存器“eax”,“ecx”和“edx”。所有其他寄存器必须先保存在堆栈中,然后才能更新。如果你有一个很长的函数,并且你不需要访问“this”那么ecx寄存器可以重新用于保存临时变量,从而可以减少需要发生的堆栈溢出量。但是,您必须在堆栈上推送新值才能创建本地,因此它可以进行改进的场景是有限的。我会忽略曾经告诉过你的人。
答案 6 :(得分:0)
其中一些答案没有回答实际问题而其他答案是错误的。通过this.obj访问成员变量需要取消引用堆栈上的元素。访问该引用的本地副本消除了解除引用步骤。因此在理论上并且缺少HotSpot后者必须更有效率。然而,除非你正在对核反应进行计时,否则差异会很小,而且每当我在我的商店看到它时,我都会弃用这种做法。