java的参数传递一直是个令初学者搞不懂的知识(- - 比如我)。今天决下心来敲代码,研究透彻java的参数传递。
根据所学习的资料,java的参数传递只有一种——值传递。
可能大部分CS专业的学生(再次——比如我)最初都是由接触C/C++开始,因此印象中总会有两种参数传递类型——1.值传递,2.引用传递。
再说说两种传递类型(浅谈C/C++):
1.值传递——值传递时内存会将原来的数据拷贝一份,将拷贝的作为参数进行传递,因此在其他地方对该参数所做的修改并不会影响原来的数据。
2.引用传递——引用传递是将原来数据的地址(java中为引用)直接作为参数进行传递,因此在其他地方对该参数所做的修改相当于修改原数据。
说到这里,就要说说java的特点了——引用,java与C++不同,无指针,java的数据类型分为两种,基本类型和引用类型。但从设计思想的本质看是一样的,都是point的感觉。
java的这个特性很容易使初学者认为java的参数传递就是“引用传递",但其实不是。
举个例子,
<span style="font-size:14px;"> Exp exp = new Exp();</span>
new Exp()实例化一个对象,而exp = new Exp();就让exp引用向这个对象,exp在内存开辟的栈中储存实例化对象的地址。当将exp进行参数传递时,值传递的机制在内存栈区创建了一个栈帧,储存的是exp这个引用的拷贝引用,因此传递后刚才的对象变成了两个引用,在栈中,exp和拷贝值储存相同的地址。
下面我直接就以代码来展开吧~
public class TestCall {
<span style="font-size:14px;"> //类成员-------------------------------</span><pre name="code" class="java"><span style="font-size:14px;"> Book bb = new Book("数据结构", 20);
Book aa = new Book("操作系统", 40);
Book cc = new Book("改变后1", 20);
Book dd = new Book("改变后2", 40);
Book[] book={aa,bb};
Book[] book1 ={cc,dd};</span>
<span style="font-size:14px;"> //内部类------------------------------
public class Book{
private String name;
private double price;
public Book(String name, double price) {
this.name = name;
this.price = price;
}
public void setPrice(double p) {
this.price = p;
}
//覆盖-------------------------------
@Override
public String toString() {
return "[name=" + this.name + ", price=" + this.price + "]";
}
}
//更改对象内容------------------------------
void adjustPrice(Book aBook) {
//aBook.setPrice(100.0); //---改变了---
aBook = new Book("我改了书名",30);//---不改变---
}
void changePrice(Book[] book){
//book[1] = new Book("这次是数组,我改了书名",30);//---改变了---
book[1].setPrice(100.0);//---改变了---
//book = book1;//---不变---
//book[1] = book1[1];//---改变---
}
//更改基本类型内容------------------------------
void adjustBase(int a){
a = 3;
}
void changeBase(int[] a){
a[1] = 3;
}
}</span>
这里我将main方法抽取出来便于编写。
(1)
<span style="font-size:14px;"> public static void main(String[] args){
TestCall t = new TestCall();
//对象------具体数组元素做参数---------------------
System.out.println("前: "+t.book[1]);
t.adjustPrice(t.book[1]);//具体数组元素做参数
System.out.println("后: "+t.book[1]);
System.out.println("");
}</span>
运行结果:
前: [name=数据结构, price=20.0]
后: [name=数据结构, price=20.0]
结论:可以看到,原数据没有收到影响。
(2)
<span style="font-size:14px;">//对象------直接用对象做参数
System.out.println("前: "+t.bb);
t.adjustPrice(t.bb);
System.out.println("后: "+t.bb);
System.out.println("");</span>
运行结果:
前: [name=数据结构, price=20.0]
后: [name=数据结构, price=20.0]
用图解释下:
(3)
<span style="font-size:14px;">//对象数组--------------------------
System.out.println("前: "+t.book[1]);
t.changePrice(t.book);//整个数组的引用做参数
System.out.println("后: "+t.book[1]);
System.out.println("");</span>
运行结果:
前: [name=数据结构, price=20.0]后: [name=数据结构, price=100.0]
结论: 将数组作为参数传递时,发生了改变。
理解: 数组对象实际上也是引用,值传递后内存栈出现两个引用向实例化数组的对象,但book[i]会操作到原对象进而改变原数据。
再加深理解, 若将类成员方法changePrice(Book[])的方法体改为book = book1;
运行结果是:
前: [name=数据结构, price=20.0]
后: [name=数据结构, price=20.0]
可以看到,这次原数据没有发生改变,为什么呢? 其实就是我们将book引用向了另一个数组book1,操纵的不是源对象,因此原数据没有发生改变。
来到这里
让我们将(2)中的adjustPrice方法的方法体改为
aBook.setPrice(100.0);
再运行看看结果:
前: [name=数据结构, price=20.0]
后: [name=数据结构, price=100.0]
结论: 我们惊奇地发现,原来的数据居然变了,其实道理也就和上面一样——因为我们操纵了源对象。
bb对象和值传递时的拷贝值都引用向原来的实例,但aBook.setPrice操纵了源对象,即它们共同引用的对象,因此原数据改变了
用图片来解释下:
(4)
//基本类型-------------------------
int a=1;
System.out.println("a是: "+a);
t.adjustBase(a);
System.out.println("a是: "+a);
System.out.println("");
运行结果:
a是: 1
a是: 1
结论:与单个具体对象一样,不会影响原数据
(5)
//基本类型数组-------------------------
int[] aa={1,2,3,4};
System.out.println("aa[1]是: "+aa[1]);
t.changeBase(aa);
System.out.println("aa[1]是: "+aa[1]);
aa[1]是: 2
aa[1]是: 3
结论:原来数组的值发生了改变,原理如(3)
总结一下:1、本质上,当传递参数为对象时,只要对参数的操作不是对原对象的操作就不会改变原数据的值。
2、当传递参数为基本数据类型时,不会对原数据造成任何影响。
但这里需要强调的是: 若传递的是数组对象(无论是基本类型数据还是对象)而不是具体的单个数组元素,则对传递数据的操作相当于对原数据的操作,即会改变原数据。