友友们,互三啦!!互三必回!!后台踢踢哦~
更多精彩:个人主页
更多java笔记系列:java学习笔记
赛博算命系列:赛博算命
赛博算命之 ”梅花易数“ 的 “JAVA“ 实现 ——从玄学到科学的探索
赛博算卦之周易六十四卦JAVA实现:六幺算尽天下事,梅花化解天下苦。
文章目录
#面向对象的介绍:
面向:拿、找
对象:能干活的东西
面向对象编程:拿东西过来做对应的事情
面向对象编程的例子:
import java.util.Random;
import java.util.Scanner;
public class mian {
public static void main(String[] args) {
//面向对象,导入一个随机数
Random r = new Random();
int data = r.nextInt(10)+1;
//面向对象,输入一个随机数
System.out.println(data);
Scanner sc = new Scanner(System.in);
// 面向对象,输出一个数
System.out.println("请输入一个数:");
int a = sc.nextInt();
System.out.println(a);
}
}
为什么java要采取这种方法来编程呢?
我们在程序之中要干某种事,需要某种工具来完成,这样更符合人类的思维习惯,编程更简单,更好理解。
面向对象的重点学习对象是什么?
学习获取已有对象并使用,学习如何自己设计对象并使用。——面向对象的语法
一、设计对象并使用
1.类和对象
- 类(设计图):是对象共同特征的描述
如何定义类:
public class 类名{
1.成员变量(代表属性,一般是名词)
2.成员方法(代表行为,一般是动词)
3.构造器(后面学习)
4.代码块(后面学习)
5.内部类(后面学习)
}
public class Phone{
//属性(成员变量)
String brand;
double price;
public void call(){
}
public void playGame(){
}
}
如何得到对象?
如何得到类的对象:
类名 对象名= new 类名();
Phone p = new Phone();
- 对象:是真实存在的具体东西
拿到对象后能做什么?
对象.成员变量;
对象.成员方法(...)
在JAVA中,必须先设计类,才获得对象
public class phone {
//属性
String name;
double price;
public void call(){
System.out.println("打电话");
}
public void send(){
System.out.println("发短信");
}
}
//测试
public class phoneTest {
public static void main(String[] args) {
//创建手机对象
phone p = new phone();
//给手机对象赋值
p.name = "小米";
p.price = 1999;
//获取手机对象的属性值
System.out.println(p.name);
System.out.println(p.price);
//调用手机对象的方法
p.call();
p.send();
}
}
2.类的几个补充注意事项
-
用来
描述
一类事物的类,专业叫做:Javabean类
。在javabean类中,是不写main方法的。
-
在以前,编写
main方法
的类,叫做测试类
。我们可以在测试中创建javabean类的对象并进行赋值调用。
public class 类名 {
1.成员变量(代表属性)
2.成员方法(代表行为)
}
public class Student {
//属性(成员变量)
String name;
int age;
//行为方法
public void study(){
System.out.println("好好学习,天天向上");
}
public void doHomework(){
System.out.println("键盘敲烂,月薪过万");
}
}
-
类名首字母建议
大写
,需要见名知意,驼峰模式。 -
一个java文件中可以定义多个class类,且只能一个类是
public
修饰的类名必须成为代码文件名。实际开发中建议还是一个文件定义一个class类。
-
成员变量的完整定义格式是:
修饰符
数据类型
变量名称
=初始化值
;一般无需指定初始化值,存在默认值。int age; //这里不写初始化值是因为,这里学生的年龄是一个群体的值,没有一个固定的初始化值。 //如果给age赋值,比如是18岁,那就代表者所有的学生年龄都是18岁。
//类的赋值不是在类里面赋值,而是在创建了对象之后再赋值,这时赋值的时这个特定的对象。 Student stu = new Student(); Stu.name="张三"; Stu.height=187;
对象的成员变量的默认值规则
数据类型 明细 默认值 基本类型 byte,short,int,long 0 基本类型 float,double 0.0 基本类型 boolean false 引用类型 类、接口、数组、 String
null //编写女朋友类,创建女朋友类的对象,给女朋友的属性赋值并调用女朋友类中的方法。自己思考女朋友有哪些属性,有哪些行为? public class girlFriend { public static void main(String[] args) { //创建女朋友对象 girl g = new girl(); //给女朋友对象赋值 g.name = "小红"; g.age = 20; g.hobby = "唱歌"; //获取女朋友对象的属性值 System.out.println(g.name); System.out.println(g.age); System.out.println(g.hobby); //调用女朋友对象的方法 g.eat(); g.sleep(); } }
//这是一个类 public class girl { //成员变量(代表属性) String name; int age; String hobby; //成员方法(代表行为) public void eat(){ System.out.println("吃饭"); } public void sleep(){ System.out.println("睡觉"); } }
3.开发中类的设计
先把需求拿过来,先要看这个需求当中有几类事物。每个事物,每类事务都要定义为单独的类,这类事物的名词都可以定义为属性,这类事物的功能,一般是动词,可以定义为行为。
二、封装
1.封装的介绍
封装是面向对象的三大特征:封装、继承、多态
封装的作用:告诉我们,如何正确设计对象的属性和方法。
/**需求:定义一个类描述人
属性:姓名、年龄
行为:吃饭、睡觉*/
public class Person{
String name;
int age;
public void eat(){
System.out.println("吃饭");
}
public void sleep(){
System.out.println("睡觉");
}
}
原则:对象代表什么,就得封装对应的数据,并提供数据对应的行为。
public class Circle {
double radius;
public void draw(){
System.out.println("根据半径"+radius+"画圆");
}
}
//人画圆,我们通常人为行为主体是人,其实是圆
//例如:人关门,这个门一定是门自己关的,人只是给了作用力,是门自己关上的。
2.封装的好处
- 对象代表什么,就得封装对应的数据,并提供数据对应的行为
- 降低我们的学习成本,可以少学,少记,或者说压根不用学,不用记对象有哪些方法,有需要时去找就行
3.private关键字
-
是一个权限修饰符
-
可以修饰成员(成员变量和成员方法)
-
被private修饰的成员只能在本类中才能访问
public class GirlFriend{ private String name; private int age; private String gender; }
public class leiMing { private int age; //set(赋值) public void setAge(int a){ if(a<0||a>120){ System.out.println("你给的年龄有误"); return; } age = a; } //get(取值) public int getAge(){ return age; } }
-
针对private修饰的成员变量,如果需要被其他类使用,提供相应的操作
-
提供“
setXxx(参数)
”方法,用于给成员变量复制,方法用public修饰 -
提供
“getXxx()”
方法,用于获取成员变量的值,方法用public修饰
为什么要调用set和get呢?
封装是面向对象编程的四大特性之一,它将数据(成员变量)和操作数据的方法绑定在一起,并隐藏对象的内部实现细节。通过将成员变量声明为 private
,外部类无法直接访问和修改这些变量,只能通过类提供的 set
和 get
方法来间接操作。这样可以防止外部代码对数据进行非法或不恰当的修改,保证数据的安全性和完整性。
三、this关键字
1.成员变量和局部变量
public class GirlFriend{
private int age;//成员变量:方法的外面,类的里面
public void method(){
int age = 10;//局部变量:方法的里面
System.out.println(age);
}
}
成员变量和局部变量一致时,采用就近原则
谁离我近,我就用谁
public class GirlFriend{
private int age;//成员变量:方法的外面,类的里面
public void method(){
int age = 10;//局部变量:方法的里面
System.out.println(age);
}
}
//在这里中,最后1个age距离 age=10最近,所以最后一个age用的是10的值
//假如我想用第一个int ,我们可以在System.out.println(this.age)
age前加入:this. 这里就可以打破就近原则,选择另一个变量
在 Java 中,当局部变量(比如方法的参数)和类的成员变量重名时,就会产生命名冲突。在这种情况下,如果直接使用变量名,Java 默认会使用局部变量。而 this
关键字的一个重要作用就是用来引用当前对象的成员变量,从而区分局部变量和成员变量。
2.举例
下面通过一个简单的示例来详细讲解从引用成员变量方向 this
关键字的用法:
class Employee {
// 定义成员变量
private String name;
private int age;
// 构造方法,用于初始化员工信息
public Employee(String name, int age) {
// 这里参数名和成员变量名相同,使用 this 引用成员变量
this.name = name;
this.age = age;
}
// 设置员工姓名的方法
public void setName(String name) {
// 使用 this 引用成员变量
this.name = name;
}
// 获取员工姓名的方法
public String getName() {
return this.name;
}
// 设置员工年龄的方法
public void setAge(int age) {
// 使用 this 引用成员变量
this.age = age;
}
// 获取员工年龄的方法
public int getAge() {
return this.age;
}
// 显示员工信息的方法
public void displayInfo() {
System.out.println("姓名: " + this.name + ", 年龄: " + this.age);
}
}
public class ThisKeywordVariableExample {
public static void main(String[] args) {
// 创建一个 Employee 对象
Employee employee = new Employee("李四", 25);
// 调用 displayInfo 方法显示员工信息
employee.displayInfo();
// 调用 setName 和 setAge 方法修改员工信息
employee.setName("王五");
employee.setAge(30);
// 再次调用 displayInfo 方法显示修改后的员工信息
employee.displayInfo();
}
}
代码详细解释:
1. 类的成员变量定义
private String name;
private int age;
这里定义了两个私有成员变量 name
和 age
,用于存储员工的姓名和年龄。
2. 构造方法中的 this
关键字使用
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
在构造方法中,参数名 name
和 age
与类的成员变量名相同。此时,this.name
表示当前对象的成员变量 name
,而直接使用的 name
则是构造方法的参数(局部变量)。通过 this.name = name;
语句,将局部变量 name
的值赋给了当前对象的成员变量 name
。同理,this.age = age;
也是将局部变量 age
的值赋给了成员变量 age
。
3. set
方法中的 this
关键字使用
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
在 setName
和 setAge
方法中,同样存在参数名和成员变量名相同的情况。使用 this
关键字来明确指定要操作的是当前对象的成员变量,避免了与局部变量的混淆。
4. get
方法中的 this
关键字使用
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
在 get
方法中,使用 this.name
和 this.age
来返回当前对象的成员变量的值。虽然在这种情况下,不使用 this
关键字也可以正常返回成员变量的值,因为这里没有局部变量与成员变量重名的问题,但使用 this
可以使代码的意图更加清晰,表明是在访问当前对象的成员变量。
5. displayInfo
方法中的 this
关键字使用
public void displayInfo() {
System.out.println("姓名: " + this.name + ", 年龄: " + this.age);
}
在 displayInfo
方法中,使用 this.name
和 this.age
来获取当前对象的成员变量的值,并将其输出。
四、构造方法
1.构造方法的概述
构造方法也叫做构造器、构造函数
2.构造方法的格式
public class Student{
修饰符 类名(参数){
方法体;
}
}
public class Student {
private String name;
private int age;
//如果我们自己没有写构造方法
// 那么编译器会自动生成一个无参构造方法
public Student() {
System.out.println("无参构造方法");
}
public Student(String name, int age) {
this.name = name;
this.age = age; //有参构造方法
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class StudentTest {
public static void main(String[] args) {
//创建类的对象
//调用的空参构造
//Student s1 = new Student();
Student s = new Student(name:"张三", age:20);
System.out.println(s.getName());
System.out.println(s.getAge());
}
}
特点:
- 方法名与类名相同,
大小写
也要一致 - 没有返回值类型,连
void
都没有 - 没有具体的
返回值
(不能由return带回结果数据)
执行时机:
- 创建对象的时候由
虚拟机
调用,不能手动调用构造方法 - 每创建一次对象,就会调用过一次构造方法
3.构造方法的作用
在创建对象的时候,由虚拟机自动调用构造方法,作用是给成员变量进行初始化的
4.构造方法的分类
public class Student{
private String name;
private int age;
public Student(){
...//空参构造方法
}
public Student (String name, int age){
....//带全部参数构造方法
}
}
无参构造方法:初始化的对象时,成员变量的数据均采用默认值
有参构造方法:在初始化对象的时候,同时可以为对象进行
5.构造方法的注意事项
- 构造方法的定义
- 如果没有定义构造方法,系统将给出一个默认的无参数构造方法
- 如果定义了构造方法,系统将不再提供默认的构造方法
- 构造方法的重载
- 带参构造方法,和无参构造方法,两者方法名相同,但是参数不同,这叫做构造方法的重载
- 推荐的使用方式
- 无论是否使用,都动手书写无参数构造方法,和带全部参数的构造方法
五、标准JavaBean
1.标准的JavaBean类
- 类名需要见名知意
- 成员变量使用
private
修饰 - 提供至少两个构造方法
- 无参构造方法
- 带全部参数的构造方法
- 成员方法
- 提供每一个成员变量对应的
setXxx()/getXxx()
- 如果还有其他行为,也需要写上
- 提供每一个成员变量对应的
举例子:
根据一个登录界面写一个JavaBean类
public class User {
//属性
private String username;
private String password;
private String email;
private String gender;
private int age;
//构造方法
//无参构造
public User() {
}
//有参构造
public User(String username, String password, String email, String gender, int age) {
this.username = username;
this.password = password;
this.email = email;
this.gender = gender;
this.age = age;
}
//方法
//set和get方法
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
我们再写一个javabean中会遇到一个问题:
这样写纯体力活啊!没事的没事的!我们有快捷键:
方法一:
alt+insert 或 alt+insert+Fn
alt+insert 第一个是构造函数,点击无选择生成的是空参 ,全选ok生成的是有参数的构造函数
alt+insert 点击setter和geteer,全选生成的是set和get
方法二:
下载插件pdg,下载完成后点击空白处就会出现。然后点击Ptg To JavaBean
六、对象内存图
1.一个对象的内存图
Student s = new Student();
- 加载class文件
- 申明局部变量
- 在堆中开辟一个空间
- 默认初始化
- 显示初始化
- 构造方法初始化
- 将堆中空间的地址值赋值给左边的局部变量
举例:
public class Student{
String name;
int age;
public void study(){
System.out.println("好好学习")
}
}
public class TestStudent{
public static void main(String [] args){
Student s= new Student();
System.out.println(s);
System.out.println(s.name+"...."+s.age);
s.name = "阿强";
s.age = 23;
System.out.println(s.name+"..."+s.age);
s.study();
}
}
解析:
内存执行顺序解析(基于Java内存模型)
1. 类加载阶段(方法区)
-
加载class文件
JVM将
Student.class
和
TestStudent.class
加载到方法区,存储类结构信息(字段、方法签名、常量池等)。
Student
类包含字段name
(String)、age
(int)和方法study()
。TestStudent
类包含main()
方法入口。
2. 栈内存操作(main方法启动)
- 声明局部变量
执行main()
时,在栈内存中创建main
方法的栈帧,声明局部变量s
(此时s
未指向任何对象,值为null
)。
3. 堆内存分配(对象实例化)
- 在堆中开辟空间
执行new Student()
时,在堆内存中为Student
对象分配空间,内存大小由字段类型决定(String
引用 +int
值)。
4. 对象初始化流程
- 默认初始化
对象字段赋默认值:name
→null
(引用类型默认值)age
→0
(基本类型默认值)。
- 显示初始化(本例中无)
如果类中字段有显式赋值(如String name = "默认";
),此时会执行。但示例代码未定义,此步骤跳过。 - 构造方法初始化(本例中无)
如果存在构造方法(如public Student() { age = 18; }
),会通过构造器赋值。示例代码未定义构造方法,此步骤跳过。
5. 变量关联与操作
-
地址赋值给局部变量
堆中对象地址赋值给栈帧中的
s
变量,完成引用关联。
- 执行
Student s = new Student();
后,s
指向堆内存中的对象。
- 执行
-
对象字段修改
后续代码通过s.name = "阿强";
和s.age = 23;
直接修改堆中对象的字段值,无需重新初始化。
6. 方法调用(方法区与栈协作)
- 执行
s.study()
- 从方法区加载
study()
的字节码指令。 - 在栈中创建
study()
方法的栈帧,执行System.out.println(" 好好学习")
(注:用户代码此处缺少分号,实际会编译报错)。
- 从方法区加载
内存操作完整流程总结
步骤 | 操作内容 | 内存区域 | 示例代码体现 |
---|---|---|---|
1 | 加载类信息 | 方法区 | Student 和TestStudent 类加载 |
2 | 声明局部变量s | 栈内存 | Student s; |
3 | 堆中分配对象空间 | 堆内存 | new Student() |
4 | 字段默认初始化(null/0 ) | 堆内存 | s.name 和s.age 初始值 |
5 | 显式/构造初始化(无) | - | 代码未定义相关逻辑 |
6 | 对象地址赋值给s | 栈内存 | s = new Student(); |
7 | 修改字段值 | 堆内存 | s.name = "阿强"; 等操作 |
关键现象解释
System.out.println(s)
输出哈希值
因打印对象时默认调用toString()
,而Student
未重写该方法,输出格式为类名@哈希值
。- 字段值修改的可见性
直接通过引用s
修改堆中对象字段,所有指向该对象的引用均会看到更新后的值。 - 编译隐患
study()
方法中System.out.println(" 好好学习")
缺少分号,实际运行前会因语法错误中断。
2.多个对象的内存图
举例:
public class Student{
String name;
int age;
public void study(){
System.out.println("好好学习");
}
}
public class TestStudent{
public static void main(String [] args){
Student s= new Student();
System.out.println(s);
s.name = "阿强";
s.age = 23;
System.out.println(s.name+"..."+s.age);
s.study();
Student s2= new Student();
System.out.println(s2);
s2.name = "阿珍";
s2.age = 24;
System.out.println(s2.name+"..."+s2.age);
s2.study();
}
}
第二次在创建对象时。class文件不需要再加载一次
解析:
2.1、执行顺序与内存操作分步解析
1. 加载class文件(方法区)
- 触发条件:首次使用
Student
类时。 - 操作内容:
- 将
Student.class
加载到方法区,存储类结构(字段name
、age
和方法study()
的定义)。 - 将
TestStudent.class
加载到方法区,存储main()
方法入口。
- 将
2. 声明局部变量(栈内存)
- 操作内容:
- 执行
main()
方法时,在栈内存创建main
方法的栈帧。 - 声明局部变量
s
和s2
(初始值均为null
)。
- 执行
3. 堆内存分配(对象空间开辟)
- 操作内容:
new Student()
触发堆内存分配,根据类结构计算对象大小(String
引用 +int
值)。- 示例:
s = new Student()
→ 堆地址0x001
。s2 = new Student()
→ 堆地址0x002
(独立空间)。
4. 默认初始化(堆内存)
- 操作内容:
- 对象字段赋默认值:
name
→null
(引用类型默认值)。age
→0
(基本类型默认值)。
- 示例:
s
的初始状态:name=null
,age=0
。s2
的初始状态:name=null
,age=0
。
- 对象字段赋默认值:
5. 显示初始化(堆内存)
- 触发条件:类中显式赋值的字段(如
String name = "默认"
)。 - 当前代码:
Student
类未定义显式赋值字段,跳过此步骤。
6. 构造方法初始化(堆内存)
- 触发条件:存在自定义构造方法(如
public Student() { ... }
)。 - 当前代码:
Student
类未定义构造方法,使用默认无参构造器,跳过此步骤。
7. 地址赋值(栈内存)
- 操作内容:
- 将堆内存地址赋值给栈中的局部变量。
- 示例:
s = 0x001
(指向第一个对象)。s2 = 0x002
(指向第二个对象)。
2.2、内存模型与对象独立性的关键验证
1. 对象独立性的体现
对象 | 堆地址 | 字段修改后的值 |
---|---|---|
s | 0x001 | name="阿强" , age=23 |
s2 | 0x002 | name="阿珍" , age=24 |
- 验证逻辑:
s
和s2
指向不同堆地址,修改其中一个对象的字段不会影响另一个对象。System.out.println(s == s2)
→ 输出false
。
2. 内存操作流程图解
2.3、执行流程总结(分阶段)
阶段 | 操作内容 | 内存区域 |
---|---|---|
类加载 | 加载Student 和TestStudent 类信息 | 方法区 |
栈帧创建 | 声明s 和s2 (初始null ) | 栈内存 |
堆内存分配 | 为s 和s2 分配独立空间 | 堆内存 |
对象初始化 | 默认初始化 → 显式赋值(用户代码修改) | 堆内存 |
方法调用 | study() 从方法区加载逻辑到栈执行 | 栈内存 |
2.4、常见问题解答
1. 为什么System.out.println(s)
输出地址?
- 原因:未重写
toString()
方法,默认调用Object.toString()
,格式为类名@哈希值
。
2. 显示初始化和构造方法初始化有何区别?
- 显示初始化:直接在类中赋值字段(如
String name = "张三"
),编译时自动插入到构造方法中。 - 构造方法初始化:通过自定义构造器赋值(优先级高于显示初始化)。
3. 如何优化内存使用?
- 复用对象:避免频繁
new
对象(尤其循环中)。 - 垃圾回收:
main()
结束后,s
和s2
成为垃圾对象,由GC自动回收。
附:修正后的代码输出示例
Student@1b6d3586
阿强...23
好好学习
Student@4554617c
阿珍...24
好好学习
3.两个变量指向同一个对象内存图
举例:
public class Student{
String name;
int age;
public void study(){
System.out.println("好好学习");
}
}
public class TestStudent{
public static void main(String [] args){
Student s= new Student();
s.name = "阿强";
Student s2= s;
s2.name = "阿珍";
System.out.println(s.name+"..."+s2.name);
}
}
3.1、类加载阶段(方法区)
- 加载TestStudent.class
当JVM启动时,首先将TestStudent.class
加载到方法区,存储类结构信息(成员方法、字段描述等) - 加载Student.class
执行new Student()
时触发类加载机制,将Student.class
加载到方法区,包含name
、age
字段和study()
方法元数据
3.2、栈内存操作(main方法栈帧)
- 声明局部变量
在main方法栈帧中创建引用变量s
(地址未初始化)和s2
(此时两者均为null
) - 对象创建指令
new Student()
操作码触发堆内存分配,此时:- 在堆中生成对象内存空间(包含对象头 +
String name
+int age
) - 默认初始化:
name=null
,age=0
(基本类型和引用类型的零值初始化) - 显式初始化:由于Student类没有直接赋值的字段(如
String name = "默认名"
),此阶段跳过
- 在堆中生成对象内存空间(包含对象头 +
- 构造方法执行
若存在构造方法(本案例未定义),会通过invokespecial
指令调用<init>
方法完成初始化
3.3、堆内存操作(对象关联)
- 地址赋值
将堆中Student对象地址赋值给栈帧中的s
变量(完成s = new Student()
) - 引用传递
s2 = s
操作使s2
指向堆中同一个对象(此时两个引用共享对象数据) - 字段修改
通过s2.name = "阿珍"
修改堆内存对象数据,此时s.name
同步变化(引用指向同一实体)
3.4、最终内存结构
内存区域 | 存储内容 |
---|---|
方法区 | TestStudent类字节码、Student类元数据(包含study()方法代码) |
堆内存 | Student对象实例(name=“阿珍”, age=0) |
栈内存 | main方法栈帧:s=0x100(指向堆对象), s2=0x100(与s同地址) |
3.5、输出结果分析
System.out.println(s.name+"..."+s2.name)
→ 输出阿珍...阿珍
(s与s2引用同一对象,堆内数据修改对所有引用可见)
关键理解点:引用类型变量的赋值操作传递的是对象地址值,而非创建新对象。这种特性是Java对象共享机制的核心体现。
4.this的内存原理
public class Student{
private int age;
public void method(){
int age=10;
System.out.println(age);//10
System.out.println(this.age);//成员变量的值 0
}
}
this的作用
:区分局部变量和成员变量
this的本质
:所在方法调用者
的地址值
public class Student{
private int age;
public void method(){
int age=10;
System.out.println(age);//10
System.out.println(this.age);//成员变量的值 0
}
}
public class StudentTest{
public static void main (String[] args){
Student s = new Student();
s.method();
}
}
4.1、类加载阶段(方法区核心操作)
- 加载
StudentTest.class
- JVM启动时优先加载含
main()
的类到方法区 - 存储类元数据:静态变量、方法表(含
main()
入口地址)
- JVM启动时优先加载含
- 触发
Student.class
加载- 当执行
new Student()
时触发类加载 - 方法区新增:
- 字段描述表(
private int age
的访问权限和偏移量) method()
的字节码指令集合- 隐式默认构造器
<init>
方法(因无自定义构造方法)
- 字段描述表(
- 当执行
4.2、对象实例化流程(堆栈协同)
步骤 | 内存区域 | 具体行为 | 代码对应 |
---|---|---|---|
3 | 栈内存 | 在main 方法栈帧声明局部变量s (初始值null ) | Student s; |
4 | 堆内存 | 分配对象空间:对象头(12字节)+ int age (4字节)= 16字节 | new Student() |
5 | 堆内存 | 默认初始化:age=0 (基本类型零值填充) | 隐式执行 |
6 | 堆内存 | 构造方法初始化:执行空参数的<init> 方法(无实际操作) | 隐式调用 |
7 | 栈内存 | 将堆地址(如0x7a3f )赋值给s 变量 | s = new... |
4.3、方法调用时的内存隔离(栈帧作用域)
执行s.method()
时发生:
-
新建栈帧:在栈顶创建
method()
的独立空间,包含:
- 隐式参数
this
(指向堆地址0x7a3f
) - 局部变量
age=10
(存储于栈帧变量表)
- 隐式参数
-
变量访问规则:
输出语句 内存访问路径 结果 System.out.println(age)
访问栈帧局部变量表 10 System.out.println(this.age)
通过 this
指针访问堆内存字段0
4.4、关键差异对比表
特征 | 成员变量this.age | 局部变量age |
---|---|---|
存储位置 | 堆内存对象内部 | 栈帧局部变量表 |
生命周期 | 与对象共存亡 | 随方法栈帧销毁而消失 |
初始化值 | 默认零值(int=0) | 必须显式赋值 |
访问方式 | 需通过对象引用 | 直接访问 |
4.5、技术扩展:this
的底层实现
当调用method()
时:
-
字节码层面:
java复制aload_0 // 将this引用压入操作数栈(对应堆地址0x7a3f) getfield #2 // 根据字段偏移量读取堆中age值(#2为字段符号引用)
-
内存隔离机制:局部变量
age
会遮蔽同名的成员变量,必须通过this.
显式穿透访问堆数据
5.基本数据类型和引用数据类型的区别
5.1基本数据类型
public class Test{
public static void main (String [] args){
int a = 10;
}
}
基本数据类型:
在变量当中存储的是真实的数据值
从内存角度:数据值是存储再自己的空间中
特点:赋值给其他变量,也是赋值的真实的值。
5.2引用数据类型
public class TestStudent{
public static void main(String[] args){
Student s=new Student;
}
}
引用数据类型:
堆中存储的数据类型,也就是new出来的,变量中存储的是地址值。引用:就是使用其他空间中数据的意思。
从内存的角度:
数据值是存储在其他空间中,自己空间中存储的是地址值
特点:
赋值给其他变量,赋的地址值。
七、补充知识:成员变量、局部变量区别
成员变量:类中方法外的变量
局部变量:方法中的变量
区别:
区别 | 成员变量 | 局部变量 |
---|---|---|
类中位置不同 | 类中,方法外 | 方法内、方法申明上 |
初始化值不同 | 有默认初始化值 | 没有,使用前需要完成赋值 |
内存位置不同 | 堆内存 | 栈内存 |
生命周期不同 | 随着对象的创建而存在,随着对象的消失而消失 | 随着方法的调用而存在,随着方法的运行结束而消失 |
作用域 | 整个类中有效 | 当前方法中有效 |