在面试或是笔试过程当中,关于java函数参数传递相关内容是常考题,而且在实际开发中若对参数传递不深入理解,也会出现很严重的错误甚至是灾难性的错误。在这篇文章中我想和大家分享下我个人对参数传递浅显的理解,先看下面的程序:
public class TestRef {
public static void clear(Person p) {
p = null;
}
public static void init(Person p) {
if (null == p) {
p = new Person();
}
p.setName("james");
p.setAge(24);
}
public static void newPerson(Person p) {
p = new Person();
p.setName("new person");
System.out.println("In newPerson, HashCode = " + p.hashCode() + ", name = " + p.getName());
}
public static void modifyPerson(Person p) {
// 这里就不做null判断了
p.setName("modify person");
System.out.println("In modifyValue, HashCode = " + p.hashCode() + ", value = " + p.getName());
}
public static void main(String[] args) {
Person person = new Person();
System.out.println("调用clear方法前:" + person.toString());
clear(person);
if (null != person) {
System.out.println("调用clear方法后person不为空");
}
System.out.println("调用init方法前:" + person.toString());
init(person);
System.out.println("调用init方法后:" + person.toString());
System.out.println("////////////////////////////////////////////////////");
Person person2 = new Person();
System.out.println("Before newPerson, HashCode = " + person2.hashCode() + ", name = " + person2.getName());
newPerson(person2);
System.out.println("After newPerson, HashCode = " + person2.hashCode() + ", name = " + person2.getName());
System.out.println("////////////////////////////////////////////////////");
Person person3 = new Person();
System.out.println("Before modify, HashCode = " + person3.hashCode() + ", name = " + person3.getName());
modifyPerson(person3);
System.out.println("After modify, HashCode = " + person3.hashCode() + ", name = " + person3.getName());
}
}
class Person {
private String name = "";
private int age = 0;
public Person() {
// TODO Auto-generated constructor stub
}
public Person(Person person) {
this.name = person.name;
this.age = person.age;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return "name:" + this.name + "\t" + "age:" + this.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;
}
}
调用clear方法前:name: age:0
调用clear方法后person不为空
调用init方法前:name: age:0
调用init方法后:name:james age:24
////////////////////////////////////////////////////
Before newPerson, HashCode = 12677476, name =
In newPerson, HashCode = 33263331, name = new person
After newPerson, HashCode = 12677476, name =
////////////////////////////////////////////////////
Before modify, HashCode = 6413875, name =
In modifyValue, HashCode = 6413875, value = modify person
After modify, HashCode = 6413875, name = modify person
下面以图示的方式做出我个人对java函数参数传递对象的过程的理解:
图1 clear方法调用参数传递过程图
从上图中可以看到在将Person对象传递给函数时其实传的是一个引用,可理解为形参p也指向person变量所指向的堆内存,当在函数中将p = null;时其实时将形参引用指向null;所以此时在函数外的person引用变量仍指向堆内存。所以调用clear方法后person不为空。
下面看一下init(Person p)方法执行过程:
图2 init方法调用参数传递过程图
通过上图不难看到,开始时person指向堆内存中初始化person对象的name为空字符串,age为0,当调用init方法时,将person对象传递给函数,此时形参p同时也指向person所指对象在堆内存中分配的内存空间,此时再修改p所指对象的话自然也将person所指对象做了修改。
下面再来看newPerson方法调用时参数传递情况:
图3 newPerson方法调用参数传递过程图
从上图可以看到,当person2对象做为参数传进来时仍传递的是引用,此时形参p和person2同时指向堆内存中同一块区域,当p = new Person()后,让p重新指向一个新对象person,此时p和person2分别指向堆内存中两块不同区域。此时对p所指对象做的修改不会影响person2所指对象。所以person2和p的hashcode不一样,在函数中没有对person2所指对象做任何修改,而是对一个新对象做了修改。
通过前面的分析很容易画出modifyPerson方法调用时参数传递过程。
图4 modifyPerson调用参数传递过程图
从上图分析看出person3和p同时指向堆内存中同一块区域,即指向同一个对象person,所以p和person3的hashcode值相同。
通过上面对java对象做为函数参数传递时是传引用的理解,我们很容易回答下面的一个笔试题。
笔试题1:
public class Test{
public static void main(String[] args) {
StringBuffer a = new StringBuffer("A");
StringBuffer b = new StringBuffer("B");
operate(a, b);
System.out.println("a=" + a + ", b=" + b);
}
public static void operate(StringBuffer x, StringBuffer y){
x.append(y);
y=x;
}
此题的输出结果是多少?
结果为:a=AB, b=B
分析:调用前a指向一个初始值为A的StringBuilder对象,b指向初始值为B的StringBuilder对象,调用operate方法后形参x和a同时指向初始值为A的StringBuilder对象,形参y指向初始值为B的StringBuilder对象,此时x.append(y);由于x和a同指向同一个对象,所以此时x和a所指对象值变为AB,而y=x;此时让y指向x所指对象即AB,而不再指向初始值为B的StringBuilder对象,这样简单引用与对象的关系简单表示为x,y,a->AB;b->B,所以打印结果为:a=AB,b=B
当然有很多人说上面的传递是值传递,且java中只存在一种传递方式即值传递。当参数为对象时只时复制了一份对象的副本供函数用。另一种说法是基本类型是值传递,而对象是引用传递。
从上面的分析我们可以得到一种得到一种简便方法来得到参数传递前后对象值的变化:
1.若是对参数对象本身的操作(如对对象重新定向,指向一个新对象(new),或是指向null等其它对象),则方法调用前后原对象不变。
2.若是对参数对象内容的更改如属性的修改则方法调用前后原对象值做相应的变化,因为没有对参数对象重定向,而仍与原对象的引用指向同一区域。