Java基础语言 ④ :面向对象核心——构造方法、this关键字与对象内存模型详解

Java 基础语言 ④ ------ 面向对象基础-类与对象

    • 前言
    • 一、类的定义
      • [1.1 类的理解](#1.1 类的理解)
      • [1.2 类的组成](#1.2 类的组成)
      • [1.3 示例:手机类](#1.3 示例:手机类)
      • [1.4 深入理解:类作为蓝图与 ADT](#1.4 深入理解:类作为蓝图与 ADT)
    • 二、对象的创建与使用
      • [2.1 创建对象:`new` 关键字](#2.1 创建对象:new 关键字)
      • [2.2 引用与对象的分离](#2.2 引用与对象的分离)
      • [2.3 赋值即拷贝引用](#2.3 赋值即拷贝引用)
      • [2.4 引用相等 vs 内容相等](#2.4 引用相等 vs 内容相等)
    • 三、对象内存模型
      • [3.1 内存区域回顾](#3.1 内存区域回顾)
      • [3.2 单个对象内存图](#3.2 单个对象内存图)
      • [3.3 多个对象内存图](#3.3 多个对象内存图)
    • 四、成员变量与局部变量
      • [4.1 四大区别](#4.1 四大区别)
    • 五、构造方法
      • [5.1 构造方法的本质:初始化而非创建](#5.1 构造方法的本质:初始化而非创建)
      • [5.2 默认构造函数](#5.2 默认构造函数)
      • [5.3 构造器重载](#5.3 构造器重载)
      • [5.4 构造器间的互调:`this(...)`](#5.4 构造器间的互调:this(...))
    • [六、`this` 关键字](#六、this 关键字)
      • [6.1 本质:隐式的隐藏参数](#6.1 本质:隐式的隐藏参数)
      • [6.2 核心应用:消除命名歧义](#6.2 核心应用:消除命名歧义)
      • [6.3 `this` 的内存模型](#6.3 this 的内存模型)
      • [6.4 关键约束](#6.4 关键约束)
      • [6.5 总结](#6.5 总结)
    • 总结

🎬 博主名称: 超级苦力怕

🔥 个人专栏: 《Java 后端修炼手册》《Java 基础语言》

🚀 每一次思考都是突破的前奏,每一次复盘都是精进的开始!


文章元信息:

  • 标签: #Java基础 #面向对象 #类与对象
  • 建议顺序: 04
  • 前置知识: 建议先读《流程控制与数组》(③)

前言

从这一篇开始,我们正式踏入 Java 最核心的领域------面向对象编程(OOP) 。如果说流程控制和数组让程序有了"逻辑",那类与对象就让程序有了"结构"。类是蓝图,对象是实物;类是抽象的模板,对象是堆内存中真实存在的数据仓库。本文将带你从零理解类的定义、对象的创建、new 关键字背后的内存操作、构造方法的初始化职责、以及 this 关键字如何将对象实体与执行逻辑绑定在一起。适合刚学完基本语法、准备系统入门面向对象的 Java 初学者,也适合想搞懂底层内存模型的进阶读者。


一、类的定义

1.1 类的理解

客观存在的事物皆为对象,所以我们也常说万物皆对象

类是对现实生活中一类具有共同属性和行为的事物的抽象。它不是具体实体,而是一张"蓝图"------规定了这一类事物有哪些特征(属性)以及能做什么(行为)。

概念 理解 举例
类(Class) 对一类事物的抽象描述,是对象的数据类型模板 "手机"这个类别
对象(Object) 看得见摸得着的具体实体 你口袋里那台 iPhone 16

简单理解:类是对事物的一种描述,对象是具体存在的事物。类是"图纸",对象是按图纸造出来的"实物"。

1.2 类的组成

类由两部分组成:

  • 属性(字段/成员变量):描述事物的特征,在类中方法外定义

  • 行为(成员方法) :描述事物能执行的操作,与普通方法的区别是去掉了 static 关键字

    类的组成:
    ├── 属性 → 成员变量(Field / Instance Variable)
    │ 例如:手机有品牌(brand)、价格(price)
    └── 行为 → 成员方法(Method)
    例如:手机可以打电话(call)、发短信(sendMessage)

类的定义步骤:

  1. 定义类:public class 类名 { }
  2. 编写成员变量:数据类型 变量名;
  3. 编写成员方法:去掉 static 关键字
java 复制代码
public class 类名 {
    // 成员变量
    数据类型 变量1;
    数据类型 变量2;

    // 成员方法
    public 返回类型 方法名() {
        // 方法体
    }
}

1.3 示例:手机类

java 复制代码
/*
    手机类 Phone

    成员变量:
        品牌  brand  (String)
        价格  price  (int)

    成员方法:
        打电话  call()
        发短信  sendMessage()
 */
public class Phone {
    // 成员变量
    String brand;
    int price;

    // 成员方法
    public void call() {
        System.out.println("打电话");
    }

    public void sendMessage() {
        System.out.println("发短信");
    }
}

⚠️ 注意 :成员变量声明在类中、方法外。它们有默认初始值(int0,引用类型为 null),不需要手动初始化即可使用。

1.4 深入理解:类作为蓝图与 ADT

从软件工程的视角看,类不仅是代码的"收纳盒",更承担了两个关键角色:

① 类定义了对象的内存布局

类规定了该类型的每个对象在堆内存中需要分配多少空间、存储哪些变量。编译器根据类定义,精确计算每个对象所需的内存大小。

② 类是实现抽象数据类型(ADT)的工具

通过将字段设为 private 并仅暴露 public 方法,类可以强制维护不变性------即内部数据始终处于合法状态。外部代码无法直接"弄脏"对象内部的数据结构。

java 复制代码
public class Date {
    private int day;
    private int month;

    // 通过构造方法强制校验
    public Date(int day, int month) {
        if (day < 1 || day > 31 || month < 1 || month > 12) {
            throw new IllegalArgumentException("日期不合法");
        }
        this.day = day;
        this.month = month;
    }
}

这种"隐藏内部实现、只暴露安全接口"的思想,贯穿了 Java 的整个面向对象设计。


二、对象的创建与使用

2.1 创建对象:new 关键字

对象是通过 new 关键字创建的。new 在堆内存中为对象分配空间,然后调用构造方法初始化对象的成员变量。

格式:

java 复制代码
类名 对象名 = new 类名();

这条语句拆开看,其实做了三件事:

复制代码
Phone p = new Phone();
  │       │     │
  │       │     └── ③ 调用构造方法,初始化对象
  │       └── ② 在堆内存中为对象分配空间
  └── ① 在栈内存中声明一个引用变量 p

![[new关键字执行三步.png]]
✅ 创建对象并访问成员

java 复制代码
public class PhoneDemo {
    public static void main(String[] args) {
        // 创建对象
        Phone p = new Phone();

        // 访问成员变量(默认值)
        System.out.println(p.brand);   // null
        System.out.println(p.price);   // 0

        // 为成员变量赋值
        p.brand = "小米";
        p.price = 2999;

        System.out.println(p.brand);   // 小米
        System.out.println(p.price);   // 2999

        // 调用成员方法
        p.call();           // 打电话
        p.sendMessage();    // 发短信
    }
}

⚠️ 区分字段与方法p.brand 没有括号,访问的是字段p.call() 带有括号,调用的是方法

2.2 引用与对象的分离

这是 Java 中最容易混淆的概念之一,必须明确区分

  • 引用变量 存储在栈(或包含它的对象)中,本质是一个内存地址
  • 对象实体始终在堆中,包含实际的成员变量数据
  • Phone p = new Phone(); 中,p 是引用变量,new Phone() 才是对象

2.3 赋值即拷贝引用

当一个引用变量赋值给另一个引用变量时,拷贝的是地址,不是对象本身

java 复制代码
Phone p1 = new Phone();
p1.brand = "华为";

Phone p2 = p1;          // p2 和 p1 指向堆中**同一个**Phone 对象
p2.brand = "苹果";      // 通过 p2 修改,实际上改的是同一个对象

System.out.println(p1.brand);   // "苹果" ← p1 看到的也变了!
复制代码
赋值前:                         赋值后:
p1 ──▶ [Phone对象]              p1 ──▶ [Phone对象]
                                    ↗
                                  p2

关键结论:两个引用指向同一个对象,通过任意一个引用修改数据,另一个引用也会"看到"这个变化。

2.4 引用相等 vs 内容相等

java 复制代码
Phone a = new Phone();
a.brand = "三星";

Phone b = new Phone();
b.brand = "三星";

System.out.println(a == b);         // false  ← 比较的是地址
System.out.println(a.brand.equals(b.brand)); // true ← 比较的是内容
比较方式 比较什么 适用场景
== 两个引用是否指向同一个对象(地址相等) 判断是否同一实例
.equals() 两个对象是否内容相同(逻辑相等) 判断数据是否等价

默认情况下 .equals() 等同于 ==。需要内容比较时,必须在类中重写 equals() 方法,后面的继承章节会详细讨论。


三、对象内存模型

3.1 内存区域回顾

JVM 将内存划分为三个主要区域:

内存区域 存储内容 生命周期 共享性
栈(Stack) 局部变量、方法参数、引用变量 方法执行期间 线程私有
堆(Heap) 对象实体(成员变量)、数组 由 GC 管理 线程共享
方法区(Method Area) 类的字节码、静态变量、方法代码 类加载到卸载 线程共享

3.2 单个对象内存图

复制代码
栈内存                         堆内存                    方法区
┌──────────────┐              ┌──────────────┐         ┌─────────────────┐
│   main()     │              │  Phone对象    │         │    Phone.class  │
│              │              │              │         │                 │
│  Phone p ────┼──────────────┼──▶ brand=null│         │  void call()    │
│              │              │    price=0   │◀────────│  void sendMsg() │
└──────────────┘              └──────────────┘         └─────────────────┘

执行 p.brand = "小米"; p.price = 2999; 后:

复制代码
栈内存                         堆内存
┌──────────────┐              ┌──────────────┐
│   main()     │              │  Phone对象    │
│              │              │              │
│  Phone p ────┼──────────────┼──▶ brand="小米"│
│              │              │    price=2999 │
└──────────────┘              └──────────────┘

调用 p.call() 时,JVM 找到 p 指向的对象,拿到对象的类信息,在方法区中找到 call() 的字节码并执行。方法执行时,会在栈顶新开一个栈帧。

3.3 多个对象内存图

java 复制代码
Phone p1 = new Phone();
p1.brand = "华为";

Phone p2 = new Phone();
p2.brand = "苹果";

结论

  • 成员变量 :每个对象在堆中有独立的内存区域,各自存储各自的变量值
  • 成员方法 :多个对象共享方法区中的同一份代码 ------方法代码只存一份,通过 this 区分是哪个对象在调用

四、成员变量与局部变量

4.1 四大区别

区别维度 成员变量 局部变量
类中位置 类中、方法外 方法内部或方法声明(参数)上
内存中位置 堆内存 栈内存
生命周期 随对象存在而存在,随对象被 GC 回收而消失 随方法调用而存在,方法结束即消失
初始化值 有默认值(int→0, double→0.0, 引用→null, booleanfalse 无默认值,必须先赋值后使用

✅ 位置差异示例

java 复制代码
public class Student {
    // 成员变量 ------ 类中方法外
    String name;
    int age;

    public void study() {
        int hours = 2;      // 局部变量 ------ 方法内部
        System.out.println(name + "学习了" + hours + "小时");
    }

    public void exam(int score) {   // score ------ 局部变量(方法参数)
        System.out.println(name + "考了" + score + "分");
    }
}

⚠️ 如果在方法内部定义了与成员变量同名的局部变量,局部变量会遮蔽成员变量 。此时必须用 this.变量名 来访问成员变量。这引出了下一节的内容------this 关键字。


五、构造方法

5.1 构造方法的本质:初始化而非创建

构造方法常被误解为"创建对象的方法",但事实上:

  • JVM 负责创建对象------在堆中分配空间、赋予默认值
  • 构造方法负责初始化------把有意义的值填入已分配好的空间

语法特征:

  • 方法名必须与类名完全相同
  • 没有返回类型 (连 void 都不能写)
  • 通过 new 关键字调用
java 复制代码
public class Student {
    String name;
    int age;

    // 构造方法
    public Student(String n, int a) {
        name = n;    // 初始化 name 字段
        age = a;     // 初始化 age 字段
    }
}
复制代码
new Student("张三", 18) 的执行过程:

  ① JVM 在堆中分配空间,字段获得默认值(name=null, age=0)
                ↓
  ② JVM 调用构造方法 Student("张三", 18)
                ↓
  ③ 构造方法将 name 设为 "张三",age 设为 18
                ↓
  ④ 返回对象的引用地址

5.2 默认构造函数

如果类中没有写任何 构造方法,Java 会自动提供一个默认的无参构造方法

java 复制代码
public class Student {
    String name;
    int age;

    // 没有显式写构造方法
    // Java 自动提供:public Student() { }
}

// 使用时:
Student s = new Student();  // 可以,有默认无参构造

⚠️ 关键规则 :一旦你手动写了任意一个 构造方法(无论是否有参),Java 就不再自动提供默认无参构造。

java 复制代码
public class Student {
    String name;
    int age;

    // 自定义了一个有参构造
    public Student(String n, int a) {
        name = n;
        age = a;
    }
}

Student s1 = new Student("张三", 18);  // ✅ 可以
Student s2 = new Student();            // ❌ 编译错误!无参构造已消失

解决方案:如果仍然需要无参构造,必须手动写出:

java 复制代码
public Student() { }                     // 手动添加无参构造
public Student(String n, int a) { ... } // 有参构造

5.3 构造器重载

一个类可以有多个构造方法,只要它们的参数数量或类型不同。(这和方法重载的规则一样。)

java 复制代码
public class Phone {
    String brand;
    int price;

    // 无参构造
    public Phone() { }

    // 只传品牌
    public Phone(String b) {
        brand = b;
    }

    // 传品牌和价格
    public Phone(String b, int p) {
        brand = b;
        price = p;
    }
}

// 三种创建方式
Phone p1 = new Phone();               // 无参
Phone p2 = new Phone("华为");          // 一个参数
Phone p3 = new Phone("苹果", 5999);    // 两个参数

5.4 构造器间的互调:this(...)

当一个类有多个构造方法时,可以用 this(...) 让一个构造方法调用另一个,避免重复代码。

java 复制代码
public class Phone {
    String brand;
    int price;

    // 主构造方法
    public Phone(String brand, int price) {
        this.brand = brand;
        this.price = price;
    }

    // 只传品牌------委托给主构造方法
    public Phone(String brand) {
        this(brand, 0);   // 调用 Phone(String, int),价格默认为 0
    }

    // 无参------委托给单参构造方法
    public Phone() {
        this("未知品牌");  // 调用 Phone(String)
    }
}

⚠️ this(...) 必须是构造方法体中的第一条语句 ,且一个构造方法中只能调用一次 this(...)


六、this 关键字

6.1 本质:隐式的隐藏参数

this 是指向当前正在操作的对象 的引用。每当调用非静态方法时,Java 会自动将调用该方法的对象的引用作为隐藏参数 传入,这个隐藏参数就是 this

java 复制代码
public class Student {
    String name;

    public void introduce() {
        // 这里的 this 就是调用 introduce 的那个对象
        System.out.println("我是" + this.name);
    }
}

Student s1 = new Student();
s1.name = "张三";
s1.introduce();   // 隐藏传入了 s1 → method 内部 this = s1

Student s2 = new Student();
s2.name = "李四";
s2.introduce();   // 隐藏传入了 s2 → method 内部 this = s2

6.2 核心应用:消除命名歧义

当方法的参数名与成员变量名相同时,局部变量(参数)会遮蔽成员变量 。此时必须用 this.变量名 来访问成员变量:

java 复制代码
public class Student {
    private String name;   // 成员变量
    private int age;       // 成员变量

    // 参数名故意与字段名相同------这是常见的命名惯例
    public Student(String name, int age) {
        this.name = name;  // this.name 是成员变量,name 是参数
        this.age = age;    // this.age 是成员变量,age 是参数
    }

    public void setName(String name) {
        this.name = name;  // 同样:this.name 是字段,name 是参数
    }
}

在构造方法和 Setter 中,this.字段 = 参数 是最常见的 Java 编码惯例。

6.3 this 的内存模型

this 本身是一个局部变量,存储在方法调用的栈帧中:

6.4 关键约束

① 静态方法中没有 this

静态方法通过 static 修饰,属于类本身而非对象,调用时不会传入对象引用,因此内部不存在 this

java 复制代码
public class Student {
    String name;

    public static void printInfo() {
        System.out.println(this.name);  // ❌ 编译错误!静态方法中不能使用 this
    }
}

this 不可被重新赋值

在 Java 中,this 是一个不可改变的隐式 final 参数 。你不能让 this 指向另一个对象。

java 复制代码
public void someMethod() {
    this = new Student();  // ❌ 编译错误!不能给 this 赋值
}

6.5 总结

this 的用法 说明 场景
this.字段名 访问当前对象的成员变量 参数与字段同名时消除歧义
this.方法名() 调用当前对象的另一个成员方法 方法间调用(通常可省略 this
this(...) 调用本类的另一个构造方法 构造器重载中消除重复代码

this 是 Java 中连接"对象实体"与"执行逻辑"的纽带------它确保了方法始终知道自己操作的是哪个对象的数据。


总结

知识点 核心要点
类与对象 类是蓝图/模板,对象是堆内存中的具体实体;类 = 成员变量(属性)+ 成员方法(行为)
new 关键字 三步:声明引用变量(栈)→ 分配堆空间 → 调用构造方法初始化
引用与对象 引用变量存地址,对象实体在堆中;赋值拷贝的是引用,不是对象本身
== vs equals() == 比较地址(同一对象),equals() 比较内容(需重写)
对象内存模型 栈存局部变量/引用,堆存对象实体,方法区存类信息/方法字节码;成员变量各对象独立,成员方法多对象共享
成员变量 vs 局部变量 位置不同(类中方法外 vs 方法内)、内存不同(堆 vs 栈)、生命周期不同、默认值不同
构造方法 只初始化不创建;与类同名无返回类型;一旦自定义构造方法,默认无参构造消失;支持重载;this(...) 实现构造器链式调用
this 关键字 隐式传入当前对象的引用;消除参数与字段的命名歧义;静态方法中不存在;不可被重新赋值

💡 核心结论: 类定义了对象的"长相"和"行为",new 在堆中把蓝图变成实物,构造方法为实物填充初始数据,this 确保每个方法都知道自己在操作哪个对象。理解这四者的关系,就理解了 Java 面向对象编程的基石。

💡 关键提醒: 始终牢记引用与对象的分离------一个引用变量只是一个"遥控器",多个遥控器可以指向同一个对象;构造方法的第一职责是初始化而非创建;在构造方法和 Setter 中坚持使用 this.字段 = 参数 的命名惯例,可以有效避免字段遮蔽带来的隐蔽 Bug。

相关推荐
欢璃1 小时前
表白墙案例
java·开发语言·jvm·spring boot·spring·maven·mybatis
IT知识分享1 小时前
数字上标、下标如何打,6种常用方法详解
开发语言·c#·xhtml
‎ദ്ദിᵔ.˛.ᵔ₎1 小时前
Java 匿名内部类与 Lambda 表达式
java
hhhhhaaa1 小时前
Java 并发编程核心原理与生产级最佳实践
java·后端
qwert10371 小时前
深入解析Python标识符:定义、规则、规范与实践指南
开发语言·数据库·python
cqwuliu1 小时前
Freemarker模板工具
java·开发语言
学习,学习,在学习1 小时前
Qt多线程的使用与注意事项
开发语言·数据库·qt
asdfg12589631 小时前
`(line1, line2) -> line1 + line2` 此Lambda 表达式的理解
java·开发语言
如竟没有火炬1 小时前
去除重复字母——贪心+单调栈
开发语言·数据结构·python·算法·leetcode·深度优先