Java-строки равны при декомпиляции

Я декомпилировал некоторый Java-код на днях и нашел это:

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

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

Очевидно, использование '==' для проверки равенства строк плохое

Но я подумал: этот код был скомпилирован и декомпилирован. Если все строки были определены во время компиляции и интернированы, а код был скомпилирован - возможно ли, что s1.equals(s2) можно было бы оптимизировать до 's1 == s2'?

Ответ 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 наносекунд). Это не является прямым измерением этой компиляции, но это разумный вывод.

Причина, по которой это не будет оптимизирована для ==, заключается в том, что оператор equals для объектов сравнивает адрес памяти объекта, а не содержимое самого объекта. Итак, если вы измените s1, то, если компилятор оптимизирует это, вы также будете менять s2.

Однако, это рискует сломать код, поэтому компилятор этого не сделает. Он оставит адреса памяти s1 и s2 be.

Ответ 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, которая возвращает true для "1".equals("2").

Итак, думая о оптимизации, которую мог бы сделать компилятор, прежде всего подумайте, как мог бы работать компилятор, если позже можно перекомпилировать какой-либо класс.

В качестве другого примера вы можете увидеть как enum реализовано и зачем ему нужен такой "странный" способ.