我们知道在Java中类型可分为两大类:基本类型与引用类型。
引用类型,是指除了基本类型之外的所有类型。所有的类型在内存中都会分配一定的存储空间(形参在使用的时候也会分配存储空间,方法调用完成之后,这块存储空间自动消失), 基本类型只有一块存储空间(分配在stack中), 而引用类型有两块存储空间(一块在stack中,一块在heap中),在函数调用时Java是传值还是传引用,改变一个变量的值,会不会影响其他变量,这个估计很多人至今都很糊涂,下面用图形与代码来解释:
在上图中引用类型在传参时不是在heap中再分配一块内存来存变量c 所指向的A(),而是让a 指向同一个A 的实例,这就与C++ 中的指针一样,先声明指针变量a,b,c,d 在传参的时候让a 指向c所指向的内存,让 d 指向 b 所指向的内存。很明显Java中的引用与C++中的指针在原理上是相类似的,但Java中没有指针,只有引用。
一、基本类型是按值传递的
当Java 方法的参数是基本类型的时候,是按值传递的。我们上代码说明:
public static void main (String[] args) { int a = 99; int num = 100; Integer NUM = 101; change0(num); change0(a); change0(NUM); System.out.println(num); System.out.println(NUM); System.out.println(a); } public static void change0(int a) { a = 1; System.out.println(a); } public static void change0(Integer a) { a = 12; }
运行结果:
1
100
101
99
我们能够看出,虽然在change0 (a) 方法中改变了传进来的参数的值,但对这个参数源变量本身并没有影响,即对 main(String[]) 方法里的 a 变量没有影响。那说明,参数类型是基本类型的时候,是按值传递的。以参数形式传递基本类型的变量时,实际上是将参数的值作了一个拷贝传进方法函数的,那么在方法函数里再怎么改变其值,其结果都是只改变了拷贝的值,而不是源值。
下面再用代码看一下:
public class Loop { public String name; } Loop a = new Loop(); Loop b = a; a.name = "123"; b.name = "345"; //a给name赋值123,后来b使name变为了345,a也会受影响改变值 System.out.println(a.name); //a = null;使得a指向断开,不影响b,若改为a。name则b受影响 a = null; System.out.println(b.name); //前面使得a = null;断开了a的指向,使a在堆中没有初始空间了,无法存放123,会报错 a.name = "123"; System.out.println(a.name);
运行结果:
二、引用类型
首先我们要知道,引用类型的出现是为了节省内存,当我们使用引用类型时,一定要给定一个空间, 即需要new一个对象。
Java 是传值还是传引用,问题主要出在对象的传递上,因为 Java 中基本类型没有引用。既然争论中提到了引用这个东西,为了搞清楚这个问题,我们必须要知道引用是什么。
简单的说,引用其实就像是一个对象的名字或者别名,一个对象在内存中会请求一块空间来保存数据,根据对象的大小,它可能需要占用的空间大小也不等。访问对象的时候,我们不会直接是访问对象在内存中的数据,而是通过引用去访问。引用也是一种数据类型,我们可以把它想象为类似 C++ 语言中指针的东西,它指示了对象在内存中的地址——只不过我们不能够观察到这个地址究竟是什么。
如果我们定义了不止一个引用指向同一个对象,那么这些引用是不相同的,因为引用也是一种数据类型,需要一定的内存空间(栈)来保存。但是它们的值是相同的,都指示同一个对象在内存(堆)的中位置。比如:
String a="This is a Text!"; String b=a;
通过上面的代码和图形示例不难看出,a 和 b 是不同的两个引用,我们使用了两个定义语句来定义它们。但它们的值是一样的,都指向同一个对象 “This is a Text!”。但要注意String 对象的值本身是不可更改的 (像 b = “World”; b = a; 这种情况不是改变了 “World” 这一对象的值,而是改变了它的引用 b 的值使之指向了另一个 String 对象 a)。
如图,开始b 的值为所指向的“word1”,然后 b=a; 使 b 指向了蓝线所指向的”word“。
这里我描述了两个要点:
(1) 引用是一种数据类型(保存在栈中),保存了对象在内存(堆)中的地址,这种类型即不是我们平时所说的基本数据类型也不是类实例(对象);
(2) 不同的引用可能指向同一个对象,换句话说,一个对象可以有多个引用,即该类类型的变量。
三、对象是如何传递的呢
随着学习的深入,你也许会对对象的传递方式产生疑问,即对象究竟是“按值传递”还是“按引用传递”?
下面我们看代码:
public static void main(String[] args) { Te aa = new Te(); aa.name = "100"; Te bb = new Te(); bb.name = "55"; change2(aa,bb); System.out.println(aa.name);//bbb qqqq System.out.println(bb.name);// bbb qqqq } public static void change2(Te a,Te aa) { Te temp = new Te(); temp.name = a.name; a.name = aa.name; aa.name = temp.name; } public class Te { public String name; }
结果:
这里我们能够看到change2(Te a,Te aa)中确实实现了交换,所以它是“按引用传递”的!
我们一定要搞清楚引用传递,否则在日后会出现很多bug的。