深入理解对象的创建
我们已经知道了类与对象的关系,我们来试验下看看具体效果如何:
案例一:
创建对象之前,我们要先创建类,代码如下:
public class Student {
String name;
int age;
char gender;
public void intro(){
System.out.println("大家好,我是"+name+",性别"+gender+"今年"+age+"岁了,");
}
}
我们再来创建和使用对象,代码如下:
//创建对象
Student stu1 = new Student();
Student stu2 = new Student();
Student stu3 = new Student();
//对象属性赋值
stu1.name = "熊大";
stu1.gender = '男';
stu1.age = 22;
stu2.name = "熊二";
stu2.gender = '女';
stu2.age = 23;
//对象调用方法
stu1.intro();
stu2.intro();
stu3.intro();
运行效果如下:
大家好,我是熊大,性别男今年22岁了,
大家好,我是熊二,性别女今年23岁了,
大家好,我是null,性别 今年0岁了,
可以看出,
1.不同对象的属性不同,调用各自方法的结果也不同。
2.如果没有赋值对象属性,会默认赋值与属性数据类型相同的数据。引用类型数据默认是null,字符类型数据默认为' '(一个空格),整数型数据默认为0。
案例二:
我们沿用案例一的Student类,改变对象的使用方式,代码如下:
//创建对象stu1
Student stu1 = new Student();
//stu1对象属性赋值
stu1.name = "熊大";
stu1.gender = '男';
stu1.age = 22;
//创建Student类型变量stu2,将stu1的引用给stu2
Student stu2 = stu1;
//stu2对象属性赋值
stu2.name = "熊二";
stu2.gender = '女';
stu2.age = 23;
//对象调用方法
stu1.intro();
stu2.intro();
不要着急看实际结果,可以先猜想一下运行的结果
如果你认为得到的结果和案例一中stu1对象和stu2对象调用方法的结果一致,那么很遗憾,你答错了。
实际运行结果如下:
大家好,我是熊二,性别女今年23岁了,
大家好,我是熊二,性别女今年23岁了,
答错的不要灰心,答对的不要骄傲,且听我慢慢道来。
我们需要从java虚拟机创建对象的内存分配开始说起...
内存分区的介绍
JVM的内存结构:
-
栈(Stack)
- 栈与线程的关系:
Java中一个线程一个栈区,每一个栈中的元素都是私有的,不被其他栈所访问。
- 栈的储存特点:
后进先出。
- 栈储存的缺点:
大小和生存期都是确定的,缺乏灵活性。
- 栈储存的优点:
储存速度快,仅次于寄存器。
- 栈存储速度快的原因:
通过栈指针的移动创建和释放空间。
但也因为栈指针移动储存的特点,限制了移动的大小和范围,进而影响了程序的灵活性。所以JVM将更大容量的数据储存在堆中。
- 栈中存放的数据种类:
基本数据类型的数据、引用数据类型的引用。
例如:int a = 10; Student stu1 = new Student();值10、对象的引用(stu1)储存在栈内存中
-
堆(heap)
- 堆储存较栈储存的优缺点:
开辟空间和生存期不受约束,但储存速度较慢。
- 堆中存放的数据种类:
类的对象,通过new创建出的对象。
- 堆内存的释放原理:
当堆内存中不存在某对象的引用时,通过垃圾回收机制(GC)完成内存空间的释放。
-
方法区(method area)
- 方法区存放的数据类型:
1.类信息
2.静态的变量
3.常量
4.成员方法方法区中包含一个特殊区域——常量池(储存的是static静态修饰符修饰的成员变量)。
-
寄存器(program counter register)
- 寄存器存放的数据种类:
当前正在执行的JVM指令地址。
java程序中,每个线程启动时,都会创建一个寄存器。 -
本地方法栈(native method stack)
- 本地方法栈存放的数据种类:
本地方法的地址。
了解完这些内存分区的知识之后,我们回到案例二,从内存的角度分析代码的执行。
案例二的内存分析
首先,Student的类会被加载到方法区,结果如图所示:

其次,新建的对象引用会被加载到栈中、对象则在堆中创建,堆中对象的内存地址赋值给了栈中的变量、栈中的对象引用指向堆中的对象(0x1234是假设的内存地址)。结果如图所示:

注意上图中对象的属性均为默认值,接下来是对stu1对象属性的赋值,结果如图所示:

接着定义Student类型的变量stu2,并将stu1的引用赋值给stu2。
之前做错的小伙伴注意了哈,是把内存地址赋值给stu2。
内存地址相同的两个对象会发生什么呢?结果如图所示:

所以,当对stu2对象属性赋值时,其实在对堆中储存的stu1的对象属性赋值,这会覆盖掉原有的属性。结果如图所示:

这也就解释了为什么调用两个不同的对象,却得到相同结果的原因,因为变量引用的堆中数据是相同的。
案例三
觉得学的差不多了?我们继续沿用案例一的类,我们再来看另一段使用对象的代码:
//创建对象stu1
Student stu1 = new Student();
//stu1对象属性赋值
stu1.name = "熊大";
stu1.gender = '男';
stu1.age = 22;
//创建对象stu2
Student stu2 = new Student();
//将stu1的引用给stu2
stu2 = stu1;
//stu2对象属性赋值
stu2.name = "熊二";
stu2.gender = '女';
stu2.age = 23;
//对象调用方法
stu1.intro();
stu2.intro();
什么,这和案例二一样?再仔细看看!相信眼尖的小伙伴已经看出来端倪了,区别就在于多了这行代码:
//创建对象stu2
Student stu2 = new Student();
小伙伴们不妨再猜一猜运行的结果,这里先卖个关子,先讲解,最后再揭晓答案!
与案例二相同的我不再赘述,这里我从直接从两者不同的地方开始。
栈中开辟空间创建变量stu2,堆中开辟空间创建对象stu2,并把内存地址赋值给栈中的变量stu2,栈中的引用指向堆中的数据。结果如图所示:

随后将stu1的内存地址赋值给stu2,栈中的stu2变量引用指向堆中的stu1对象,堆中的stu2对象失去引用,被当作垃圾回收,结果如图所示:

对stu2的属性赋值就相当于对堆中的stu1对象属性赋值,结果如图所示:

因此,案例三的结果水落石出,看看小伙伴们答对了没有:
大家好,我是熊二,性别女今年23岁了,
大家好,我是熊二,性别女今年23岁了,
实际结果和案例二一模一样呢,但是其中的过程可是大不相同呢!
欢迎小伙伴们的留言~
浙公网安备 33010602011771号