Java函数传参(String的不可变性)
本篇博客无意讨论Java里面函数传参是否只有值传递还是值传递和引用传递同时存在,仅讨论函数传参的一些原理。
一. 什么是函数传参?
最常见的一个swap函数:下面这个swap函数并不能实现我们想要的交换的功能,因为它传递的是基本数据类型。类似于C++的值传递:二级指针和指针引用函数传参(C++)。这里是Java的函数传参,所以下面将分别介绍两种传参的方式。
public class FunctionTest {
public static void main(String[] args) {
FunctionTest functionTest=new FunctionTest();
int a=11;
int b=22;
functionTest.swap(a,b);
System.out.println(a); //11
System.out.println(b); //22
}
public void swap(int a,int b){
int c=a;
a=b;
b=c;
}
}
二. 基本数据类型(值传递)
public class FunctionTest {
public static void main(String[] args) {
FunctionTest functionTest=new FunctionTest();
int a=11;
int b=22;
functionTest.swap(a,b);
System.out.println(a);
System.out.println(b);
}
public void swap(int c,int d){
int mid=c;
c=d;
d=mid;
}
}
在 functionTest.swap(a,b)
调用之前,a,b两个变量如下:
那么在调用functionTest.swap(a,b)
之后且未执行int mid=c;
之前吶?函数仅完成了函数参数与传入参数的赋值,由于c,d在函数体内重新分配地址,并使用a,b的值进行赋值,在这之后,函数体内对c,d的操作就与a,b无关了。
结论:基本数据类型传入函数参数在函数体内对参数进行的操作不影响原数据
三. 对象数据类型(引用传递)
//和上面代码不同的是int换成了StringBuilder类型,为什么不是String吶?
//因为String类型有着自己的特殊性,这又区别于其它的对象数据类型。
public class FunctionTest {
public static void main(String[] args) {
FunctionTest functionTest=new FunctionTest();
StringBuilder a=new StringBuilder("11");
StringBuilder b=new StringBuilder("22");
functionTest.swap(a,b);
System.out.println(a); //11
System.out.println(b); //22
}
public void swap(StringBuilder c,StringBuilder d){
StringBuilder mid=c;
c=d;
d=mid;
}
}
从输出结果来看,似乎和基本数据类型一样,都没能改变实参a,b的值。引用类型我们依然可以看做是地址的传值,而带有&的则表示地址的值。通过直接引用赋值的方式也不能更改实参的值,这个看起来和上面的基本数据类型是一样的。既然直接更改地址不可行,那么我们可以尝试更改地址指向的值(即上图最后一列的值)。
public class FunctionTest {
public static void main(String[] args) {
FunctionTest functionTest=new FunctionTest();
StringBuilder a=new StringBuilder("11");
StringBuilder b=new StringBuilder("22");
functionTest.swap(a,b);
System.out.println(a); //22
System.out.println(b); //11
}
public void swap(StringBuilder c,StringBuilder d){
StringBuilder mid=new StringBuilder(c);//申请新的内存复制c的值
c.delete(0,c.length());//清空c的值
c.append(d);//将b的值追加在c之后,两句合起来就是d的值赋给c
d.delete(0,d.length());
d.append(mid);
}
}
这次我们通过实参的值更改却是成功了。
结论:对象数据类型传入函数参数在函数体内对参数地址的操作不影响原数据,对地址指向的值更改直接更改了实参的值(毕竟它们指向一个地方)
四. 对象数据类型(引用传递)的一些特例
说是特例,不过是一些对象数据类型在Java语言里面存在着一些特性,如String类型指向的值不可变性,包装类(Integer,Float,Double…),下面就说明一下上面列举的这些例子。
Ⅰ. String类型的值不可变性
package lizi;
public class FunctionTest {
public static void main(String[] args) {
FunctionTest functionTest=new FunctionTest();
String strA="123";
functionTest.test(strA);
System.out.println(strA); //123
}
public void test(String strB){
strB="456";
}
}
上面看似是想通过修改值来更改实参的值,然而却是失败了,这不与引用传参改变地址指向的值可以改变实参的值的结论相违背吗?我们想改变地址指向的值,然而由于String的值不可变,Java并不允许我们直接修改它的值。Java将456存储在一个新申请的地址里面,而原来存在123的地址依然还是123,再将456的地址返回给strB,看似更改地址的值,却被String的特性弄成了修改地址,自然修改完地址与实参的关系也就断了,所以还是符合上面引用传参的结论。
Ⅱ. 包装类的自动装箱
public class FunctionTest {
public static void main(String[] args) {
FunctionTest functionTest=new FunctionTest();
Integer intA=111;
functionTest.test(intA);
System.out.println(intA);//111
}
public void test(Integer intB){
intB=555;
}
}
上面test函数字节码如下:
public void test(java.lang.Integer);
Code:
0: sipush 555
3: invokestatic #14//赋值时调用了valueOf函数进行自动装箱操作
// Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
6: astore_1
7: return
}
由于赋值时候的valueOf函数调用返回了一个Integer对象,变成了和String类似的地址的改变,自然也就无法影响到实参intA的值了。