关于Java值传递和引用传递之间的困扰
前言
最近由于要拓宽自己的技能和学习有关Java的课程需要学习Java,便开始了自学Java的过程,但是学习过程中往往会有一些难以解决的问题。今天出现的这个问题就是我迈入Java大门首次要击败的一个小boss。
Java有很多和C++类似的部分,比如new就是引用C++的关键字。在《Java核心技术》中有很多C++的辨析部分供读者参考。我也是本书受益者之一,但是由于曾经接触过别的一些类似的编程语言,所以我在学习的过程中出现了很多惯性思维导致理解错误的部分。
注:
问题的出现
在引用参数的过程中,我写了交换对象的方法,但是在运行后发现对象并没有被交换。
public static void cgobj(demo st1,demo st2) {
demo swap=new demo();
swap=st1;
st1=st2;
st2=swap;
在main中我写下了如下内容后对象被交换
demo swap=new demo();
swap = stu[1];
stu[1]=stu[2];
stu[2]=swap;
便产生了很大的疑问,为什么对象没有被交换
两种参数的传递方式
有过编程基础的人应该知道参数传递的两种方式
-
值传递
-
引用传递
由于Java没有引入指针这一概念,我们首先得知道两种传递是什么
值传递
值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
引用传递
函数参数的传递方式有引用传递。所谓引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
用C语言解释值传递和引用传递
形参为形式参数,实参为实际参数,为了简写这一概念,下文我将会用形参和实参代替。
如果在C语言中,那么很好理解这一概念
#include<studio.h>
void swap(int x,int y)
{
int t;
t=x;
x=y;
y=t;
}
int main()
{
int a=20,b=25;
printf("a=%d,b=%d",a,b);
swap(a,b);
printf("a=%d,b=%d",a,b);
return 0;
}
程序结果:
a=20,b=25
a=20,b=25
参数的传递过程是将实参的”值“传递给形参,因此参数的传递是单向传递。其本质是在函数调用发生时,系统给形参x,y开辟内存,然后将实参的”值“复制一份给了形参,随后结束参数的传递过程。在参数传递结束后,实参a,b和形参x,y之间没有关系,所以当被调用函数修改x,y的值时,不会对主调函数中的实参产生任何影响。所以变量a和b并没有发生实际上的交换,这便是所谓的值传递。
我们再来看一组例子
#include<studio.h>
void swap(int *x,int *y)
{
int t;
t=*x;
*x=*y;
*y=t;
}
int main()
{
int a=20,b=25;
int *pa=&a,*pb=&b;
printf("a=%d,b=%d",a,b);
swap(pa,pb);
printf("a=%d,b=%d",a,b);
return 0;
}
程序结果:
a=20,b=25
a=25,b=20
将形参修改指针变量后,主函数中的变量a和b发生了交换,这一交换显然是swap函数来完成的。
先来分析参数传递。由于实参pa和pb分别存放的是a和b的内存地址,因此pa和pa分别是a和b的间接引用形式。形参指针变量分别收到的是pa和pb中存放的内存地址。
然后再来看交换过程。x和y便指向的是a和b在内存中的地址,x和y通过内存地址间接访问的方式修改了a和b变量中存放的值,所以直接影响到了实际参数。
Java中的值传递
在Java中的数据类型分为两种,一种为基本类型,另一种为引用类型。基本类型的传递方式是值传递这点我相信大家都是毋庸置疑的。
Java中的基本类型
- byte
- short
- int
- long
- float
- double
- boolean
- char
参数的传递过程是将实参的”值“传递给形参,因此参数的传递是单向传递。形参并不会影响到实参。
引用类型
我们先来看一组例子
package demo;
public class demo {
private String name;
public demo(String pubname) {
this.name=pubname;
}
public String getName() {
return name;
}
public static void swapObj(demo swapObj1,demo swapObj2) {
demo swap=swapObj1;
swapObj1=swapObj2;
swapObj2=swap;
}
public static void main(String[] args) {
demo[] stu=new demo[3];
stu[0]=new demo("Tusuki");
stu[1]=new demo("Tom");
stu[2]=new demo("Jarry");
for(int i=0;i<3;i++) {
System.out.println(stu[i].getName());
}
System.out.println("————————————————————");
demo swap=stu[1];
stu[1]=stu[2];
stu[2]=swap;
for(int i=0;i<3;i++) {
System.out.println(stu[i].getName());
}
}
}
程序运行结果:
Tusuki
Tom
Jarry————————————————————
Tusuki
Jarry
Tom
我们看到Tom和Jarry对象输出位置交换了,仔细读代码的同学会发现demo有一个swapObject的方法,我们试着使用swapObject方法来交换试试
package demo;
public class demo {
private String name;
public demo(String pubname) {
this.name=pubname;
}
public String getName() {
return name;
}
public static void swapObj(demo swapObj1,demo swapObj2) {
demo swap=swapObj1;
swapObj1=swapObj2;
swapObj2=swap;
}
public static void main(String[] args) {
demo[] stu=new demo[3];
stu[0]=new demo("Tusuki");
stu[1]=new demo("Tom");
stu[2]=new demo("Jarry");
for(int i=0;i<3;i++) {
System.out.println(stu[i].getName());
}
System.out.println("————————————————————");
demo.swapObj(stu[1], stu[2]);
for(int i=0;i<3;i++) {
System.out.println(stu[i].getName());
}
}
}
程序运行结果:
Tusuki
Tom
Jarry————————————————————
Tusuki
Tom
Jarry
为什么会这样呢?对象没有发生交换,仔细阅读代码,代码也没有存在任何问题。
先来分析这里
demo[] stu=new demo[3];
stu[0]=new demo("Tusuki");
stu[1]=new demo("Tom");
stu[2]=new demo("Jary");
创建一个demo类的对象数组stu
使用demo的构建函数分别赋值。
这里没有任何问题,我们创建了三个对象
demo swap=stu[1];
stu[1]=stu[2];
stu[2]=swap;
这时候我们要分清楚一个概念,对象和对象引用
如果用惯性思维去思考这段代码的话,我们会认为
- 创建一个swap数组,把stu[1]赋给swap
- stu[2]赋给stu[1]
- swap赋给stu[2]
- 完成对象的交换
这样的思维是错误的,并没有直接改变内存中对象的值,只是对象引用的改变。虽然输出结果中看到对象交换了,但在堆中,对象并没有发生任何改变。
对象和对象引用
首先得理解什么是对象,什么又是对象的引用
我先举一个创建对象的例子
demo tsuki = new demo("Tsuki");
创建一个demo类的对象Tsuki,并使用demo类构建方法
我分成两个语句来解释这个比较友好
demo tsuki;
tsuki = new demo("Tsuki");
这样应该比较容易理解,这里有两个实例,一个是对象引用变量,另一个是对象本身。
在堆空间里创建实例,与在数据段以及栈空间里创建的实例不同。一个类可以创建出无数个对象,这些对象都有各自不同的引用变量。对象连名都没有,没法直接访问它。我们只能通过对象引用来间接访问对象。这时候可能有点疑惑,tsuki不是对象的名字吗?tsuki是对象的引用而不是对象的实例,对象存放在堆空间中需要通过对象的引用来使用对象。说道这里应该有点恍然大悟的感觉,我们再来看这段代码
demo swap=stu[1];
stu[1]=stu[2];
stu[2]=swap;
这里并不是把对象的实例交换了而是仅仅交换对象的引用,我们用指针的思维去思考这个问题应该很容易理解,把stu对象数组和swap看成是指针,会很容易理解这个问题。
我们再重新来看这段代码
public static void swapObj(demo swapObj1,demo swapObj2) {
demo swap=swapObj1;
swapObj1=swapObj2;
swapObj2=swap;
}
这里是把对象引用变量的“值”传递给了形参,实参并没有发生任何实际的变化,形参是对象引用变量并不是对象本身,形参是通过对象引用来间接访问对象。在本例中,实参变量值是对象的引用,形参是实参值传递后的引用,形参发生了改变但是实参并没有发生改变。
再来看个例子就可以很容易理解这个概念
package demo;
public class demo {
private String name;
public demo(String pubname) {
this.name=pubname;
}
public String getName() {
return name;
}
public static void swapObj(demo [] swapObject) {
demo swap=swapObject[1];
swapObject[1]=swapObject[2];
swapObject[2]=swap;
for(int i=0;i<3;i++) {
System.out.println(swapObject[i].getName());
}
System.out.println("————————————————————");
}
public static void main(String[] args) {
demo[] stu=new demo[3];
stu[0]=new demo("Tusuki");
stu[1]=new demo("Tom");
stu[2]=new demo("Jarry");
for(int i=0;i<3;i++) {
System.out.println(stu[i].getName());
}
System.out.println("————————————————————");
// demo swap=stu[1];
// stu[1]=stu[2];
// stu[2]=swap;
demo.swapObj(stu);
for(int i=0;i<3;i++) {
System.out.println(stu[i].getName());
}
}
}
程序运行结果:
Tusuki
Tom
Jarry
————————————————————
Tusuki
Jarry
Tom
————————————————————
Tusuki
Jarry
Tom
通过这个例子发现在方法内输出的内容发生了改变,而在main方法内没有发生任何改变。
我们再来修改一下swap的名字试试stu[1]会不会发生变化
package demo;
public class demo {
private String name;
public demo(String pubname) {
this.name=pubname;
}
public String getName() {
return name;
}
public static void swapObj(demo [] swapObject) {
demo swap=swapObject[1];
swapObject[1]=swapObject[2];
swapObject[2]=swap;
for(int i=0;i<3;i++) {
System.out.println(swapObject[i].getName());
}
System.out.println("————————————————————");
}
public static void main(String[] args) {
demo[] stu=new demo[3];
stu[0]=new demo("Tusuki");
stu[1]=new demo("Tom");
stu[2]=new demo("Jarry");
for(int i=0;i<3;i++) {
System.out.println(stu[i].getName());
}
System.out.println("————————————————————");
// demo swap=stu[1];
// stu[1]=stu[2];
// stu[2]=swap;
demo.swapObj(stu);
for(int i=0;i<3;i++) {
System.out.println(stu[i].getName());
}
}
}
程序运行结果:
Tusuki
Tom
Jarry
————————————————————
Tusuki
Jarry
newName
看到这里我相信已经理解Java中的值传递和引用传递了