Java:if-return-if-return与if-return-elseif-return

31

我问了一个无关的问题,其中有这样的代码:

public boolean equals(Object obj)
{
    if (this == obj)
        return true;

    if (obj == null)
        return false;

    if (getClass() != obj.getClass())
        return false;

    // Check property values
}

我收到了一条评论,声称这不是最优解,而且应该这样做(如果我理解正确):

public boolean equals(Object obj)
{
    if (this == obj)
        return true;

    else if (obj == null)
        return false;

    else if (getClass() != obj.getClass())
        return false;

    // Check property values
}

由于有return语句,我真的看不出为什么其中任何一种方法应该比其他方法更有效或更快。根据我所见,对于某个对象,两种方法都必须进行相同数量的检查。而且由于存在return语句,任何一种情况下都不会运行额外的代码。

我是否漏了什么?这里有什么问题吗?是否存在某些编译器优化或其他操作?

我知道这是微小的优化,无论如何我最终可能都会坚持使用第一种方式,因为我认为在同一位置放置所有if语句看起来更清晰。但我忍不住好奇!


14
告诉我那个发表评论的人的名字,我会给他的评论点“踩” ;) - Andreas Dolk
3
我建议不要太关注这样微小的优化。你总是可以找到一些纠结于细节的人。这些优化都会被编译器处理得很好,很有可能第一个版本已经被缩减为第二个版本(或反之,这取决于你的编译器认为哪个更好)。 - d-live
1
@Jigar Joshi - 用虚拟的 (+1) 代替举手 :-) - Andreas Dolk
我想的是这个 完全超出了正常范围,@Andreas_D @Swish 谢谢 :) - jmj
“Tautology” 指用不同的词汇重复表达相同的内容,通常被认为是文体上的缺陷。 - MaxZoom
显示剩余2条评论
5个回答

25

对于这两种情况,生成的字节码是相同的,所以这只是一种风格上的区别。

我编写了两个方法e1e2,它们都生成了下面这段字节码(使用javap -v命令进行查看):

public boolean e1(java.lang.Object);
  Code:
   Stack=2, Locals=2, Args_size=2
   0:   aload_0
   1:   aload_1
   2:   if_acmpne   7
   5:   iconst_1
   6:   ireturn
   7:   aload_1
   8:   ifnonnull   13
   11:  iconst_0
   12:  ireturn
   13:  aload_0
   14:  invokevirtual   #25; //Method java/lang/Object.getClass:()Ljava/lang/Class;
   17:  aload_1
   18:  invokevirtual   #25; //Method java/lang/Object.getClass:()Ljava/lang/Class;
   21:  if_acmpeq   26
   24:  iconst_0
   25:  ireturn

我省略了在这段代码之后添加的内容,以便让其可以编译通过。


3
@Jigar:这里不需要智能,编译器没有其他选择,只能为所有情况生成相同的代码。在字节码中没有else语句,只有一堆if-condition-goto语句。 - Paŭlo Ebermann
1
唯一需要的智能是编译器需要知道在 ireturn 之后它不需要放置一个 jump-to-the-end-of-the-if-else-cascade 语句。这个检查相当简单。 - Joachim Sauer
3
“有趣,编译器非常聪明。” 你这么认为吗?Jigar,我告诉你一个新闻。自从第三代编程语言被发明以来(甚至是人们开始在汇编中使用宏指令之前),所有编译器都一直在做这件事情。欢迎来到大约60年前发明的编译器技术。你甚至不需要了解编译器技术。简单的逻辑决定这两组if(和if-else)语句是等价的。 - luis.espinal
4
你说的话实际上是事实,但我不明白你使用这种语气的原因。并非所有人都学过编译技术。 - Joachim Sauer
@Paŭlo Ebermann,@Joachim Sauer 我的想法是这个。完全不合适,@Andreas_D @Swish 谢谢 :) ,有意删除了评论。 - jmj
显示剩余4条评论

11

两种方法的效率并没有区别。编译器可以轻松地看出这两个方法是相同的,实际上,Suns/Oracles javac 为这两种方法生成了相同的字节码

这里是一个名为IfTest的类:

class IfTest {

