为了了解Java中参数的传值和传引用,我们需要先知道Java中参数的基本类型,Java中数据类型分为两大类,基本类型和对象类型。相应的,变量也有两种类型:基本类型和引用类型。 基本类型的变量保存原始值,即它代表的值就是数值本身;而引用类型的变量保存引用值,"引用值"指向内存空间的地址,代表了某个对象的引用,而不是对象本身,对象本身存放在这个引用值所表示的地址的位置。 基本类型包括:byte,short,int,long,char,float,double,Boolean,returnAddress, 引用类型包括:类类型,接口类型和数组。 相应的,变量也有两种类型:基本类型和引用类型。 方法的参数分为实际参数,和形式参数。
形式参数:定义方法时写的参数。 实际参数:调用方法时写的具体数值。
一般情况下,在数据做为参数传递的时候,基本数据类型是值传递,引用数据类型是引用传递(地址传递)。
在了解传值和传引用之前,我们需要先了解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中并不存在。但是有人会疑问:为什么第一个例子中,在方法内修改了形参的内容,会导致原始对象的内容发生改变呢?这是因为:无论是基本类型和是引用类型,在实参传入形参时,都是值传递,也就是说传递的都是一个副本,而不是内容本身。
结合上面的分析,关于值传递和引用传递可以得出这样的结论: (1)基本数据类型传值,对形参的修改不会影响实参; (2)引用类型传引用,形参和实参指向同一个内存地址(同一个对象),所以对参数的修改会影响到实际的对象; (3)String, Integer, Double等immutable的类型特殊处理,可以理解为传值,最后的操作不会修改实参对象。