繁体   English   中英

Java - 反编译时字符串等于

[英]Java - strings equals when decompiled

我前几天反编译了一些Java代码并发现了这个:

String s1 = "something";
String s2 = "something_else";

if (s1 == s2) {
// Path 1
} else {
// Path 2
}

显然使用'=='来测试字符串相等是不好的

但我想知道 - 这段代码已被编译和反编译。 如果在编译时已经定义了所有字符串并且已经编译并且代码已经编译 - 是否可能s1.equals(s2)已经优化到's1 == s2'?

我非常怀疑它。 通常,Java编译器通过字节码优化做很少的事情,将优化留给JIT阶段。

我已经尝试了一点,我的编译器对以下内容没有做任何有趣的事情:

public class Clazz {

    public static void main(String args[]) {
        final String s1 = "something";
        final String s2 = "something_else";
        if (s1.equals(s2)) {
            System.out.println("yes");
        } else {
            System.out.println("no");
        }
    }

}

这可能是最容易优化的情况。 但是,字节码是:

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #16                 // String something
       2: astore_1      
       3: ldc           #18                 // String something_else
       5: astore_2      
       6: ldc           #16                 // String something
       8: ldc           #18                 // String something_else
      10: invokevirtual #20                 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
      13: ifeq          27
      16: getstatic     #26                 // Field java/lang/System.out:Ljava/io/PrintStream;
      19: ldc           #32                 // String yes
      21: invokevirtual #34                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      24: goto          35
      27: getstatic     #26                 // Field java/lang/System.out:Ljava/io/PrintStream;
      30: ldc           #40                 // String no
      32: invokevirtual #34                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      35: return        

因此,我强烈怀疑==是原始源代码的一部分。

不,它看起来不像Java优化它(默认情况下)。

我只对两种解决方案进行了基准 如果没有优化,我们希望看到s1.equals(s2)s1 == s2慢。 这正是我们所看到的。 如果它被优化,则s1.equals(s2)将花费与s1==s2相同的时间量。 但是,它们需要不同的时间(大约50,000纳秒)。 不是对此编译的直接测量,但它是一个合理的推论。

这不会被优化为==的原因是因为对象的equals运算符将比较对象内存地址,而不是对象本身的内容。 因此,如果您更改s1 ,那么,如果编译器对此进行了优化,那么您也将更改s2

但是,这可能会破坏代码,因此编译器不会这样做。 它会留下s1s2的内存地址。

主要规则是,如果编译器可以从单个类中的源代码中扣除精确值。 因为它只使用最小的编译单元 - 类进行所有优化。 如果我写一个代码

public class Test
{
    private static final String i = "1";
    public static void main(String[] args)
    {
        if(i == "2")
            System.out.println("hello");
        System.out.println("world");
    }
}

编译器会查看与此类中的语句相关的所有代码,并优化if条件。 解编译后代码看起来像

public class Test
{
  private static final String i = "1";

  public static void main(String[] paramArrayOfString)
  {
    System.out.println("world");
  }
}

(我用过jd-gui

但是,如果用.equals替换== ,编译器就不能假设方法.equals如何工作。 因为,在编译Test类之后,你可以破解你的JDK并放置另一个版本的java.lang.String类,它为"1".equals("2")返回true

因此,考虑优化编译器可以做什么,首先要考虑如果以后可以重新编译任何类,编译器可以如何表现。

作为另一个例子,你可以看到如何实现enum ,为什么它需要这种“怪异”的方式。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM