Java中常量池、字符串、参数传递涉及到的一些问题
为什么有这篇文章
String orgId = someThreadLocal.getOrgId();
someClass.someMethod(orgId);
someClass.someMethod(someThreadLocal.getOrgId());
起源于上面这段代码对比,我们产生了一些争论:
A:
orgId
这个中间变量,导致JVM多产生了一个对象。
B & C: 不,没有多产生。
为了了结这段争论,我花时间阅读了一小部分《深入理解Java虚拟机》,并结合网上的资料做了部分总结。
常量池
常量池里有什么?
- 字面量(Liternal)
- 符号引用(Symbolic References)
常量池从哪里来
javac编译时,将能确定的常量(UTF8编码的字符串,integer, float, long, double, string)保存到了.class
文件中的常量池部分中。
紧接着主次版本号之后的是常量池入口,常量池是Class文件结构中与其他项目关联最多的数据类型名也是占用Class文件控件最大的数据项目之一,同时它还是在Class文件中第一个出现的表类型数据结构。 --《深入理解Java虚拟机》
如String s = "haha"
对应的虚拟机指令是
0: ldc #16; //String haha
2: astore_1
3: return
以上内容通过javap
命令获得。haha
这个字符串被保存在常量池的CONSTANT_String_info
表中。
String与常量池
所以以下几种例子,字符串在编译后都保存在常量池中:
String s1 = "hello world";
String s2 = "hello" + " world";
System.out.println(s1 == s2); // Output: true
结果说明:JVM创建了两个引用s1和s2,但在String Pool中只创建了一个对象(hello world),而且两个引用都指向了该对象。
当变量的值编译期无法确定时(加号连接有引用存在),就会在运行时动态拼接,并把新地址赋给新变量:
String s1 = new String("hello world2");
String s2 = new String("hello world2");
System.out.println(s1 == s2); // Output: false
再看下String的构造函数描述:
* Initializes a newly created {@code String} object so that it represents
* the same sequence of characters as the argument; in other words, the
* newly created string is a copy of the argument string. Unless an
* explicit copy of {@code original} is needed, use of this constructor is
* unnecessary since Strings are immutable.
意思也很明确,每一个new出来的String对象都是你传入的参数的复制,都是新的对象。而我们又知道Java中new出的对象都是存在Heap中的,所以这里并不会说常量池中有这个字符串,就不产生新对象了。
上面这段分析的结论是:运行时的new String总是会产生新对象的且存在Heap中。
那么最开始的问题就变成了:方法返回的字符串和外面接收到的字符串是同一个对象吗?
方法返回的String是否是新的对象?
public static void main(String[] args) {
String s1 = oneString();
System.out.println(System.identityHashCode(s1)); // Output: 1044036744
}
private static String oneString() {
String s1 = new String("hello world3");
System.out.println(System.identityHashCode(s1)); // Output: 1044036744
return s1;
}
通过System.identityHashCode获取与内存地址强相关的hashCode,可见并没有产生新的对象。
不过此时我又有新的疑问了,方法传入的String是否是新的对象?
方法传入的String是否是新的对象?
public static void main(String[] args) {
String s1 = "hello world4";
System.out.println(System.identityHashCode(s1)); // Output: 1044036744
pass(s1);
}
private static void pass(String s) {
System.out.println(System.identityHashCode(s)); // Output: 1044036744
}
由输出可见:传入String的也是同一个对象。那为何我改变String的时候,外部的String不变呢?和平时的理解不一样啊!
经过一番查询,找到了一种解释:
Java is always pass-by-value. when we pass the value of an object, we are passing the reference to it.
如下图:
到此为止最开始的问题已解决了。