    public boolean eq1(Object obj) {
        if (this == obj)
            return true;

        if (obj == null)
            return false;

        if (getClass() != obj.getClass())
            return false;

        return true;
    }


    public boolean eq2(Object obj) {

        if (this == obj)
            return true;

        else if (obj == null)
            return false;

        else if (getClass() != obj.getClass())
            return false;

        return true;
    }
}

我使用javac进行了编译,反汇编结果如下:

public boolean eq1(java.lang.Object);
  Code:
   0:   aload_0
   1:   aload_1
   2:   if_acmpne   7
   5:   iconst_1
   6:   ireturn
   7:   aload_1
   8:   ifnonnull   13
   11:  iconst_0
   12:  ireturn
   13:  aload_0
   14:  invokevirtual   #2; //Method Object.getClass:()Ljava/lang/Class;
   17:  aload_1
   18:  invokevirtual   #2; //Method Object.getClass:()Ljava/lang/Class;
   21:  if_acmpeq   26
   24:  iconst_0
   25:  ireturn
   26:  iconst_1
   27:  ireturn

 

public boolean eq2(java.lang.Object);
  Code:
   0:   aload_0
   1:   aload_1
   2:   if_acmpne   7
   5:   iconst_1
   6:   ireturn
   7:   aload_1
   8:   ifnonnull   13
   11:  iconst_0
   12:  ireturn
   13:  aload_0
   14:  invokevirtual   #2; //Method Object.getClass:()Ljava/lang/Class;
   17:  aload_1
   18:  invokevirtual   #2; //Method Object.getClass:()Ljava/lang/Class;
   21:  if_acmpeq   26
   24:  iconst_0
   25:  ireturn
   26:  iconst_1
   27:  ireturn

换句话说,我建议使用第一种版本(没有else)。有些人可能会认为带有else部分更加简洁,但是我认为相反。包括else表示程序员没有意识到它是不必要的。


+1 对于这个分析,我已经在问题的评论中指出了。如果您也尝试单返回版本,我相信结果会完全相同 - 编译器不会费心创建变量并执行内存操作,因为它知道所有这些都是为了返回结果而存在的。 - d-live
有趣的是,你的编译器长度为27,而@Joachim Sauer的编译器长度为25。你使用了哪个JDK? - rajah9
我同意,包括else语句表明程序员没有意识到它是不必要的。 - Christopher Perry

5

我不认为有任何实际理由用另一个实现来替换其中一个 - 无论是哪个方向。

如果你想在一个方法中避免多个返回语句 - 有些人喜欢这种编码方式,那么第二个例子就有意义了。 然后 我们需要if-else if结构:

public boolean equals(Object obj)
{
    boolean result = true;

    if (this == obj)
        result = true;

    else if (obj == null)
        result = false;

    else if (getClass() != obj.getClass())
        result = false;

    return result;
}

3

这样想。当执行返回语句时,控制权离开方法,因此else实际上没有添加任何价值,除非你认为它增加了可读性(我不认为它会,但其他人可能会有不同的看法)。

所以当你有:

if (someCondition)
    return 42;

if (anotherCondition)
    return 43;

在第二个if语句中添加else实际上并没有什么价值。

事实上,我在编写C#代码时使用一个名为Resharper的工具,它会将这些情况下的else标记为无用代码。因此,我认为通常最好将它们省略掉。正如Joachim已经提到的,编译器会对它们进行优化处理并去除它们。


2
我完全同意Resharper的观点... 当我看到这样的else语句时,我倾向于将它们删除。同样地,当我看到if (cond) return true; else return false; 这样的代码时,我感到不舒服。 - PhiLho

0

我认为这段代码可以稍微改进一下(注意,它非常易读):

  if (obj == null)
        return false;

  if (getClass() != obj.getClass())
        return false;

instanceof 运算符相当于这两者的组合,可能更快 - 代码更少,没有方法调用:

  if (!(obj instanceof MyClass))
        return false;

但是我又能知道什么呢……我太懒了,从来没有分析过字节码。:-p


1
你应该意识到getClass()instanceof是*不等价的。如果你有两个类AB,其中B扩展自A,那么getClass()总是false,而b instanceof A则为true - beatngu13

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接