一直记得,JAVA语言除了8大基本类型(byte,short,char,int,long,float,double,boolean
),其他类型皆为引用类型。但是,今天有人告诉我String
为值类型,顿时迷惑了。回来百度一通,有人说String
是值类型,也有人说String
是引用类型,大家都有自己的说法。
好吧,求人不如求己,实践出真知。
首先申明下结论:Java中的String
是正宗的引用类型,但是,一定条件下,String
会表现出一定的值特性。贴上代码,开工:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toast.makeText(this, "test1() = " + test1(), Toast.LENGTH_LONG).show();
Toast.makeText(this, "test2() = " + test2(), Toast.LENGTH_LONG).show();
Toast.makeText(this, "test3() = " + test3(), Toast.LENGTH_LONG).show();
}
private boolean test1() {
String a = "123";
String b = "123";
return a == b;
}
private boolean test2() {
String a = new String("123");
String b = new String("123");
return a == b;
}
private boolean test3() {
String a = "123";
doTest3(a);
return TextUtils.equals("123", a);
}
private void doTest3(String str) {
str = str + "123";
}
}
从test1
开始分析:
test1()
返回值为true
,看起来有点像值类型。但是,一切都是可以解释的:
首先分析:
String a = "123";
和
String a = new String("123");
的不同之处:
-
前者的
String
对象分配在堆中,但在常量池中保存了指向String
对象的指针,而a
为String
型指针,指针的内容和常量之中“123
”对应的指针相同。具体来说,执行过程如下:- 首先,常量池中查找“
123
”的指针 - 如果在常量池中未能找到“
123
”的指针,则在堆中分配“123
”的内存空间,把地址保存到常量池中,并把这个地址赋值给String
型指针a
- 如果在常量池中找到“
123
”的指针,说明堆中已经存在“123
”的实体,因为常量表示一个不可变的对象,所以,没有必要再创建新的实例,直接把常量池中的指针内容赋值给String
型指针a
- 首先,常量池中查找“
-
而后者,实际上涉及到两个
String
实体,“123
”在堆中存在一个实体,并且在常量池中存在一个指向“123
”的指针,而new String()
会在堆中创建一个新的String
实体,并深度拷贝“123
”的内容,并返回新的String
实体的地址,赋值给指针a
所以,`test1`中,a和b两个指针都指向常量“`123`”的实体,他们的值相等,故`a==b`为`true`。 而`test2`中,a和b两个指针都指向各自的`String`实体,虽然两个`String`实体都深度拷贝自常量“`123`”,所以内容相同,但是,因为不是同一个`String`实体,故而内存地址不同,`a==b`为`false`。 说到这里,顺便说明一个C#和Java不同之处:`C#`对于所有的`String`对象都做了常量化处理,所以,内容确定的`String`实体在堆上只会有一个实例。故,如果`test2`函数以C#语言实现,则仅会有一个实例,`a==b`为`true`,这也是为什么`C#`中,`String`虽然是引用类型,却可以使用“`==`”操作符判断两个字符串相等的原因。 再来说下`test3` 粗看时,`string`类型作为引用传递,似乎应该被`doTest3`函数所改变,故`TextUtils.equal("123","123123")`应该为`false`才对,而实际测试结果则是:`test3()`返回值为`true`。 这里,让我通过调试来分析原因:
-
首先,创建a,a的实体地址为“
830042018440
”
接着,进入到doTest3
函数中,str的实体地址也为“830042018440”,这时,内存中存在a
和str
两个指针,他们都指向同一个String
实体
- 然后,我们发现str指针指向了一个新的实体“123123”,地址为“830042152712”,但是这并不影响a指针,a指针仍旧指向之前的实体“123”
- 最后,函数返回,
a
指针果然还指向“123
”,所以TextUtils.equal("123","123")
返回true
总结:
我想前面的解释已经很清楚了,String
类型的确是引用类型,虽然某些情况下,看起来有些像值类型。
另外,相信有些细心的朋友已经发现了,String
的实现实际上就是char[]
加变量count
(先忽略offset
和hash code
),的确从本质上来说,String
可以归结于char[]
,既然char[]
是引用类型的,那String
怎么可能是值类型呢?