庖丁解牛,洞悉 Java 面向对象的精妙架构
友友们,互三啦!!互三必回!!后台踢踢哦~
更多精彩:个人主页
更多java笔记系列:java学习笔记
赛博算命系列:赛博算命
赛博算命之 ”梅花易数“ 的 “JAVA“ 实现 ——从玄学到科学的探索
赛博算卦之周易六十四卦JAVA实现:六幺算尽天下事,梅花化解天下苦。
文章目录
- #面向对象的介绍:
- 一、设计对象并使用
- 1.类和对象
- 2.类的几个补充注意事项
- 3.开发中类的设计
- 二、封装
- 1.封装的介绍
- 2.封装的好处
- 3.private关键字
- 三、this关键字
- 1.成员变量和局部变量
- 2.举例
- 代码详细解释:
- 1. 类的成员变量定义
- 2. 构造方法中的 `this` 关键字使用
- 3. `set` 方法中的 `this` 关键字使用
- 4. `get` 方法中的 `this` 关键字使用
- 5. `displayInfo` 方法中的 `this` 关键字使用
- 四、构造方法
- 1.构造方法的概述
- 2.构造方法的格式
- 3.构造方法的作用
- 4.构造方法的分类
- 5.构造方法的注意事项
- 五、标准JavaBean
- 1.标准的JavaBean类
- 六、对象内存图
- 1.一个对象的内存图
- 内存执行顺序解析(基于Java内存模型)
- **1. 类加载阶段(方法区)**
- **2. 栈内存操作(main方法启动)**
- **3. 堆内存分配(对象实例化)**
- **4. 对象初始化流程**
- **5. 变量关联与操作**
- **6. 方法调用(方法区与栈协作)**
- **内存操作完整流程总结**
- **关键现象解释**
- 2.多个对象的内存图
- **2.1、执行顺序与内存操作分步解析**
- **1. 加载class文件(方法区)**
- **2. 声明局部变量(栈内存)**
- **3. 堆内存分配(对象空间开辟)**
- **4. 默认初始化(堆内存)**
- **5. 显示初始化(堆内存)**
- **6. 构造方法初始化(堆内存)**
- **7. 地址赋值(栈内存)**
- **2.2、内存模型与对象独立性的关键验证**
- **1. 对象独立性的体现**
- **2. 内存操作流程图解**
- **2.3、执行流程总结(分阶段)**
- **2.4、常见问题解答**
- **1. 为什么`System.out.println(s)` 输出地址?**
- **2. 显示初始化和构造方法初始化有何区别?**
- **3. 如何优化内存使用?**
- 3.两个变量指向同一个对象内存图
- 3.1、类加载阶段(方法区)
- 3.2、栈内存操作(main方法栈帧)
- 3.3、堆内存操作(对象关联)
- 3.4、最终内存结构
- 3.5、输出结果分析
- 4.this的内存原理
- 4.1、类加载阶段(方法区核心操作)
- 4.2、对象实例化流程(堆栈协同)
- 4.3、方法调用时的内存隔离(栈帧作用域)
- 4.4、关键差异对比表
- 4.5、技术扩展:`this`的底层实现
- 5.基本数据类型和引用数据类型的区别
- 5.1基本数据类型
- 5.2引用数据类型
- 七、补充知识:成员变量、局部变量区别
#面向对象的介绍:
面向:拿、找
对象:能干活的东西
面向对象编程:拿东西过来做对应的事情
面向对象编程的例子:
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(a120){ 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指令调用方法完成初始化
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()入口地址)
- 触发Student.class 加载
- 当执行new Student()时触发类加载
- 方法区新增:
- 字段描述表(private int age的访问权限和偏移量)
- method()的字节码指令集合
- 隐式默认构造器方法(因无自定义构造方法)
4.2、对象实例化流程(堆栈协同)
步骤 内存区域 具体行为 代码对应 3 栈内存 在main方法栈帧声明局部变量s(初始值null) Student s; 4 堆内存 分配对象空间:对象头(12字节)+ int age(4字节)= 16字节 new Student() 5 堆内存 默认初始化:age=0(基本类型零值填充) 隐式执行 6 堆内存 构造方法初始化:执行空参数的方法(无实际操作) 隐式调用 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出来的,变量中存储的是地址值。引用:就是使用其他空间中数据的意思。
从内存的角度:
数据值是存储在其他空间中,自己空间中存储的是地址值
特点:
赋值给其他变量,赋的地址值。
七、补充知识:成员变量、局部变量区别
成员变量:类中方法外的变量
局部变量:方法中的变量
区别:
区别 成员变量 局部变量 类中位置不同 类中,方法外 方法内、方法申明上 初始化值不同 有默认初始化值 没有,使用前需要完成赋值 内存位置不同 堆内存 栈内存 生命周期不同 随着对象的创建而存在,随着对象的消失而消失 随着方法的调用而存在,随着方法的运行结束而消失 作用域 整个类中有效 当前方法中有效 - 加载TestStudent.class
- 原因:未重写toString()方法,默认调用Object.toString() ,格式为类名@哈希值。
- 验证逻辑:
- 操作内容:
- Student类未定义构造方法,使用默认无参构造器,跳过此步骤。
- Student类未定义显式赋值字段,跳过此步骤。
- 对象字段赋默认值:
- 操作内容:
- 操作内容:
- 操作内容:
- System.out.println(s) 输出哈希值
- 执行s.study()
-
- 默认初始化
- 在堆中开辟空间
- 声明局部变量
-
-
-
- 对象:是真实存在的具体东西
- 类(设计图):是对象共同特征的描述