一个简单的字符串比较,以下四种写法有什么区别呢?
public class TestClass { public void test() { "str1".equals("str2"); } public static void main(String[] args) { new TestClass().test(); } }
public class TestClass { public void test() { String str = "str1"; str.equals("str2"); } public static void main(String[] args) { new TestClass().test(); } }
public class Constans { public static final String str1 = "str1"; } public class TestClass { public void test() { String str = Constans.str1; str.equals("str2"); } public static void main(String[] args) { new TestClass().test(); } }
public class TestClass { public void test() { String str = "str1"; String st2 = "str2"; str.equals(st2); } public static void main(String[] args) { new TestClass().test(); } }
以上四种写法基本上一样,但如果我们非要抠一下区别呢,我们通过 javap -c -l TestClass.class
来看一下它们的区别。
Compiled from "TestClass.java" public class com.example.demo.compile.TestClass { public com.example.demo.compile.TestClass(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 8: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/example/demo/compile/TestClass; public void test(); Code: 0: ldc #2 // String str1 2: ldc #3 // String str2 4: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 7: pop 8: return LineNumberTable: line 11: 0 line 12: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 this Lcom/example/demo/compile/TestClass; public static void main(java.lang.String[]); Code: 0: new #5 // class com/example/demo/compile/TestClass 3: dup 4: invokespecial #6 // Method "<init>":()V 7: invokevirtual #7 // Method test:()V 10: return LineNumberTable: line 15: 0 line 16: 10 LocalVariableTable: Start Length Slot Name Signature 0 11 0 args [Ljava/lang/String; }
重点关注 test()
下的 Code
那一段。
Compiled from "TestClass.java" public class com.example.demo.compile.TestClass { public com.example.demo.compile.TestClass(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 8: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/example/demo/compile/TestClass; public void test(); Code: 0: ldc #2 // String str1 将常量“str1”压入栈 2: astore_1 // 将栈中的“str1”pop出,赋值给str 3: aload_1 // 将str的引用值压入栈 4: ldc #3 // String str2 6: invokevirtual #4 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 9: pop 10: return LineNumberTable: line 11: 0 line 12: 3 line 13: 10 LocalVariableTable: Start Length Slot Name Signature 0 11 0 this Lcom/example/demo/compile/TestClass; 3 8 1 str Ljava/lang/String; public static void main(java.lang.String[]); Code: 0: new #5 // class com/example/demo/compile/TestClass 3: dup 4: invokespecial #6 // Method "<init>":()V 7: invokevirtual #7 // Method test:()V 10: return LineNumberTable: line 16: 0 line 17: 10 LocalVariableTable: Start Length Slot Name Signature 0 11 0 args [Ljava/lang/String; }
可以看到,在 test
方法里, 写法二
比 写法一
多了对 str
局部变量的操作。
关于方法里的几个命令的作用,见下图:
图片来源:《 大话+图说:Java字节码指令——只为让你懂 》
Compiled from "TestClass.java" public class com.example.demo.compile.TestClass { public com.example.demo.compile.TestClass(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 8: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/example/demo/compile/TestClass; public void test(); Code: 0: ldc #3 // String str1 2: astore_1 3: aload_1 4: ldc #4 // String str2 6: invokevirtual #5 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 9: pop 10: return LineNumberTable: line 11: 0 line 12: 3 line 13: 10 LocalVariableTable: Start Length Slot Name Signature 0 11 0 this Lcom/example/demo/compile/TestClass; 3 8 1 str Ljava/lang/String; public static void main(java.lang.String[]); Code: 0: new #6 // class com/example/demo/compile/TestClass 3: dup 4: invokespecial #7 // Method "<init>":()V 7: invokevirtual #8 // Method test:()V 10: return LineNumberTable: line 16: 0 line 17: 10 LocalVariableTable: Start Length Slot Name Signature 0 11 0 args [Ljava/lang/String; }
写法二
和 写法三
完全一样,这是为什么呢?
我们用反编译工具打开 写法三
的 class
文件。
从 class
文件反编译后了内容上看, 写法三
的 String str = Constans.str1
在编译后会被转成 String str = "str1"
,转之后跟 写法二
一样了。
java在编译的时候,会做一些性能上的优化,比如:为了减少运行时的栈调用,将 var = 常量字段
直接编译成 var = 常量字段的值
。
Compiled from "TestClass.java" public class com.example.demo.compile.TestClass { public com.example.demo.compile.TestClass(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 8: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/example/demo/compile/TestClass; public void test(); Code: 0: ldc #3 // String str1 2: astore_1 3: ldc #4 // String str2 5: astore_2 6: aload_1 7: aload_2 8: invokevirtual #5 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 11: pop 12: return LineNumberTable: line 11: 0 line 12: 3 line 13: 6 line 14: 12 LocalVariableTable: Start Length Slot Name Signature 0 13 0 this Lcom/example/demo/compile/TestClass; 3 10 1 str Ljava/lang/String; 6 7 2 st2 Ljava/lang/String; public static void main(java.lang.String[]); Code: 0: new #6 // class com/example/demo/compile/TestClass 3: dup 4: invokespecial #7 // Method "<init>":()V 7: invokevirtual #8 // Method test:()V 10: return LineNumberTable: line 17: 0 line 18: 10 LocalVariableTable: Start Length Slot Name Signature 0 11 0 args [Ljava/lang/String; }
写法四
是在 写法二
基础上,又多了对 str2
局部变量的操作。