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
реализовано и зачем ему нужен такой "странный" способ.