一个简单的字符串比较,以下四种写法有什么区别呢?
写法一:
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,"_"后面的"1"是槽点(slot),对应下面"LocalVariableTable"里的"Slot"
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
局部变量的操作。
注:javap
如果加上-v
可以看更详细的汇编信息,不仅会输出行号、本地变量表信息、反编译汇编代码,还会输出当前类用到的常量池等信息。
输入javap -help
可以看到以下参数,比较常用的就是-v -c -l
。
用法: javap <options> <classes>
其中, 可能的选项包括:
-help --help -? 输出此用法消息
-version 版本信息
-v -verbose 输出附加信息
-l 输出行号和本地变量表
-public 仅显示公共类和成员
-protected 显示受保护的/公共类和成员
-package 显示程序包/受保护的/公共类
和成员 (默认)
-p -private 显示所有类和成员
-c 对代码进行反汇编
-s 输出内部类型签名
-sysinfo 显示正在处理的类的
系统信息 (路径, 大小, 日期, MD5 散列)
-constants 显示最终常量
-classpath <path> 指定查找用户类文件的位置
-cp <path> 指定查找用户类文件的位置
-bootclasspath <path> 覆盖引导类文件的位置
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。