读深入理解Java中的String(包括JVM)一文总结和提升
Posted kyaoyyw
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了读深入理解Java中的String(包括JVM)一文总结和提升相关的知识,希望对你有一定的参考价值。
读深入理解Java中的String(包括JVM)一文总结和提升
摘要:String作为Java语言中的字符串模拟类,无论是实际的编程工作还是笔试面试过程,都需要我们都String类非常熟悉,对于String类的大部分字符串操作方法,都必须达到熟练运用的程度才行。但是,笔试和面试过程中,面试官往往喜欢问一些String特性相关的题目,来考察面试者对于String基础知识的掌握是否牢固。(本人尚未研读深入理解JVM这本书,分析JVM都是查看网上资料来分析的,若在接下来的内容有分析不到位的地方请见谅和提出相应的意见和答案。2019-3-16 大二本科学生记)
参考各文章链接:
https://www.cnblogs.com/xiaoxi/p/6036701.html(String)
http://www.cubrid.org/blog/dev-platform/understanding-jvm-internals(JVM)
https://www.cnblogs.com/dingyingsi/p/3760447.html(JVM内存模型)
源码面前,了无秘密(源码分析)
1 public final class String 2 implements java.io.Serializable, Comparable<String>, CharSequence { 3 /** The value is used for character storage. */ 4 private final char value[]; 5 6 /** Cache the hash code for the string */ 7 private int hash; // Default to 0 8 9 /** use serialVersionUID from JDK 1.0.2 for interoperability */ 10 private static final long serialVersionUID = -6849794470754667710L; 11 12 /** 13 * Class String is special cased within the Serialization Stream Protocol. 14 * 15 * A String instance is written into an ObjectOutputStream according to 16 * <a href="{@docRoot}/../platform/serialization/spec/output.html"> 17 * Object Serialization Specification, Section 6.2, "Stream Elements"</a> 18 */ 19 private static final ObjectStreamField[] serialPersistentFields = 20 new ObjectStreamField[0]; 21 }
总结
1. String类的底层使用 char 的数组保存数据。
2. String类是一个 final 类,不允许被继承。
3. 在Java中,被final修饰的类是不允许被继承的,并且该类中的成员方法都默认为final方法。
1 public String substring(int beginIndex) { 2 if (beginIndex < 0) { 3 throw new StringIndexOutOfBoundsException(beginIndex); 4 } 5 int subLen = value.length - beginIndex; 6 if (subLen < 0) { 7 throw new StringIndexOutOfBoundsException(subLen); 8 } 9 return (beginIndex == 0) ? this : new String(value, beginIndex, subLen); 10 } 11 public static String copyValueOf(char data[]) { 12 return new String(data); 13 } 14 public String replace(char oldChar, char newChar) { 15 if (oldChar != newChar) { 16 int len = value.length; 17 int i = -1; 18 char[] val = value; /* avoid getfield opcode */ 19 20 while (++i < len) { 21 if (val[i] == oldChar) { 22 break; 23 } 24 } 25 if (i < len) { 26 char buf[] = new char[len]; 27 for (int j = 0; j < i; j++) { 28 buf[j] = val[j]; 29 } 30 while (i < len) { 31 char c = val[i]; 32 buf[i] = (c == oldChar) ? newChar : c; 33 i++; 34 } 35 return new String(buf, true); 36 } 37 } 38 return this; 39 } 40 public String concat(String str) { 41 int otherLen = str.length(); 42 if (otherLen == 0) { 43 return this; 44 } 45 int len = value.length; 46 char buf[] = Arrays.copyOf(value, len + otherLen); 47 str.getChars(buf, len); 48 return new String(buf, true); 49 }
总结
1.String类是一个 immutable 类,该类的对象生成后,内容不会发生变化。该类中的所有返回String类型对象的成员方法都是返回一个新的String对象。
2.String对象一旦被创建就是固定不变的了,对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象。
JVM内存模型
Java作为一门半编译半解释或者即编译又解释的编程语言,Java源码文件需要先被编译器编译成 ByteCode(字节码) 文件,然后在 JVM(Java虚拟机) 上解释执行。为了理解和掌握String类的特性,必须清楚地知道JVM的内存模型。对于字符串类型,也就是String类,JVM从编译源码到执行字节码的整个过程中,都做了特定的调整与优化,正是这些调整与优化造成了String类与对象的一些诡异特性。
(1)虚拟机:JRE由Java API和JVM组成,JVM通过类加载器(Class Loader)加类Java应用,并通过Java API进行执行。虚拟机是通过软件模拟物理机器执行程序的执行器。最初Java语言被设计为基于虚拟机器在而非物理机器,重而实现WORA(一次编写,到处运行)的目的,尽管这个目标几乎被世人所遗忘。所以,JVM可以在所有的硬件环境上执行Java字节码而无须调整Java的执行模式。
(2)JVM的基本特性:
基于栈(Stack-based)的虚拟机: 不同于Intel x86和ARM等比较流行的计算机处理器都是基于寄存器(register)架构,JVM是基于栈执行的。
符号引用(Symbolic reference): 除基本类型外的所有Java类型(类和接口)都是通过符号引用取得关联的,而非显式的基于内存地址的引用。
垃圾回收机制: 类的实例通过用户代码进行显式创建,但却通过垃圾回收机制自动销毁。
通过明确清晰基本类型确保平台无关性: 像C/C++等传统编程语言对于int类型数据在同平台上会有不同的字节长度。JVM却通过明确的定义基本类型的字节长度来维持代码的平台兼容性,从而做到平台无关。
网络字节序(Network byte order)
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
图一
图二
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
总结
1.图一验证虚拟机是通过软件模拟物理机器执行程序的执行器,JVM可以在所有的硬件环境上执行Java字节码而无须调整Java的执行模式。
2. 图二验证JVM是基于栈执行的;除基本类型外的所有Java类型(类和接口)都是通过符号引用取得关联的,而非显式的基于内存地址的引用;JVM通过明确的定义基本类型的字节长度来维持代码的平台兼容性,从而做到平台无关。
验证JVM运行机制(反编译)
1 public class Demo { 2 public static void main(String... strings) { 3 int i = 0; 4 short s = 0; 5 long l = 0; 6 float f = 0.0f; 7 double d = 0.0d; 8 String s0 = "s0"; 9 String s1 = new String("s1"); 10 } 11 }
(1)javac命令用来编译java文件;java命令可以执行生成的class文件。
(2)为了管理这些class 文件, JVM提供了javap命令来对二进制文件进行反编译。执行javap得到的是直观的java指令序列。
(3)使用"javap -verbose"命令分析.class文件。
(4)对程序代码执行javap -c可得到应用中指令序列。
(5)类加载过程
- 加载(Loading): 从文件中获取类并载入到JVM内存空间。
- 验证(Verifying): 验证载入的类是否符合Java语言规范和JVM规范。在类加载流程的测试过程中,这一步是最为复杂且耗时最长的部分。大部分JVM TCK的测试用例都用于检测对于给定的错误的类文件是否能得到相应的验证错误信息。
- 准备(Preparing): 根据内存需求准备相应的数据结构,并分别描述出类中定义的字段、方法以及实现的接口信息。
- 解析(Resolving): 把类常量池中所有的符号引用转为直接引用。
- 初始化(Initializing): 为类的变量初始化合适的值。执行静态初始化域,并为静态字段初始化相应的值。
(6)JVM执行生成的class文件
总结(这是个人猜测如果有不对请指正)
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
编译java文件过程
1.Constant Pool静态常量池包含字符串字面量(如s0;s1),还包含类(如java/lang/String;java/lang/Object)、方法的信息(如java/lang/Object."<init>":()V;java/lang/String."<init>":(Ljava/lang/String;)V),占用class文件绝大部分空间。
2.Code:stack=3, locals=10, args_size=1;Java 虚拟机栈用于存储局部变量表、操作栈、动态链接、方法出口等信息;局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型,它不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress 类型(指向了一条字节码指令的地址)。
0: iconst_0
1: istore_1
2: iconst_0
3: istore_2
4: lconst_0
5: lstore_3
6: fconst_0
7: fstore 5
9: dconst_0
10: dstore 6
验证了局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)。
12: ldc #2 // String s0
14: astore 8
16: new #3 // class java/lang/String
19: dup
20: ldc #4 // String s1
22: invokespecial #5 // Method java/lang/String."<init>":(Ljava/lang/String;)V
25: astore 9
验证了对象引用(reference 类型,它不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress 类型(指向了一条字节码指令的地址)。
局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
编译器子系统通过javac命令将.java文件进行编译形成了三大内存模型:Java栈(局部变量表);Java堆(句柄池存放着对象引用的指针);方法区(静态常量池)的.class文件。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------(这是个人猜测如果有不对请指正)
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
JVM执行生成的class文件(类加载)
3.类加载过程
- 加载(Loading): 从文件中获取类并载入到JVM内存空间。
- 验证(Verifying): 验证载入的类是否符合Java语言规范和JVM规范。在类加载流程的测试过程中,这一步是最为复杂且耗时最长的部分。大部分JVM TCK的测试用例都用于检测对于给定的错误的类文件是否能得到相应的验证错误信息。
- 准备(Preparing): 根据内存需求准备相应的数据结构,并分别描述出类中定义的字段、方法以及实现的接口信息。
- 解析(Resolving): 把类常量池中所有的符号引用转为直接引用。
- 初始化(Initializing): 为类的变量初始化合适的值。执行静态初始化域,并为静态字段初始化相应的值。---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
(这是个人猜测如果有不对请指正)
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
面试题例子
(1)采用字面值的方式赋值
1 public class Demo { 2 public static void main(String... strings) { 3 String s0="ccc"; 4 String s1="ccc"; 5 System.out.println("======test1========"); 6 System.out.println(s0==s1); 7 } 8 } 9 /*~ 10 * output 11 * ======test1======== 12 true 13 s0和s1指向方法区的运行常量池的常量ccc 14 */
使用javap命令验证思路:
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Result:Constant pool 静态常量池只有ccc对象类型;Java栈中有两个引用对象并指向句柄池。
当执行String s0="ccc"时,JVM首先会去字符串池中查找是否存在"ccc"这个对象,如果不存在,则在字符串池中创建"ccc"这个对象,然后将池中"ccc"这个对象的引用地址返回给字符串常量s0,这样str1会指向池中"ccc"这个字符串对象;如果存在,则不创建任何对象,直接将池中"ccc"这个对象的地址返回,赋给字符串常量。当创建字符串对象s1时,字符串池中已经存在"ccc"这个对象,直接把对象"ccc"的引用地址返回给s1,这样s1指向了池中"ccc"这个对象,也就是说s0和s1指向了同一个对象,因此语句System.out.println(s0 == s1)输出:true。
(2)采用new关键字新建一个字符串对象
1 public class Demo { 2 public static void main(String... strings) { 3 String str3=new String("aaa"); 4 String str4=new String("aaa"); 5 System.out.println("===========test2============"); 6 System.out.println(str3==str4); 7 } 8 } 9 /*~ 10 * output 11 * ===========test2============ 12 false 13 new的方式是生成不同的对象 14 */
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Result:Constant pool 静态常量池只有aaa对象类型;Java栈中有两个对象并执行句柄池。
采用new关键字新建一个字符串对象时,JVM首先在字符串池中查找有没有"aaa"这个字符串对象,如果有,则不在池中再去创建"aaa"这个对象了,直接在堆中创建一个"aaa"字符串对象,然后将堆中的这个"aaa"对象的地址返回赋给引用str3,这样,str3就指向了堆中创建的这个"aaa"字符串对象;如果没有,则首先在字符串池中创建一个"aaa"字符串对象,然后再在堆中创建一个"aaa"字符串对象,然后将堆中这个"aaa"字符串对象的地址返回赋给str3引用,这样,str3指向了堆中创建的这个"aaa"字符串对象。当执行String str4=new String("aaa")时, 因为采用new关键字创建对象时,每次new出来的都是一个新的对象,也即是说引用str3和str4指向的是两个不同的对象,因此语句System.out.println(str3 == str4)输出:false。
(3)编译期确定
1 public class Demo { 2 public static void main(String... strings) { 3 String s0="helloworld"; 4 String s1="helloworld"; 5 String s2="hello"+"world"; 6 System.out.println("===========test3============"); 7 System.out.println(s0==s1); 8 System.out.println(s0==s2); 9 } 10 } 11 /*~ 12 * output 13 * ===========test3============ 14 true 15 true 16 */
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
分析:因为例子中的s0和s1中的"helloworld”都是字符串常量,它们在编译期就被确定了,所以s0==s1为true;而"hello”和"world”也都是字符串常量,当一个字符串由多个字符串常量连接而成时,它自己肯定也是字符串常量,所以s2也同样在编译期就被解析为一个字符串常量,所以s2也是常量池中"helloworld”的一个引用。所以我们得出s0==s1==s2。
(4)编译期无法确定
1 public class Demo { 2 public static void main(String... strings) { 3 String s0="helloworld"; 4 String s1=new String("helloworld"); 5 String s2="hello" + new String("world"); 6 System.out.println("===========test4============"); 7 System.out.println( s0==s1 ); 8 System.out.println( s0==s2 ); 9 System.out.println( s1==s2 ); 10 } 11 } 12 /*~ 13 * output 14 * ===========test4============ 15 false 16 false 17 false 18 */
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Result:Constant pool方法区中的静态常量池有三个对象helloword;hello;word。
分析:
String s2="hello" + new String("world");
①new StringBuilder("hello");
②new String("world");
③new StringBuilder("hello").append(new String("world")).toString();
个人想法:类加载完成后Java堆中有3个对象实例。两个String对象;一个StringBuilder对象。(这是个人猜测如果有不对请指正)
分析:用new String() 创建的字符串不是常量,不能在编译期就确定,所以new String() 创建的字符串不放入常量池中,它们有自己的地址空间。s0还是常量池中"helloworld”的引用,s1因为无法在编译期确定,所以是运行时创建的新对象"helloworld”的引用,s2因为有后半部分new String(”world”)所以也无法在编译期确定,所以也是一个新创建对象"helloworld”的引用。(这是我参考的大神文章的分析,但是我觉得他有点不对;new String() 创建的字符串是放进编译时方法区的静态常量池,当JVM加载类时String引用就会根据句柄池的指针来指向常量池中的对象类型从而在Java堆中创建String对象实例随后指向S1。可能大神指的不放入常量池是运行时的常量池(上面大神提过);但是运行常量池本质是不是就是静态常量池呢?)(希望有人能认真思考给出答案;或者留言商量)
(5)另一种编译期确定和优化
1 public class Demo { 2 public static void main(String... strings) { 3 String s0 = "ab"; 4 final String s1 = "b"; 5 String s2 = "a" + s1; 6 System.out.println("===========test9============"); 7 System.out.println((s0 == s2)); 8 } 9 } 10 /* 11 * ~ output ===========test9============ true 12 */
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
分析:String s2="a"+s1;
result:静态常量池只有对象类型String ab;并没有new 一个对象实例;显然说明了JVM对s1进行了优化,JVM在编译的时候直接将final修饰的String引用看成常量看待。
故:String s2="a"+s1;相当于 String s2="a"+"b";所以静态常量池只有ab对象类型。
分析:和例子7中唯一不同的是s1字符串加了final修饰,对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到自己的常量池中或嵌入到它的字节码流中。所以此时的"a" + s1和"a" + "b"效果是一样的。故上面程序的结果为true。
方法剖析
方法名 | 功能 | 方法名 | 功能 |
---|---|---|---|
String | 构造 | codePoint* | 取值 |
length | 长度 | getChars | 取值 |
isEmpty | 判空 | getBytes | 取值 |
charAt | 取值 | *equals* | 判等 |
compareTo* | 比较 | regionMatches | 正则 |
startWith | 判断 | *indexOf | 取值 |
substring | 截取 | concat | 拼接 |
replace* | 替换 | matches | 正则 |
contains | 包含 | split | 分割 |
join | 拼接 | to* | 转换 |
trim | 去空格 | format | 格式化 |
*valueOf | 转换 | intern | 获取 |
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
说明:方法是参考另一个大神整理得到的;其实我们只要记住核心的方法就行;如果需要用到再查API印象更深刻和容易理解。
(1)String.intern()
intern()方法API说明:
public String intern()
返回字符串对象的规范表示。
最初为空的字符串池由String
类String
。
当调用intern方法时,如果池已经包含与equals(Object)
方法确定的相当于此String
对象的字符串,则返回来自池的字符串。 否则,此String
对象将添加到池中,并返回对此String
对象的引用。
由此可见,对于任何两个字符串s
和t
, s.intern() == t.intern()
是true
当且仅当s.equals(t)
是true
。
所有文字字符串和字符串值常量表达式都被实体化。 字符串文字在The Java™ Language Specification的 3.10.5节中定义。
结果
一个字符串与该字符串具有相同的内容,但保证来自一个唯一的字符串池。
String实例str调用intern()方法时,java查找常量池中是否有相同unicode的字符串常量,如果有,则返回其引用,如果没有,则在常量池中增加一个unicode等于str的字符串并返回它的引用。
(2)equals和==(面试题源码的实现)
1 /* 2 String‘s equals 3 */ 4 public boolean equals(Object anObject) { 5 if (this == anObject) { 6 return true; 7 } 8 if (anObject instanceof String) { 9 String anotherString = (String)anObject; 10 int n = value.length; 11 if (n == anotherString.value.length) { 12 char v1[] = value; 13 char v2[] = anotherString.value; 14 int i = 0; 15 while (n-- != 0) { 16 if (v1[i] != v2[i]) 17 return false; 18 i++; 19 } 20 return true; 21 } 22 } 23 return false; 24 } 25 /* 26 Object‘s equals 27 */ 28 public boolean equals(Object obj) { 29 return (this == obj); 30 }
1.对于==,如果作用于基本数据类型的变量(byte,short,char,int,long,float,double,boolean ),则直接比较其存储的"值"是否相等;如果作用于引用类型的变量(String),则比较的是所指向的对象的地址(即是否指向同一个对象)。
2.equals方法是基类Object中的方法,因此对于所有的继承于Object的类都会有该方法。在Object类中,equals方法是用来比较两个对象的引用是否相等,即是否指向同一个对象。
3.对于equals方法,注意:equals方法不能作用于基本数据类型的变量。如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;而String类对equals方法进行了重写,用来比较指向的字符串对象所存储的字符串是否相等。其他的一些类诸如Double,Date,Integer等,都对equals方法进行了重写用来比较指向的对象所存储的内容是否相等。
String、StringBuffer、StringBuilder的区别(面试题)
1.可变与不可变:String是不可变字符串对象,StringBuilder和StringBuffer是可变字符串对象(其内部的字符数组长度可变)。
2.是否多线程安全:String中的对象是不可变的,也就可以理解为常量,显然线程安全。StringBuffer 与 StringBuilder 中的方法和功能完全是等价的,只是StringBuffer 中的方法大都采用了synchronized 关键字进行修饰,因此是线程安全的,而 StringBuilder 没有这个修饰,可以被认为是非线程安全的。
3.String、StringBuilder、StringBuffer三者的执行效率:
StringBuilder > StringBuffer > String 当然这个是相对的,不一定在所有情况下都是这样。比如String str = "hello"+ "world"的效率就比 StringBuilder st = new StringBuilder().append("hello").append("world")要高。因此,这三个类是各有利弊,应当根据不同的情况来进行选择使用:
当字符串相加操作或者改动较少的情况下,建议使用 String str="hello"这种形式;
当字符串相加操作较多的情况下,建议使用StringBuilder,如果采用了多线程,则使用StringBuffer。
String中的final用法和理解
1 final StringBuffer a = new StringBuffer("111"); 2 final StringBuffer b = new StringBuffer("222"); 3 a=b;//此句编译不通过 4 5 final StringBuffer a = new StringBuffer("111"); 6 a.append("222");//编译通过
final只对引用的"值"(即内存地址)有效,它迫使引用只能指向初始指向的那个对象,改变它的指向会导致编译期错误。至于它所指向的对象的变化,final是不负责的。
-------------------------------------------------------------------------------------------------------------------------------------------------------------
字符串池的优缺点
字符串池的优点就是避免了相同内容的字符串的创建,节省了内存,省去了创建相同字符串的时间,同时提升了性能;另一方面,字符串池的缺点就是牺牲了JVM在常量池中遍历对象所需要的时间,不过其时间成本相比而言比较低。
值传递 VS 引用传递(面试题)
1 public class StringDemo { 2 public static void main(String[] args) { 3 String s = new String("aaa"); 4 change(s); 5 System.out.println(s); 6 7 } 8 9 private static void change(String arg) { 10 arg = "bbb"; 11 } 12 } 13 /*~output 14 * aaa 15 * 16 */
1.Java中的String参数是值传递方式
2.String类是一个 immutable 类,该类的对象生成后,内容不会发生变化。该类中的所有返回String类型对象的成员方法都是返回一个新的String对象。(特例性)
对比:(面试题)
1 public void change(Car car) { 2 car.name = "aaa"; 3 } 4 Car car = new Car("ccc"); 5 car.change(car); 6 System.out.println(car.getName());
也是值传递;Java编程语言只有值传递参数。
总结:
1.当一个对象实例作为一个参数被传递到方法中时,参数的值就是该对象的引用一个副本。指向同一个对象,对象的内容可以在被调用的方法中改变,但对象的引用(不是引用的副本)是永远不会改变的。
2.Java中没有指针,所以也没有引用传递了,仅仅有值传递。不过,可以通过对象的方式来实现引用传递。类似Java没有多继承,但可以用多次implements接口实现多继承的功能。
3.在Java应用程序中永远不会传递对象,而只传递对象的引用。因此是按引用传递对象。但重要的是要区分参数是如何传递的。
4.Java应用程序中的变量可以为以下两种类型之一:引用类型或基本类型。当作为参数传递给一个方法时,处理这两种类型的方式是相同的。两种类型都是按值传递的,没有一种按引用传递。
注:本人是大二在校生,因为研读了Thinking in Java这本书初始化和清理这一章联想到了String类的底层实现所以研究了源码,因为String类涉及到JVM分析,因为本人的原因并没有研读深入理解Java虚拟机都是在网上找资料自己学习摸索的。如果我有分析不到位的地方请大神们提出来大家一起学习和进步。
以上是关于读深入理解Java中的String(包括JVM)一文总结和提升的主要内容,如果未能解决你的问题,请参考以下文章