java.lang.String 的 + 号操作到底做了什么事情?

java

前言

 在之前的面试经历中,对于String的考察还是挺频繁的,大致考察以下几个知识点:

  • String 常量池
  • new String()
  • == 和 equals 的区别
  • native 方法 String.intern()
    虽然面试中大体答对了,但是今天早上微信群里的一个问题我却答不上来,这个问题是这样的:

    String str3 = "what";

String str4 = str3 + " a nice day";

//运行时, + 相当于 new,所以堆中会有 "what a nice day"对象,常量池中会有"what"," a nice day"两个对象,而不会有 "what a nice day"对象。

//这句话大佬们看看对不对啊,我怎么感觉不对啊

//常量池不会有"what a nice day" 对象吗?

看完这个问题,说实话我也是有点懵的,我只是知道 "what a nice day"不会在常量池,但是不知道具体的原因,后来群里的同学说 + 号是调用了 StringBuffer 的append 方法。我去证实了,发现确实调用了 append 方法,但是当时没有 调用toString()方法,我很疑惑。(最后经过证实,是StringBuilder的append 方法,不是StringBuffer)。

代码验证

 public static void main(String[] args) {

//#1

String str1 = "what";

//#2

String str2 = str1 + " a nice day";

//#3

System.out.println("what a nice day".equals(str2));

//#4

System.out.println("what a nice day" == str2);

}

现在有以下几个问题,小伙伴们看看是否能答出来,即使答出来了,你知道为什么吗?

  • #1 str1 存放位置?
  • #2 str2 存放位置?
  • #3 结果是 true 还是 false?
  • #4 结果是 true 还是 false?
  • #5 "what a nice day" 存放在哪个位置呢?

解答分析(基于JDK1.8)

下面也不靠猜,我们直接查看生成的字节码:

localhost:test didi$ javap -verbose -p Main.class

Classfile /develop/project/string-test/out/production/classes/com/fanpan26/string/test/Main.class

Last modified 2019-11-29; size 972 bytes

MD5 checksum 1d1f1a23bfe85c2f88d2f767e8aac314

Compiled from "Main.java"

public class com.fanpan26.string.test.Main

minor version: 0

major version: 52

flags: ACC_PUBLIC, ACC_SUPER

Constant pool:

#1 = Methodref #13.#34 // java/lang/Object."<init>":()V

#2 = String #35 // what

#3 = Class #36 // java/lang/StringBuilder

#4 = Methodref #3.#34 // java/lang/StringBuilder."<init>":()V

#5 = Methodref #3.#37 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

#6 = String #38 // a nice day

#7 = Methodref #3.#39 // java/lang/StringBuilder.toString:()Ljava/lang/String;

#8 = Fieldref #40.#41 // java/lang/System.out:Ljava/io/PrintStream;

#9 = String #42 // what a nice day

#10 = Methodref #43.#44 // java/lang/String.equals:(Ljava/lang/Object;)Z

#11 = Methodref #45.#46 // java/io/PrintStream.println:(Z)V

#12 = Class #47 // com/fanpan26/string/test/Main

#13 = Class #48 // java/lang/Object

#14 = Utf8 <init>

#15 = Utf8 ()V

#16 = Utf8 Code

#17 = Utf8 LineNumberTable

#18 = Utf8 LocalVariableTable

#19 = Utf8 this

#20 = Utf8 Lcom/fanpan26/string/test/Main;

#21 = Utf8 main

#22 = Utf8 ([Ljava/lang/String;)V

#23 = Utf8 args

#24 = Utf8 [Ljava/lang/String;

#25 = Utf8 str1

#26 = Utf8 Ljava/lang/String;

#27 = Utf8 str2

#28 = Utf8 StackMapTable

#29 = Class #24 // "[Ljava/lang/String;"

#30 = Class #49 // java/lang/String

#31 = Class #50 // java/io/PrintStream

#32 = Utf8 SourceFile

#33 = Utf8 Main.java

#34 = NameAndType #14:#15 // "<init>":()V

#35 = Utf8 what

#36 = Utf8 java/lang/StringBuilder

#37 = NameAndType #51:#52 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

#38 = Utf8 a nice day

#39 = NameAndType #53:#54 // toString:()Ljava/lang/String;

#40 = Class #55 // java/lang/System

#41 = NameAndType #56:#57 // out:Ljava/io/PrintStream;

#42 = Utf8 what a nice day

#43 = Class #49 // java/lang/String

#44 = NameAndType #58:#59 // equals:(Ljava/lang/Object;)Z

#45 = Class #50 // java/io/PrintStream

#46 = NameAndType #60:#61 // println:(Z)V

#47 = Utf8 com/fanpan26/string/test/Main

#48 = Utf8 java/lang/Object

#49 = Utf8 java/lang/String

