Java按值传递、按引用传递
一般我们会说Java基本类型采用值传递,对象以及数组采用引用传递。但事实上这只是表面上的现象。实质上,Java都是按值传递引用。(Java中“引用”的概念相当于C++中的指针,可以不断改变值)
一,对象
对象与变量(实例)的区别:
对象保存在heap,而变量保存在stack;对象的入口地址是不可预知的,所以程序只能通过变量来访问对象,变量是对象的一个引用。
例1:
class Word { String word; public Word(String word){ this.word = word; } public void print(){ System.out.println(word); } } Word o1, o2; o1 = new Word("Every Day"); o2 = o1; o2 = new Word("Every Night!"); o1.print();
结果是Every Day。
第一句o1 = new Word("Every Day");
首先创建一个Word实例,即对象,然后把引用赋值给o1。
第二句o2 = o1;
o1把对象的引用赋值给o2,注意赋的值是对象的引用而不是o1自身的引用。
所以,在第三句o2 = new Word("Every Night!");
就是又创建一个新对象,再把新对象的引用赋值给o2。
因为o1和 o2之间是值传,所以,对o2的改变丝毫不会影响到o1。
例2:
我们为Word类增加了一个新方法
public void setWord(String word){ this.word = word; } Word o1, o2; o1 = new Word("Every Day"); o2 = o1; o2.setWord("Every Night!"); o1.print();
这时的结果是"Every Night!"。
这个并不是变量的改变,因为o1只是保存对象的引用,执行之后,o1还是持有该对象的引用。所以,o1没变,变的是o1所引用的对象。
二,基本类型
栈的数据存储
存在栈中的数据可以共享。如:int a = 3; int b = 3;
编译器会先处理int a = 3,首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没找到,就将3存放进来,然后将a指向3。接着处理int b = 3,也是先创建b的引用,之后因为在栈中已经有3这个值,便将b直接指向3。这时如果令a = 4; 那么编译器会重新搜索栈中是否有4,如果没有,将4存放进来,并令a指向4;如果已经有了,则直接将a指向这个地址。因此a的改变不会影响到b的值。
由此可见,Java的基本类型也是按值传递的。
三,String类型
Java中的String是一个特殊的包装类数据,可以用以下两种形式来创建
1)String str = new String ("abc"); 存放在heap
2)String str = "abc"; 存储在heap的字符串常量池
***常量池背景知识***
1、类型常量池,存放在方法区里面,每个class文件都有一个
2、运行时常量池、存放在方法区里面,所有class共用
3、字符串常量池:存放在堆区里面
*********************
比较类里面的数值是否相等时,用equals()方法;测试两个类的引用是否指向同一个对象时,用==。
例3:
String a ="张敬轩"; String b ="张敬轩"; System.out.println(a == b); //true System.out.println(a.equals(b)); //true String a ="张敬轩"; String b = new String("张敬轩"); System.out.println(a == b); //false System.out.println(a.equals(b)); //true String a = new String("张敬轩"); String b = new String("张敬轩"); System.out.println(a == b); //false(在堆区的非字符串常量池里面创建了两个不同的对象) System.out.println(a.equals(b)); //true String a = "张"+"敬轩"; String b = "张敬轩"; System.out.println(a == b); //true(Java编译的优化机制:在编译的时候就已经开始进行计算。所以a指向的地址依旧是"张敬轩") System.out.println(a.equals(b)); //true String a = "张"; String b = "敬轩"; String c =a+b; String d = "张敬轩"; System.out.println(d == c); //false(c存储在堆区非字符串常量池里) System.out.println(d.equals(c)); //true String a = "张"; String b = "敬轩"; String c =(a+b).intern(); String d = "张敬轩"; System.out.println(d == c); //true(intern()方法是将堆区里面的非字符常量池里面的对象强行放到字符常量池里面) System.out.println(d.equals(c)); //true
String作为参数传递时,可以看作时基本数据类型,即方法体会在堆中开辟一块新内存存储String值,参数变量指向这个新地址。
例4:
public class Test { public static void main(String[] args) { String str = new String ("World"); char ch[] = {'H', 'e', 'l', 'l', 'o'}; change(str,ch); System.out.print(str+"and"); System.out.println(ch); } public static void change (String str, char ch[]) { str = "Change"; ch[0] = 'C'; } }
结果是World and Cello。