Java中变量的传值和传引用
1.Java中变量的基本类型
为了了解Java中参数的传值和传引用,我们需要先知道Java中参数的基本类型,Java中数据类型分为两大类,基本类型和对象类型。相应的,变量也有两种类型:基本类型和引用类型。
基本类型的变量保存原始值,即它代表的值就是数值本身;而引用类型的变量保存引用值,"引用值"指向内存空间的地址,代表了某个对象的引用,而不是对象本身,对象本身存放在这个引用值所表示的地址的位置。
基本类型包括:byte,short,int,long,char,float,double,Boolean,returnAddress,
引用类型包括:类类型,接口类型和数组。
相应的,变量也有两种类型:基本类型和引用类型。
方法的参数分为实际参数,和形式参数。
形式参数:定义方法时写的参数。
实际参数:调用方法时写的具体数值。
一般情况下,在数据做为参数传递的时候,基本数据类型是值传递,引用数据类型是引用传递(地址传递)。
2.传值和传引用的区别
在了解传值和传引用之前,我们需要先了解Java内存区域的划分。如图在这个数据区中,它由以下几部分组成:
1 . 虚拟机栈2. 堆3. 程序计数器4. 方法区5. 本地方法栈
堆、栈、静态方法区、常量区相应地,每个存储区域都有自己的内存分配策略:堆式、栈式、静态
前面已经介绍过形参和实参,也介绍了数据类型以及数据在内存中的存储形式,接下来,就是文章的主题:值传递和引用的传递。
值传递
值传递:
在方法被调用时,实参通过形参把它的内容副本传入方法内部,此时形参接收到的内容是实参值的一个拷贝,因此在方法内对形参的任何操作,都仅仅是对这个副本的操作,不影响原始值的内容。
1public static void valueCrossTest(int age,float weight){
2 System.out.println("传入的age:"+age);
3 System.out.println("传入的weight:"+weight);
4 age=33;
5 weight=89.5f;
6 System.out.println("方法内重新赋值后的age:"+age);
7 System.out.println("方法内重新赋值后的weight:"+weight);
8 }
9
10//测试
11public static void main(String[] args) {
12 int a=25;
13 float w=77.5f;
14 valueCrossTest(a,w);
15 System.out.println("方法执行后的age:"+a);
16 System.out.println("方法执行后的weight:"+w);
17}
结果
1传入的age:25
2传入的weight:77.5
3
4方法内重新赋值后的age:33
5方法内重新赋值后的weight:89.5
6
7方法执行后的age:25
8方法执行后的weight:77.5
例子中
int a=10;中的a在被调用之前就已经创建并初始化,在调用func方法时,他被当做参数传入,所以这个a是实参。
而func(int a)中的a只有在func被调用时才开始,而在func调用结束之后,它也随之被释放掉,,所以这个a是形参。
引用传递
引用传递:
”引用”也就是指向真实内容的地址值,在方法调用时,实参的地址通过方法调用被传递给相应的形参,在方法体内,形参和实参指向通愉快内存地址,对形参的操作会影响的真实内容。
举个关于马云和马化腾的例子
public class Person {
private String name;
private int age;
public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} public static void PersonCrossTest(Person person) {
System.out.println("传入的person的name:" + person.getName());
person.setName("我是马云");
System.out.println("方法内重新赋值后的name:" + person.getName());
} // 测试
public static void main(String[] args) {
Person p = new Person();
p.setName("我是马化腾");
p.setAge(45);
PersonCrossTest(p);
System.out.println("方法执行后的name:" + p.getName());
}
}
输出结果
传入的person的name:我是马化腾
方法内重新赋值后的name:我是马云
方法执行后的name:我是马云
可以看出,person经过personCrossTest()方法的执行之后,内容发生了改变,这印证了上面所说的“引用传递”,对形参的操作,改变了实际对象的内容。
下面我们对上面的例子稍作修改,加上一行代码
public static void PersonCrossTest(Person person) {
System.out.println("传入的person的name:" + person.getName());
person=new Person();//加多此行代码
person.setName("我是马云");
System.out.println("方法内重新赋值后的name:" + person.getName());
}
结果就变成了
传入的person的name:我是马化腾
方法内重新赋值后的name:我是马云
方法执行后的name:我是马化腾
为什么这次的输出和上次的不一样了呢?
因为当执行到PersonCrossTest()方法时,因为方法内有这么一行代码:
1person=new Person();
此时堆内另外开辟一块内存来存储new Person(),假如真的是引用传递,那么由上面讲到:引用传递中形参实参指向同一个对象,形参的操作会改变实参对象的改变。可以推出:实参也应该指向了新创建的person对象的地址,所以在执行PersonCrossTest()结束之后,最终输出的应该是后面创建的对象内容。然而实际上,最终的输出结果却跟我们推测的不一样,最终输出的仍然是一开始创建的对象的内容。由此可见:引用传递,在Java中并不存在。但是有人会疑问:为什么第一个例子中,在方法内修改了形参的内容,会导致原始对象的内容发生改变呢?这是因为:无论是基本类型和是引用类型,在实参传入形参时,都是值传递,也就是说传递的都是一个副本,而不是内容本身。
3.总结
结合上面的分析,关于值传递和引用传递可以得出这样的结论:
(1)基本数据类型传值,对形参的修改不会影响实参;
(2)引用类型传引用,形参和实参指向同一个内存地址(同一个对象),所以对参数的修改会影响到实际的对象;
(3)String, Integer, Double等immutable的类型特殊处理,可以理解为传值,最后的操作不会修改实参对象。