【问题标题】:Java - strings equals when decompiledJava - 反编译时字符串等于
【发布时间】:2013-04-04 05:23:07
【问题描述】:

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

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

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

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

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

【问题讨论】:

  • 一种方法是像这样编译代码,然后查看反编译器返回的内容:)
  • 它是从什么编译代码反编译而来的?
  • 假设您使用的编译器版本与他们用于创建原始类文件的编译器版本相同。
  • 这看起来像是程序员会(正确或错误地)执行的“优化”,而不是编译器会执行的。
  • 您为什么要这样做? String#equals 已经在使用 == 作为第一个测试。

标签: java string optimization equality string-interning


【解决方案1】:

我非常怀疑。通常,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        

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

【讨论】:

    【解决方案2】:

    不,Java 似乎没有对此进行优化(默认情况下)。

    我刚刚对这两种解决方案进行了基准测试。如果未优化,我们预计s1.equals(s2) 会比s1 == s2 慢。这正是我们所看到的。如果它被优化,那么s1.equals(s2) 将花费与s1==s2 相同的时间。但是,它们需要不同的时间(大约 50,000 纳秒)。这不是对该汇编的直接衡量,但它是一个合理的推论。

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

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

    【讨论】:

    • @RaptorDotCpp 我认为这是战术性的。哦,好吧。
    • JIT 没有优化这一点让我有点惊讶。你有没有用预热、自己的方法等编写一个合理的基准测试?
    • @Voo 老实说,它更快更脏。平均几十万个 System.nanoTime()。但是,无论如何,它是相当准确的。而且,我并不惊讶它没有优化(见我的回答)。
    • @Telthien 我担心你的解释没有意义,因为字符串是不可变的。所以只要我们有s1 = "Foo" 并且以后没有在 this 方法中分配给 s1,编译器就可以确定他知道 char 缓冲区的起始地址。
    • @Voo 查看上面的答案:它没有优化。
    【解决方案3】:

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

    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

    所以,考虑到编译器可以做的优化,首先要考虑如果任何类可以在以后重新编译,编译器会如何表现。

    再举一个例子,你可以看到how enum is implemented,为什么它需要这种“奇怪”的方式。

    【讨论】:

      猜你喜欢
      • 2011-08-08
      • 1970-01-01
      • 1970-01-01
      • 2014-07-29
      • 1970-01-01
      • 2022-11-16
      • 1970-01-01
      • 2011-06-25
      • 2011-11-08
      相关资源
      最近更新 更多