java参数传递:值传递

 简述栈、堆和方法区的用法

通常我们定义一个基本数据类型的变量非成员变量,对象的成员变量和对象一起放在堆中)、一个对象的引用、还有就是函数调用的现场保存都使用JVM中的栈(stack)空间


通过new关键字和构造器创建的对象则放在堆(heap)空间,没有被引用的对象就成为“垃圾”,因此堆是垃圾收集器(GC)管理的主要区域。由于现在的垃圾收集器都采用分代收集算法,所以堆空间还可以细分为新生代和老年代,再具体一点可以分为Eden、Survivor、Tenured。堆是线程共享的内存区域。


方法区(method area)用于存储已经被JVM加载的类信息常量静态变量JIT编译器编译后的代码等数据。程序中的字面量,如直接书写的100、“hello”和常量都是放在常量池中,常量池是方法区的一部分。


栈空间操作起来最快但是栈空间很小,通常大量的对象都放在堆空间,栈和堆的大小都是可以通过JVM的启动参数进行调整。栈空间用完了会引发StackOverflowError,而堆和常量池空间不足会引发OutOfMemoryError。

String str = new String(“Hello”);

上面语句中变量str是一个引用,放在栈空间上的,用new创建出来的字符串对象是放在堆空间上的,而“Hello”这个字面量是放在方法区的。栈空间中存放的是new对象在堆空间存放的地址值。

Person p = new Person(1);
Class Person{
int a=1;
public Person(int a){
this.a=a;
}
}

上面语句中p是一个指向Person类的实例(对象)的一个引用,放在栈空间中。真正创建对象的语句是new Person(1),这个对象放在堆空间中,对应栈空间p中存放的是这个Person对象在堆空间中的地址。其中a是成员变量,存放在堆空间中的。

若声明了一个int类型的非成员变量a,然后对a进行赋值为1,因为a为基础数据类型直接保存在栈空间中,因此栈空间中a对应存储的就是1。

注意

引用《java编程思想(第四版)》P23页的一句话:

对于这些基本类型,java采取与C和C++相同的方法,也就是说,不用new来创建变量,而是创建一个并非是引用的“自动”变量。这个变量直接存储“值”,并置于堆栈中,因此更加高效。

 

明白了上面关于对象和变量的储存特点,现在我们来说说什么是java值传递。

 值传递

2.1 什么是值传递?

按值传递指的是在方法调用时,传递的参数是把值的拷贝传递过去的。由于传递的是值的拷贝,因此按值传递的特点就是传递过后两个参数互不相干。

示例如下:

public class Test {
	public static void main(String[] args) {
		Test test=new Test();
		int a=1;
		test.sum(a);
		System.out.println("main方法里的a="+a);
	}
public void sum(int a){
		a=2;
		System.out.println("sum方法里的a="+a);
	}
}


运行结果:

sum方法里的a=2
main方法里的a=1


2.2 java值传递过程分析

(1)当运行到main方法第2行代码时:

首先在栈空间中创建一个变量为a,然后将1存入变量a所在内存空间中。

(2)当运行到sum方法中时:

main方法将a变量的拷贝传给sum方法,此时在栈内存中会为sum方法中的变量a开辟一块空间,存入由main方法传过来的值1。通过a=2操作过后将sum方法中a的值改变成2。因此sum方法中的变量a更改不会影响main方法中的变量a。

 

图(1)

引用传递

3.1  什么是引用传递?

引用传递是C语言里面的一种参数传递方式,就是在方法调用时,传递给另一个方法的是对象的引用,是同一个引用。但是在java语言中,并不存在像C语言这样的引用传递,java语言中只存在值传递。之所以很多人把java传递对象误以为是引用传递,是因为没有深入的理解对象创建的内存分配规则。在java中传递对象,其实是传递的对象引用的值的拷贝,这个值也就是对象在堆内存中的地址。

Java中对象引用值的传递的特点是:两个引用指向的是同一个对象,即同一个内存空间(试想,如果是引用传递,则就不会存在两个引用了,只会有一个引用)。

 

示例代码如下:

public class Test {
	public static void main(String[] args) {
		Test test=new Test();
		Person p;
		p=new Person();
		p.setAge(5);
		test.handle(p);
		System.out.println("main方法里:"+p.toString());
	}
	public void handle(Person p){
		p.setAge(10);
		System.out.println("handle方法里:"+p.toString());
	}
}

class Person{
	int age=0;
	public void setAge(int age) {
		this.age = age;
	}
	@Override
	public String toString() {
		return "Person [age=" + age + "]";
	}
}

 

运行结果:

handle方法里:Person [age=10]
main方法里:Person [age=10]


3.2  Java“引用传递”过程分析

(1)当运行完main方法第4行时:

此时程序在栈空间中开辟了一块空间存放Person的一个引用p,并且在堆内存里面创建了一个对象。

 

图(2)

(2)当运行完main方法第5行的时:

此时main方法将引用p地址值的一个拷贝传递给handle方法,handl方法首先创建一个新的引用p,并且将传过来的地址值赋值给新引用p。此时两个引用都指向同一个内存空间。

 

图(3)

(3)当运行完handle方法的第1行时:

此时handle方法中的引用p,修改了地址值为0000的Person对象的age=10,由于两个引用指向的是同一个对象,因此main方法中Person对象的age也等于10,因为是同一个Person对象。

 

图(4)

 

假如将handle方法修改成如下代码:

 

public void handle(Person p){
	p=new Person();
	p.setAge(10);
	System.out.println("handle方法里:"+p.toString());
}


运行结果为:

handle方法里:Person [age=10]
main方法里:Person [age=5]


分析过程为:

运行到新handle方法第一行之前,运行过程和上面(1)(2)一样,只不过当运行完第1行后放生了点变化。handle中重新new了一个Person对象,并且将handle方法中的引用p指向这个对象。

 

图(5)

运行完handle方法第2行代码后,handle里面的引用p修改了Person对象的age,此时由于main方法和handle方法中的引用指向了各自的Person对象,因此此次修改操作不会影响main方法中的Person对象,因此:main方法里:Person [age=5]。

 

图(6)

26.4 总结

(1)Java当中参数传递都是值传递!

(2)对于这些基本类型,java采取与C和C++相同的方法,也就是说,不用new来创建变量,而是创建一个并非是引用的“自动”变量。这个变量直接存储“值”,并置于堆栈中,因此更加高效。

(3)对于大家理解的引用传递,其实传递的是引用的值,即传递引用对象的地址值。



注意:如有问题请批评指正!



【四川乐山程序员联盟,欢迎大家加群相互交流学习5 7 1 8 1 4 7 4 3】

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值