先不要太关注参数到底是值传递还是引用传递,抛开这个想法,先搞清楚Java中值、对象、对象的引用是怎么存储的?
- 栈:存放8种基本数据类型的变量和对象的引用(对象的引用保存的只是对象本身的地址),对象本身不存放在栈中,而是存放在堆和常量池中。
- 堆:存放所有new出来的对象或数组。JVM不定时查看堆中的对象,如果没有引用指向这个对象就回收。
- 常量池:存放字符串常量和基本类型常量(public static final)。
一、基本数据类型
基本数据类型的变量和常量:变量和引用存储在栈中,常量存储在常量池中。
以int为例:
int m1 = 7;
int m2 = 7;
public static final int m3 = 7;
public static final int m4 = 7;
基本数据类型跟堆没有任何关系。
二、数组,字符串和其他引用类型
下面的例子对于数组,字符串和其他引用类型都是一样的,只是用字符串举例。
以String为例:
对于字符串:其对象的引用都是存储在栈中,对象本身如果是编译期已经创建好(直接用双引号赋值的:String s1 = “mistra1”; )的就存储在常量池中,如果是运行期(new出来的)就存储在堆中。对于equals相等的字符串,在常量池中永远只有一份,在堆中有多份。
String m1 = "mistra";
String m2 = "mistra";
String m3 = new String("mistra");
String m4 = new String("mistra");
String m1 = “mistra”; ,这行代码被执行的时候,JVM首先在常量池中查找是否已经存在了值为"mistra"的这么一个对象,它的判断依据是String类equals(Object obj)方法的返回值。如果有,则不再创建新的对象,直接返回已存在对象的引用;如果没有,则先创建这个对象,然后把它加入到字符串池中,再将它的引用返回。
通过new产生一个字符串(假设为"mistra")时,会先去常量池中查找是否已经有了"mistra"对象,如果没有则在常量池中创建一个此字符串对象,然后在堆中再创建一个常量池中此"mistra"对象的拷贝对象。
String s = new String(“mistra”);产生几个对象?一个或两个,如果常量池中原来没有"mistra",就是两个。
- 对于成员变量和局部变量:成员变量就是在方法外部,类的内部定义的变量;局部变量就是在方法或语句块内部定义的变量。局部变量必须初始化。 形式参数是局部变量,局部变量的数据存在于栈内存中。栈内存中的局部变量随着方法的消失而消失。 成员变量存储在堆中的对象里面,由垃圾回收器负责回收。
三、到底是值传递还是引用传递?
基本数据类型:值就直接保存在变量中。
引用类型:变量中保存的只是实际对象的地址。这里的变量就是对象的引用,引用指向实际对象,实际对象中保存着内容。
|>>>>>赋值运算符(=)的作用
赋值运算符对基本数据类型和引用类型的作用看下图就可以明白:
对于基本数据类型num ,赋值运算符会直接改变变量的值,原来的值被覆盖掉。
对于引用类型str,赋值运算符会改变引用中所保存的地址,原来的地址被覆盖掉。但是原来的对象(“mistra”)不会被改变。如上图所示,“mistra” 字符串对象没有被改变。(没有被任何引用所指向的对象是垃圾,会被垃圾回收器回收)
1、基本数据类型:
public class ParameterTransferTest1 {
public static void main(String[] args) {
int a = 3;
change(a);
System.out.println(a);
}
public static void change(int i) {
i =10;
}
}
输出结果:3
基本数据类型是值传递,传递的只是一个副本,与原值无关。 相当于把a的值3 copy给了 i,i 改变值并不会影响a的值。
2、没有提供改变自身方法的引用类型:
这里涉及到String与StringBuilder的知识点了。
public class ParameterTransferTest2 {
public static void main(String [] args){
String str = "a";
change(str);
System.out.println(str);
}
public static void change(String i) {
i = "b";
}
}
输出结果:a
每次改变String对象的值都是新创建了一个对象,原对象还在。而StringBuilder对象不同,每次改变都是在对象本身改变,不创建新对象。
假设str指向了地址x010,把这个地址引用传递给了 i,i 也指向x010,但是执行 i = “b”;之后,i 指向了新地址x011,并不影响str指向的地址,所以没有改变。
3、提供了改变自身方法的引用类型:
public class ParameterTransferTest3 {
public static void main(String [] args){
StringBuilder sb = new StringBuilder("a");
change(sb);
System.out.println(sb);
}
public static void change(StringBuilder i) {
i.append("b");
}
}
输出结果:ab
假设sb指向了地址x010,把这个地址引用传递给了 i,i 也指向x010,但是执行 i.append(“b”);之后,i 指向的地址不变,还是指向x010,所以影响了sb的值。
这里放几道例题加深理解:例题原博客地址
public class foo {
public static void main(String sgf[]) {
StringBuffer a=new StringBuffer(“A”);
StringBuffer b=new StringBuffer(“B”);
operate(a,b);
System.out.println(a+”.”+b);
}
static void operate(StringBuffer x,StringBuffer y) {
x.append(y);
y=x;
}
}
public class Example {
String str = "abc";
char[] ch = { 'a', 'b', 'c' };
public static void main(String args[]) {
Example ex = new Example();
ex.change(ex.str, ex.ch);
System.out.println(ex.str);
System.out.println(ex.ch);
}
public void change(String str, char ch[]) {
*** // ch = new char[]{'a','b','c'};
str = "change";
ch[0] = 'c';
}
}
输出结果:
abc
cba
变量ch和引用传递是一样的,都是操作的为同一对象,如果有* * *的一行不注释的话,结果就为
abc
abc