参数类型:
- 形参:方法被调用时需要传递进来参数,例如:public void run(int a)中的int a ,它只有run方法被调用间a才有意义,也就是被分配内存空间,在方法执行完毕后,方法出栈即被销毁释放内存空间,也就不存在了。
- 实参:方法被调用时传递进来的实际值,它在方法被调用前就被初始化,并且在方法被调用时传入。例:成员变量和局部变量(方法内的局部变量除外)
值传递和引用传递:
- 值传递:在方法被调用时,实参通过把他的内容副本传入方法内部,此时形参接收的内容是实参的拷贝。因此在方法内对实参的任何操作,都仅仅是对这个内容的副本进行操作,不影响原初始值的内容。值传递传递的是一个真实的内容副本,对副本的操作不影响原内容,也就是形参再怎么变化,也不影响实参对应的内容。在jvm内存中的说法也就是传递的堆地址。
- 引用传递:“引用”也就是指向真实内容的地址值。在方法调用时,实参的地址通过放法调用传递给相应的形参,在方法体内,形参和实参指向同一块内存地址,对形参的操作会影响原来的内容。即传递的是栈地址。
java中不存在引用传递
看如下代码(基本类型参数的值传递):
public class Zcd {
public static void main(String[] args) {
int a=12;
System.out.println("调用过程前a的值"+a);
change(a);
System.out.println("调用过程后a的值"+a);
}
private static void change(int a) {
// TODO Auto-generated method stub
a=100;
System.out.println("调用过程中a的值"+a);
}
}
输出结果:
我们发现值传递和我们上面所说一致,它是把实参复制一份通过形参传给方法,而这个方法改变的只是副本,无法对原来的数值改变。
如下图:值传递的jvm内存图,因为int a是局部变量,它只在方法区内它不占用堆内存也不在方法区内存放,方法出栈数据就会丢失,所以change方法结束后它会出栈,而且赋的值也根本不会占用内存,副本的值也不会对原数据产生改变。这就是基本数据类型的值传递
看如下代码(引用类型参数的值传递):
public class Valtransfer {
public static void main(String[] args) {
String name=new String("123");
System.out.println(name);
System.out.println("调用过程前堆地址"+name.hashCode());
change(name);
System.out.println(name);
System.out.println("调用过程后堆地址"+name.hashCode());
}
private static void change(String name) {
// TODO Auto-generated method stub
name="lhs";
System.out.println("调用过程中堆地址"+name.hashCode());
System.out.println(name);
}
}
结果:
我们发现值传递和我们上面所说不一致,String比较特殊,它的源代码是由final修饰的,你无法改变他原始的值,它是把实参复制一份通过形参传给方法,而这个方法改变的只是副本,并且和其他引用类型参数不同它改变了它的堆地址,所以也无法对原来的数值改变。
string源代码如下图
如下图:值传递的jvm内存图,change方法结束后它会出栈,又因为string源代码是final修饰,我们知道final修饰的引用类型固定的是一个堆地址,所以复制的那一份应该是开辟了新的堆地址,副本的值并不会对原数据产生改变。
那我们看这个代码:
public class Valtransfer {
public static void main(String[] args) {
StringBuffer a = new StringBuffer("A");
StringBuffer b = new StringBuffer("B");
change(a, b);
System.out.println(a+","+b);
}
public static void change(StringBuffer x,StringBuffer y) {
x.append(y);
y = x;
}
}
结果是:
会发现它会对原数据进行行了改变,那他就是引用传递吧?并不是,他还是值传递 ,java没有引用传递。
StringBuffer的append方法会对数据所在的内存进行字符串拼接,从而避免内存资源浪费,提高效率,从而导致了对原数据的改变。
还有一种看如下代码(引用类型参数的值传递)
public class Valtransfer {
static String nameString;
static int age;
public Valtransfer(String name,int age) {
this.nameString=name;
this.age=age;
}
public static void main(String[] args) {
Valtransfer a=new Valtransfer("qcby",26);
System.out.println("调用过程前"+a.nameString);
getName(a.nameString);
System.out.println("调用后"+a.nameString);
}
public static void getName(String name) {
nameString="gs";
System.out.println("调用过程中"+nameString);
}
}
结果为:
这时就会疑问,这总会是引用传递了吧?并不是,这是引用类型参数的值传递 。
我们看jvm内存图:
getName方法入栈后改变了静态常量池中的内容,之后它出栈,所以导致了内容的改变。它还是复制了一份副本,他们堆地址相同,栈地址不同。我们所说的对副本的更改对原本没有影响是指在入栈后方法内的数据,而对指向的堆和方法区内的更改是会对原数据进行更改的,这就是造成java有引用传递争论的原因,结论是没有值传递。
还有一种看如下代码(引用类型参数的值传递)
public class Valtransfer {
String nameString;
int age;
public Valtransfer(String name,int age) {
this.nameString=name;
this.age=age;
}
public static void main(String[] args) {
Valtransfer a=new Valtransfer("qcby",26);
System.out.println("调用过程前"+a.nameString);
System.out.println("调用过程前堆地址"+a.hashCode());
getName(a);
System.out.println("调用后"+a.nameString);
System.out.println("调用过程后堆地址"+a.hashCode());
}
public static void getName(Valtransfer a) {
a.nameString="gs";
System.out.println("调用过程中"+a.nameString);
System.out.println("调用过程中堆地址"+a.hashCode());
}
}
有人就又会疑问了,形参里的是对象,那传递的应该是引用本身,这肯定是引用传递,我确实这样疑惑过,但仔细想想,其实还是值传递,因为他们指向同一个堆,所以改变堆内内容当然对原本有影响了,我们所说的没有影响是栈中的原数据,这里是对象a,副本a根本对原实参a无法造成影响,因为a是对象,所以你对对象a中数据的更改跟a没有关系,因为堆中的数据时共享的,很多人都会理解错这一点。
结果如下图:
还有这种值传递,如下图
我们说的复制的堆地址上面形参中的jvm图是这样copy的:
上面我把多种值传递情况都列了出来,希望可以解决大家的疑惑 。
总结:java没有引用传递,值传递复制的是堆地址,是拷贝了一个副本。
我们所说的对副本的更改对原本没有影响是指在入栈后方法内的数据,而对指向的堆和方法区内的更改是会对原数据进行更改的,这就是造成java有引用传递争论的原因