本文共 2008 字,大约阅读时间需要 6 分钟。
Java初学者,因为之前学过C,所以对于学Java的时候老是能想起来地址、指针问题,感觉挺纠结。
Java定义实例化的对象时,具体对象数据存在堆中,对象名存在栈中,栈中的对象名可以看做C++中的指针,指向堆中具体的对象数据。并且同一类的两个不同对象之间的赋值,实际上是改变栈中对象名的指向,而不是改变堆中的对象数据,当堆中的对象数据没有对象名指向时,这个数据成为垃圾数据,对应的堆内存由Java的垃圾处理机制(GC)自动回收。
Java的基本类型是没有内存关系的,没有变量名指向变量实体这种说法的,相互赋值用法和C++中变量赋值用法是相同的。有一点区别:C++可以使用函数交换两个变量的值(指针传参),而Java实现相同功能只能用函数返回值(返回数组,保存交换完后的两个数值)
举例三个不同类型的例子说明一下吧:
public class PersonDemo{ public static void main(String args[]){ int a = 10; fun(a); System.out.println(a); } public static void fun (int temp) { temp = 100; }}//输出10基本数据类型没有内存关系,对于fun函数的传递是值传递,fun函数中temp变量指向了新的数值100,而原本a指向的10不变。fun函数执行完毕后,temp与100均为内存垃圾,被GC回收。
public class PersonDemo{ public static void main(String args[]){ String str = "hello"; fun(str); System.out.println(str); } public static void fun (String temp) { temp = "world"; }}//输出hello字符串String类型一旦声明赋值则不可改变,此处fun函数的参数传递为引用传递,str与temp均指向堆内存中的“hello”,但是字符串“world”出现时,是在堆内存中重新开辟一个空间(等价于使用string类的new String("world")),赋值“world”,然后temp指向“world”。fun函数执行完毕后,temp与“world”均为内存垃圾,被GC回收。
class Demo { private String msg; public void setMsg(String msg) { this.msg = msg; } public String getMsg() { return this.msg; }}public class PersonDemo{ public static void main(String args[]){ Demo demo = new Demo(); demo.setMsg("hello"); fun(demo); System.out.println(demo.getMsg()); } public static void fun (Demo temp) { temp.setMsg("world!"); }}//输出world
自定义类的成员对象的内存关系又不太一样:
1、Demo demo = new Demo(); 是在栈内存开辟两个空间,存两个名字 类型名(demo和msg),然后再堆内存开辟空间,存类实体,此处为可以指向msg地址的值。
2、demo.setMsg("hello"); 在堆内存开辟空间,存字符串“hello”,然后栈内存中的msg指向此处的“hello”。
3、fun(demo); 在栈内存开辟空,存类型名 temp,然后将demo的指向赋给temp,即引用传递,此时temp指向的堆内存中的空间与demo所指向的是一样的。
4、temp.setMsg("world!"); 此处“world!”与上面的“hello”相同,需要在堆内存重新开辟空间,然后存“world!”;执行setMsg函数时,在temp所指向的类空间中找到栈空间的msg,然后将msg的指向从“hello”转移至“world!”。fun函数执行完毕后,temp和“hello”将成为内存垃圾。
具体内存变化图如下:
1、java基本类型没有内存关系,变量之间的赋值等效于C、C++;
2、java String类型一旦赋值,则在堆内存中不可改变,只能被GC回收;重新赋值需要开辟新的堆内存空间,改变栈内存中变量名的指向;
3、java 可通过外部函数更改类的成员变量的值(引用传递),具体内存变换见上图。