浅谈Java中字符串的初始化及字符串操作类(下)

简介: 大家好,我是本周的值班编辑 江南一点雨 ,本周将由我为大家排版并送出技术干货,大家可以在公众号后台回复“springboot”,获取最新版 Spring Boot2.1.6 视频教程试看。在深入学习字符串类之前, 我们先搞懂JVM是怎样处理新生字符串的.当你知道字符串的初始化细节后, 再去写 Strings="hello"或 Strings=newString("hello")等代码时, 就能做到心中有数。

配合反编译代码验证字符串初始化操作.

相信看到这里, 再见到有关的面试题, 你已经无所畏惧了, 因为你已经懂得了背后原理。

在结束之前我们不妨再做一道压轴题

public class Main {
public static void main(String[] args) {
String s1 = "hello ";
String s2 = "world";
String s3 = s1 + s2;
String s4 = "hello world";
System.out.println(s3 == s4);
}
}

这道压轴题是经过精心设计的, 它不但照应上面所讲的字符串常量池知识, 也引出了后面的话题.

如果看这篇文章是你第一次往底层探索字符串的经历, 那我估计你不能立即给出答案. 因为我第一次见这几行代码时也卡壳了。

首先第一行和第二行是常规的字符串对象声明, 我们已经很熟悉了, 它们分别会在堆内存创建字符串对象, 并会在字符串常量池中进行注册。

影响我们做出判断的是第三行代码 Strings3=s1+s2;, 我们不知道 s1+s2在创建完新字符串"hello world"后是否会在字符串常量池进行注册。

说白了就是我们不知道这行代码是以双引号""形式声明字符串, 还是用new关键字创建字符串。

这时, 我们应该去读一读这段代码的反编译代码. 如果你没有读过反编译代码, 不妨借此机会入门。

在命令行中输入 javap-c对应.class文件的绝对路径, 按回车后即可看到反编译文件的代码段。

class
Compiled from "Main.java"
public class forTest.Main {
public forTest.Main();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #2 // String hello
2: astore_1
3: ldc #3 // String world
5: astore_2
6: new #4 // class java/lang/StringBuilder
9: dup
10: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V
13: aload_1
14: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
17: aload_2
18: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
21: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
24: astore_3
25: ldc #8 // String hello world
27: astore 4
29: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
32: aload_3
33: aload 4
35: if_acmpne 42
38: iconst_1
39: goto 43
42: iconst_0
43: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
46: return
}
  • 首先调用构造器完成Main类的初始化
  • 0:ldc#2 // String hello
  • 从常量池中获取"hello "字符串并推送至栈顶, 此时拿到了"hello "的引用
  • 2:astore_1
  • 将栈顶的字符串引用存入第二个本地变量s1, 也就是s1已经指向了"hello "
  • 3:ldc#3 // String world
  • 5:astore_2
  • 重复开始的步骤, 此时变量s2指向"word"
  • 6:new#4 // class java/lang/StringBuilder
  • 刺激的东西来了: 这时创建了一个StringBuilder, 并把其引用值压到栈顶
  • 9:dup
  • 复制栈顶的值, 并继续压入栈定, 也就意味着栈从上到下有两份StringBuilder的引用, 将来要操作两次StringBuilder.
  • 10:invokespecial#5 // Method java/lang/StringBuilder."<init>":()V
  • 调用StringBuilder的一些初始化方法, 静态方法或父类方法, 完成初始化.
  • 13: aload_1
  • 把第二个本地变量也就是s1压入栈顶, 现在栈顶从上往下数两个数据依次是:s1变量和StringBuilder的引用
  • 14:invokevirtual#6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  • 调用StringBuilder的append方法, 栈顶的两个数据在这里调用方法时就用上了.
  • 接下来又调用了一次append方法(之前StringBuilder的引用拷贝两份就用途在此)
  • 完成后, StringBuilder中已经拼接好了"hello world", 看到这里相信大家已经明白虚拟机是如何拼接字符串的了. 接下来就是关键环节

 

  • 21:invokevirtual#7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
  • 24:astore_3
  • 拼接完字符串后, 虚拟机调用StringBuilder的 toString()方法获得字符串 hello world, 并存放至s3.
  • 激动人心的时刻来了, 我们之所以不知道这道题的答案是因为不知道字符串拼接后是以new的形式还是以双引号""的形式创建字符串对象.
  • 下面是我们追踪StringBuilder的 toString()方法源码:
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}

ok, 这道题解了, s3是通过new关键字获得字符串对象的。

回到题目, 也就是说字符串常量表中没有存储"hello world"的引用, 当s4以引号的形式声明字符串时, 由于在字符串常量池中查不到相应的引用, 所以会在堆内存中新创建一个字符串对象. 所以s3和s4指向的不是同一个字符串对象, 结果为false。

相关文章
|
1天前
|
Java
类信息的“隐形守护者”:JAVA反射技术全揭秘
【7月更文挑战第1天】Java反射技术是动态获取类信息并操作对象的强大工具。它基于Class对象,允许在运行时创建对象、调用方法和改变字段。例如,通过`Class.forName()`动态实例化对象,`getMethod()`调用方法。然而,反射可能破坏封装,影响性能,并需处理异常,故使用时需谨慎。它是Java灵活性的关键,常见于框架设计中。
6 0
|
1天前
|
安全 Java
解决Java中集合类的内存占用问题
解决Java中集合类的内存占用问题
|
1天前
|
Java 容器
Java中使用Optional类的建议
Java中使用Optional类的建议
|
1天前
|
存储 安全 Java
Java详解 : 单列集合 | 双列集合 | Collections类
Java详解 : 单列集合 | 双列集合 | Collections类
|
1天前
|
Java
Java中的Object类 ( 详解toString方法 | equals方法 )
Java中的Object类 ( 详解toString方法 | equals方法 )
|
1天前
|
安全 Java 索引
带你快速掌握Java中的String类和StringBuffer类(详解常用方法 | 区别 )
带你快速掌握Java中的String类和StringBuffer类(详解常用方法 | 区别 )
|
1天前
|
Java 索引
Java中的Arrays类
Java中的Arrays类
|
1天前
|
Java
Java面向对象 ( 类与对象 | 构造方法 | 方法的重载 )
Java面向对象 ( 类与对象 | 构造方法 | 方法的重载 )
|
1天前
|
存储 Java 容器
Java数组的初始化方法
Java数组的初始化方法
|
1天前
|
Java
Calendar类在Java中的高级应用与使用技巧
Calendar类在Java中的高级应用与使用技巧