#50 = Utf8 java/io/PrintStream

#51 = Utf8 append

#52 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;

#53 = Utf8 toString

#54 = Utf8 ()Ljava/lang/String;

#55 = Utf8 java/lang/System

#56 = Utf8 out

#57 = Utf8 Ljava/io/PrintStream;

#58 = Utf8 equals

#59 = Utf8 (Ljava/lang/Object;)Z

#60 = Utf8 println

#61 = Utf8 (Z)V

{

public com.fanpan26.string.test.Main();

descriptor: ()V

flags: ACC_PUBLIC

Code:

stack=1, locals=1, args_size=1

0: aload_0

1: invokespecial #1 // Method java/lang/Object."<init>":()V

4: return

LineNumberTable:

line 6: 0

LocalVariableTable:

Start Length Slot Name Signature

0 5 0 this Lcom/fanpan26/string/test/Main;

public static void main(java.lang.String[]);

descriptor: ([Ljava/lang/String;)V

flags: ACC_PUBLIC, ACC_STATIC

Code:

stack=3, locals=3, args_size=1

0: ldc #2 // String what

2: astore_1

3: new #3 // class java/lang/StringBuilder

6: dup

7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V

10: aload_1

11: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

14: ldc #6 // String a nice day

16: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

19: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;

22: astore_2

23: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;

26: ldc #9 // String what a nice day

28: aload_2

29: invokevirtual #10 // Method java/lang/String.equals:(Ljava/lang/Object;)Z

32: invokevirtual #11 // Method java/io/PrintStream.println:(Z)V

35: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;

38: ldc #9 // String what a nice day

40: aload_2

41: if_acmpne 48

44: iconst_1

45: goto 49

48: iconst_0

49: invokevirtual #11 // Method java/io/PrintStream.println:(Z)V

52: return

LineNumberTable:

line 9: 0

line 11: 3

line 13: 23

line 15: 35

line 16: 52

LocalVariableTable:

Start Length Slot Name Signature

0 53 0 args [Ljava/lang/String;

3 50 1 str1 Ljava/lang/String;

23 30 2 str2 Ljava/lang/String;

StackMapTable: number_of_entries = 2

frame_type = 255 /* full_frame */

offset_delta = 48

locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String ]

stack = [ class java/io/PrintStream ]

frame_type = 255 /* full_frame */

offset_delta = 0

locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String ]

stack = [ class java/io/PrintStream, int ]

}

SourceFile: "Main.java"

从 Constant pool: 中的信息可以看到,#2 #6 #9 可以解答上文中的#1,#5两个问题。

  • str1 是存放在常量池的
  • "what a nice day" (非str2)也是存放在常量池的.

下面我们看一下 + 操作做了什么事情,可以在Code中看到,该操作调用了 StringBuilder.append 方法

        11: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

14: ldc #6 // String a nice day

16: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

19: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;

那么到这里一切都答案都出来了

  • str2 是存放在堆中。
  • equals 为 true
  • == 为 false

所以说其实 str1 + " a nice day" 就相当于 new StringBuilder().append(str1).append(" a nice day");

        //这两种写法生成的字节码是一样的。

//String str2 = str1 + " a nice day";

String str2 = new StringBuilder().append(str1).append(" a nice day").toString();

而StringBuilder 的toString 方法如下:

  @Override

public String toString() {

// 所以说 str2 其实是一个 new String,是不在常量池里面的。

return new String(value, 0, count);

}

总结

通过类的字节码可以查看底层具体用什么方式实现,所以说虽然看似一个简单的String问题,其实往深处挖掘还是考察了对生成的字节码的理解。还有,遇到一个问题,不能死记答案,有些人告诉你,+ 操作就是 new 对象,但是具体到底是不是或者为什么是有没有思考过呢?上文中如有错误,欢迎指出。

试一试

/**

* 以下程序输出的结果是什么?

* */

public static void main(String[] args) {

String str1 = "what";

String str2 = str1 + " a nice day";

System.out.println("what a nice day".equals(str2));

System.out.println("what a nice day" == str2);

}

/**

* 以下程序输出的结果是什么?

* */

public static void main(String[] args) {

String str1 = "what a nice day";

String str2 = new String("what a nice day");

System.out.println(str1.equals(str2));

System.out.println(str1 == str2);

}

/**

* 以下程序输出的结果是什么?

* */

public static void main(String[] args) {

String str1 = "what";

String str2 = str1.concat(" a nice day");

System.out.println("what a nice day".equals(str2));

System.out.println("what a nice day" == str2);

System.out.println("what a nice day"==str2.intern());

}

 

以上是 java.lang.String 的 + 号操作到底做了什么事情? 的全部内容, 来源链接: utcz.com/z/394588.html

回到顶部