一个简单的字符串比较,以下四种写法有什么区别呢?

写法一:

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局部变量的操作。
关于方法里的几个命令的作用,见下图:
1622532775-58becf6a14a1b_articlex.png
图片来源:《大话+图说: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文件。
78664155.png
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>    覆盖引导类文件的位置

noname
317 声望50 粉丝

一只菜狗