package Test;
public class Point {
private int x;
private int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public void setLocation(int x, int y) {
this.x = x;
this.y = y;
}
private static void modifyPoint(Point p1, Point p2) {
Point tmpPoint = p1;
p1 = p2;
p2 = tmpPoint;
p1.setLocation(5, 5);
p2 = new Point(5, 5);
}
public static void main(String[] args) {
Point p1 = new Point(0, 0);
Point p2 = new Point(0, 0);
modifyPoint(p1, p2);
System.out.println("[" + p1.x + "," + p1.y + "]" + "[" + p2.x + "," + p2.y + "]");
}
}
输出结果:
[0,0][5,5]
上面题目main函数的执行步骤为:
1.在堆内存中创建两个Point对象,分别指向了栈内存中的p1,p2.
2.执行modifyPoint方法.
3.在modifyPoint方法中,Point tmpPoint = p1; p1 = p2; p2 = tmpPoint;
是对传进来的p1,p2两个引用的交换.
4.再执行p1.setLocation(5, 5);
此时已经是p2这个引用在调用setLocation
方法,所以main函数中的对象p2会将设定值 (0,0) 换为 (5,5).
5.继续执行p2 = new Point(5, 5);
要知道一个很重要的点:
当对象的引用被执行赋值操作(=)后, 将不再指向原来的对象(新的对象不是原来的对象)
所以这一步执行完之后,main中的p1对象并没有被改变,此时modifyPoint方法中的p2和main方法中的p1并不是指向同一片堆内存的Point对象.
6.最后执行System.err.println("[" + p1.x + "," + p1.y + "]" + "[" + p2.x + "," + p2.y + "]");
会输出[0,0][5,5]
.
那么当对象的引用被执行赋值操作(=)后,是不是不再指向原来的对象,我们可以来观察hashcode值
代码如下
package Test;
public class Point {
public static void main(String[] args) {
Point p1 = new Point(0, 0);
Point p2 = new Point(0, 0);
System.out.println("main中的p1"+p1);
System.out.println("main中的p2"+p2);
modifyPoint(p1, p2);
System.out.println("[" + p1.x + "," + p1.y + "]" + "[" + p2.x + "," + p2.y + "]");
}
private int x;
private int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public void setLocation(int x, int y) {
this.x = x;
this.y = y;
}
private static void modifyPoint(Point p1, Point p2) {
Point tmpPoint = p1;
p1 = p2;
p2 = tmpPoint;
p1.setLocation(5, 5);
p2 = new Point(5, 5);
System.out.println("modifyPoint中的p1"+p1);
System.out.println("modifyPoint中的p2"+p2);
}
}
输出结果:
main中的p1Test.Point@15db9742
main中的p2Test.Point@6d06d69c
modifyPoint中的p1Test.Point@6d06d69c
modifyPoint中的p2Test.Point@7852e922
[0,0][5,5]
/**
* 可以使用断点调试观察modify方法中p2的指向变化
* Eclipse中引用被赋予新的值时, 调试界面中[变量列表]的变化为 黄色高亮显示
*/
注意不要重写这个类的hashcode方法!!!那么这个类就会继承父类object类的hashcode方法!而Object的hashcode方法默认返回的是内存地址.
从上面可以看出main中的p1Test.Point@15db9742
和modifyPoint中的p2Test.Point@7852e922
hashcode值不相等,那么这俩对象的内存地址一定不相等,也就是说他俩并不指向同一块地址值.
所以当对象的引用被执行赋值操作(=)后, 将不再指向原来的对象(新的对象不是原来的对象)!!!
总结
Java引用传递和值传递其实主要是看参数列表声明中参数名的属性
引用的意义是起个别名, 其本身指的就是实际传入的那个参数地址, 这和 c++ 中指针功能是相近的.
而按照值传递,是传递一个参数副本进去,也就说这个副本必是非地址型的变量.
如果参数以 8大基本数据类型( byte short int long float double boolean char) 定义的, 则为按值传递,如果参数以类名或某个引用类型的变量定义的则为按引用传递.
基本数据类型, 变量的存储方式是比较精简的,而对于复杂数据类型, 诸如 class 定义的类的对象, 那么对象名所指的虽然是该对象存储的首地址, 而其存储方式上则相对复杂庞大. 而函数本身的参数列表基于存储空间的限制, 是不支持复杂的数据结构这种存储方式(因为他太浪费空间了), 那么作为复杂数据类型的传递, 只能以地址也就是引用的方式存在.
注意:
基本数据类型的值传递,不改变其值;而引用数据类型的值传递,改变其值
所以p1.setLocation(5, 5);
改变了原值.
还有一点要注意
将程序改为下面这样
package Test;
public class Point {
public static void main(String[] args) {
Point p1 = new Point(1, 0);
Point p2 = new Point(2, 0);
modifyPoint(p1, p2);
System.out.println("[" + p1.x + "," + p1.y + "]" + "[" + p2.x + "," + p2.y + "]");
}
private int x;
private int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public void setLocation(int x, int y) {
this.x = x;
this.y = y;
}
private static void modifyPoint(Point p1, Point p2) {
Point tmpPoint = p1;
p1 = p2;
p2 = tmpPoint;
}
}
输出结果:
[1,0][2,0]
输出结果却为[1,0][2,0]
而不是[2,0][1,0]
!
我们发现前后p1,p2的值并没有发生变化,也就是方法并没有改变存储在变量p1和p2中的对象引用。modifyPoint方法的参数p1和p2被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝的值而已,最终,所做的事都是白费力气罢了。在方法结束后p1和p2将被丢弃,而原来的变量p1和p2仍然引用这个方法调用之前所引用的对象.
这个过程也充分说明了java程序设计语言对对象采用的不是引用调用,实际上是对象引用进行的是值传递,当然在这里我们可以简单理解为这就是按值调用和引用调用的区别,而且必须明白即使java函数在传递引用数据类型时,也只是拷贝了引用的值罢了,之所以能修改引用数据是因为它们同时指向了一个对象,但这仍然是按值调用而不是引用调用.