如果函数内引用没有被改变,函数内部的对象被修改将导致外部的对象变化。
如果函数内部引用发生了改变—引用被切断:(B=A,或者引用了 新new的对象),则函数内部的对象修改不会导致外部对象的变化。
可以简单的记为,在初始化时,“=”语句左边的是引用,右边new出来的是对象。
在后面的左右都是引用的“=”语句时,左右的引用同时指向了右边引用所指向的对象。
传值意味着传的是副本,不会改变被传递对象本身。传引用既是传内存地址(也可认为是内存地址的副本),如果传递的对象发生了改变,那么被传递的对象也将改变(所有某些场景需要深拷贝)
1、对象是按引用传递的,原始数据类型是按值传递的
2、如果把引用副本也当做是一种“值”(java中一个对象s是什么,同样也是一个指针),对于jvm来说,这个引用(内存地址)也仅仅是一个int型的整数,所以可以认为:Java 应用程序有且仅有的一种参数传递机制,即按值传递。
但是,传值和传引用本来就是两个不同的内容,没必要把两者弄在一起,弄在一起反而更不易理解。
对于一切对象型变量,Java都是传引用的副本。其实传引用副本的实质就是复制指向地址的指针,只不过Java不像C++中有显著的*和&符号。(这里Java和C++不同,在C++中,当参数是引用类型时,传递的是真实引用而不是引用副本)
也就是说,参数传递,如果参数是对象的话,那么此时,传递的是引用。所以如果函数内部(形参)让该参数对象发生变化,外部对象(被传递的参数也会发生变化)。
以代码下来证明3,4在参数的传递中,哪一个是正确的?
3、按值传递意味着当将一个参数传递给一个函数时,函数接收的是原始值的一个副本
4、按引用传递意味着当将一个参数传递给一个函数时,函数接收的是原始值的内存地址,而不是值的副本
首先我们来看看第一点:对象是按引用传递的确实,这一点我想大家没有任何疑问,例如:
class Test01
{
public static void main(String[] args)
{
StringBuffer s= new StringBuffer("good");
StringBuffer s2=s;
s2.append(" afternoon.");
System.out.println(s);
}
}
对象s和s2指向的是内存中的同一个地址因此指向的也是同一个对象。
如何解释“对象是按引用传递的”的呢?
这里的意思是进行对象赋值操作是传递的是对象的引用,因此对象是按引用传递的,有问题吗?
程序运行的输出是:
good afternoon.
这说明s2和s是同一个对象。所以对象的赋值,是传递的引用,他们的已经绑为一体。
所以反过来,s.append, 最终打印s2,结果依然是一样的:当引用传递(对象赋值)的时候,无论哪一个对象内容反正了改变,另外一个对象也会发生变化,除非某个对象重新建立一个引用(s2 = new StringBuffer())。
这里有一点要澄清的是,这里的传对象其实也是传值,因为对象就是一个指针,这个赋值是指针之间的赋值,因此在java中就将它说成了传引用。(引用是什么?不就是地址吗?地址是什么,不过就是一个整数值)
再看看下面的例子:
class Test02
{
public static void main(String[] args)
{
int i=5;
int i2=i;
i2=6;
System.out.println(i);
}
}
程序的结果是什么?5!!!
这说明什么,原始数据类型是按值传递的,这个按值传递也是指的是进行赋值时的行为。
下一个问题:如果把对象的引用看作内存地址的值,那么Java 应用程序有且仅有的一种参数传递机制,即按值传递.(基本变量传递肯定是值传递)
下面的例子加入了在函数中进行引用赋值的操作,进行之前,先回顾一下Java中,对象与引用的重要概念。
如下表达式:
A a1 = new A();
它代表A是类,a1是引用,a1不是对象,new A()才是对象,a1引用指向new A()这个对象。
在JAVA里,“=”不能被看成是一个赋值语句,它不是在把一个对象赋给另外一个对象,它的执行过程实质上是将右边对象的地址传给了左边的引用,使得左边的引用指向了右边的对象。JAVA表面上看起来没有指针,但它的引用其实质就是一个指针,引用里面存放的并不是对象,而是该对象的地址,使得该引用指向了对象。在JAVA里,“=”语句不应该被翻译成赋值语句,因为它所执行的确实不是一个赋值的过程,而是一个传地址的过程,被译成赋值语句会造成很多误解,译得不准确。
所以当看到"A=B"操作时,要真正理解它为 A→B:A的引用地址变成了B的引用地址。或者说,让A和B都指向同一地址。切忌简单想成A等于B,或者把B的值赋给A。
这样,当函数体中进行引用变换时: A=B, 要考虑:右边的B对应的外部B引用会变,左边的A对应的外部A引用反而不会变:即函数外部B会变(因为B没有被赋予新的引用,所以他对应的值在函数内被改变,而造成了外部B引用也跟着改变[因为对象传递是引用传递]),而外部A引用的打印值不变,是因为在函数内部,A被赋予了新的引用【B】,所以外部A与函数内部A的引用关系被切断,他们指向了不同的对象。
再如:
A a2;
它代表A是类,a2是引用,a2不是对象,a2所指向的对象为空null;
再如:
a2 = a1;
它代表,a2是引用,a1也是引用,a1所指向的对象的地址传给了a2(传址),使得a2和a1指向了同一对象。
class Test03
{
public static void main(String[] args)
{
StringBuffer s= new StringBuffer("good");
StringBuffer s2=new StringBuffer("bad");
test(s,s2);
System.out.println(s);//9
System.out.println(s2);//10
}
static void test(StringBuffer s,StringBuffer s2) {
System.out.println(s);//1
System.out.println(s2);//2
s2=s;//3 改变s2的引用,使s2指向引用对象s,即s2和s都指向同一对象
s=new StringBuffer("new");//4
/*
* note: 本来s被new 了新的,引用被切断也不应该被改变外部的s,但是因为s2=s(且s2在函数中没有被赋予新的引用),那么s2的变化
* 才导致外部s的变化。
*
* important: 如果3,4调换顺序,则s的引用被改变成new,且s2指向s相同的新引用new,那么相对于外部引用来说,函数内部的s和s2,他们的引用都发生了变化
* 所以外部(main)的打印结果,保持不变。
*/
System.out.println(s);//5 s被赋予了新的引用,所以打印新值: new
System.out.println(s2);//6 对象的赋值是引用传递,但关联引用(s2-->s)不会实时变化,所以s2的引用地址还是老的s,这个跟s2=s;s.append("new"), 然后导致s2也 //会被append "new"不一样。
s.append("hahs");//7 此时函数内s, 是newhahs1, 对于外部s来说,这个append不会起作用,因为第4步里,s被重新设置了引用.
s2.append("hahs2");//8 因为s2=s的关系,此时s2.append相当于s.append。且s2指向的是老的s引用地址,则此时,外部s,仍然是good,所以函数执行完毕后s的打
//印是good+ s2.append的值【并没有受到new StringBuffer的影响】
/* 8 等价于s.append("hahs2"), 因为在第3步中这个引用对象被赋予了新的引用:“s”, 等同于切断外部s2和内部s2的 相同引用传递。则引用赋值s2=s的左边s2
* 是被切断的引用,那么外部的s2(原来那个引用对象s2)就和此时的s2毫无关联,他们指向了不同的对象。s也被new了新的引用,所以外部s本该不变,但
* 第8行,因为s2与s已经连为一体(引用赋值),所以s2变化将绑定在老s(外部s)上,因为他们是同一地址。则s2的append,实际上对于外部的s来说,是
* 相当于进行了s.append("hahs2") 的操作
* 所以不论函数里面的s,s2如何变化,都不会影响外部的那个引用对象s2(原来的s2),故调用方法前后的s2没有发生变化。
*/
}
}
程序的输出是:
good
bad
new
good
goodhahs2
bad
Note:test方法传递的值,所以9,10中,s2 原来的值不变。
Need to think: s为什么变成了goodhah?
对于基本類型和字符串(字符串特殊,因为其是immutable,所以传递字符串的时候,对象作为参数在函数内部发生变化,外部这个对象,是不会变的)是传值,但是对于对象而言,传的是引用,而引用指向的是同一個對象!所以函数外部的s和s2的地址一致(绑定在了一起,操作同一块内存空间),而s2被append了,所以s发生了变化。