В IntellijIDEA есть замечательная фича - показать байткод, позволяющая посмотреть как и в итоге JRE будет выполнять написанный код.(впрочем это также некоторая интерпретация компилятором машинного байткода для удобства чтения)
Находится в View->Show Byte Code
Что делает - анализирует скомпилированные .class - файлы. Описание команд можно найти по ссылке википедии.
Соответственно появилась идея разобрать байткоды для двух вариантов main-метода с инициализацией одинаковых строк, и попытаться прояснить почему они дают разные результаты: Вариант 1:(если сравнить str1==str2, то получаем false)
Ссылки на почитать ещё: src1: http://cs.au.dk/~mis/dOvs/jvmspec/ref--33.html src2: http://mihaimoldovan.com/download/Inside-Java-Virtual-Machine.pdf src3: http://en.wikipedia.org/wiki/Java_class_file#The_constant_pool src4: http://en.wikipedia.org/wiki/Java_bytecode_instruction_listings src5: http://stackoverflow.com/questions/10209952/java-constant-pool
src6: http://stackoverflow.com/questions/14150628/string-constant-pool-java Так почему же в одном случае строки равны, а в другом нет? Потому что в случае new String - игнорируется string constant pool, и создается именно новая копия строки(ссылка src6 про это). И хоть байткод не очень помог прояснить это, очевидно что во втором случае он совершает меньше операций, а потому этот вариант лучше.
Соответственно появилась идея разобрать байткоды для двух вариантов main-метода с инициализацией одинаковых строк, и попытаться прояснить почему они дают разные результаты: Вариант 1:(если сравнить str1==str2, то получаем false)
public static void main(String[] args){
String str1 = new String("test");
String str2 = new String("test");
}
Вариант 2:(если сравнить str3==str4, то получаем true)
public static void main(String[] args){
String str3 = "test";
String str4 = "test";
}
Байткод первого варианта(в квадратных скобках[] будет состояние стека после выполнения команды(вершина стека справа)):
public static main([Ljava/lang/String;)V
L0
LINENUMBER 5 L0
NEW java/lang/String
//новая строка - кладем ссылку на класс "String "на вершину стека: [value_string]
DUP
//копируем ссылку на вершине стека: [value_string, value_string]
LDC "test"
//кладём в стек ссылку на "test" из constant pool(а если в нём еще нет ссылки на "test",
//то она также остается в constant pool'e): [value_string, value_string, value_test]
INVOKESPECIAL java/lang/String. (Ljava/lang/String;)V
//вызов new для String с аргументами с вершины стека -
//после вызова инициализирующего метода для стринг(и его внутренних манипуляций со стеком,
//которые аналогично можно посмотреть в байткоде java.lang.String )
//- в стеке будет ссылка на проинициализированную строку: [value_result_string]
//Почему для инициализации String(String) нужно три аргумента в стеке:
//1)ссылка на входной аргумент 2)новая строка, 3)хешкод строки
ASTORE 1
//сохраняем ссылку с вершины стека в локальную переменную(1) с вершины стека: [ ]
L1
LINENUMBER 6 L1
NEW java/lang/String //аналогично первой строке
DUP
LDC "test"
INVOKESPECIAL java/lang/String. (Ljava/lang/String;)V
ASTORE 2
L2
LINENUMBER 7 L2
RETURN //return void - компилятор дописывает за нас если метод возвращает void
L3
LOCALVARIABLE args [Ljava/lang/String; L0 L3 0 // список локальных переменных метода
LOCALVARIABLE str3 Ljava/lang/String; L1 L3 1 // L1(метка где операции с локальной переменной) L3(метка где описание что это за локальная переменная) 1(номер локальной переменной)
LOCALVARIABLE str4 Ljava/lang/String; L2 L3 2 //
MAXSTACK = 3 //максимальная глубина стека метода
MAXLOCALS = 3 //количество локальных переменных метода
//
//То есть сначала JRE будет выделять память при
//вызове метода учитывая всю вложенность вызовов методов и new - инициализаций
//(и если её не хватит - то всё вылетит с каким-нибудь OutOfMemory
//эксепшном), а только потом будут производиться операции
Байткод второго варианта:
public static main([Ljava/lang/String;)V
L0
LINENUMBER 5 L0
LDC "test" //берем ссылку на "test" из constant_pool и кладем на вершину стека: [value_test]
ASTORE 1 //забираем ссылку с вершины стека в переменную(1) str1 : [ ]
L1
LINENUMBER 6 L1
LDC "test" //берем ссылку на "test" из constant_pool и кладем на вершину стека: [value_test]
ASTORE 2 //забираем ссылку с вершины стека в переменную(2) str2: [ ]
L2
LINENUMBER 7 L2
RETURN //return void (который компилятор дописывает за нас как успешный конец метода)
L3
LOCALVARIABLE args [Ljava/lang/String; L0 L3 0 //список локальных переменных, под которые выделяется память
LOCALVARIABLE str1 Ljava/lang/String; L1 L3 1 //например: L1(метка где записан код) L3(метка где описан тип объекта и выделяется память) 1(локальный номер объекта)
LOCALVARIABLE str2 Ljava/lang/String; L2 L3 2 //
MAXSTACK = 1 //максимальная глубина стека для метода
MAXLOCALS = 3 //количество локальных переменных метода
Ссылки на почитать ещё: src1: http://cs.au.dk/~mis/dOvs/jvmspec/ref--33.html src2: http://mihaimoldovan.com/download/Inside-Java-Virtual-Machine.pdf src3: http://en.wikipedia.org/wiki/Java_class_file#The_constant_pool src4: http://en.wikipedia.org/wiki/Java_bytecode_instruction_listings src5: http://stackoverflow.com/questions/10209952/java-constant-pool
src6: http://stackoverflow.com/questions/14150628/string-constant-pool-java Так почему же в одном случае строки равны, а в другом нет? Потому что в случае new String - игнорируется string constant pool, и создается именно новая копия строки(ссылка src6 про это). И хоть байткод не очень помог прояснить это, очевидно что во втором случае он совершает меньше операций, а потому этот вариант лучше.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