Java面向对象入门:类与对象、构造方法与this关键字
文章目录
- Java面向对象入门:类与对象、构造方法与this关键字
-
- 前言
- 一、面向对象的基本概念
-
- [1.1 面向过程 vs 面向对象](#1.1 面向过程 vs 面向对象)
- [1.2 面向对象的三大特性(预习)](#1.2 面向对象的三大特性(预习))
- 二、类与对象
-
- [2.1 什么是类?什么是对象?](#2.1 什么是类?什么是对象?)
- [2.2 定义一个类](#2.2 定义一个类)
- [2.3 创建与使用对象](#2.3 创建与使用对象)
- [2.4 对象的内存分析](#2.4 对象的内存分析)
- 三、成员变量与局部变量
- 四、构造方法
-
- [4.1 什么是构造方法?](#4.1 什么是构造方法?)
- [4.2 构造方法的注意事项](#4.2 构造方法的注意事项)
- [4.3 构造方法的重载](#4.3 构造方法的重载)
- 五、this关键字
-
- [5.1 this是什么?](#5.1 this是什么?)
- [5.2 this的三种用法](#5.2 this的三种用法)
- 六、封装思想的初探
- 七、综合案例------简易通讯录系统
- 总结
- [✅ 亮点总结](#✅ 亮点总结)
- 适用场景
- 扩展方向
前言
Java是一门纯粹的面向对象编程语言 。面向对象编程(OOP,Object-Oriented Programming)不是简单的语法特性,而是一种编程思想------将现实世界的事物抽象为程序中的"对象",用对象之间的交互来解决问题。理解面向对象是成为Java开发者的必经之路。
很多从C语言转过来的程序员刚开始接触Java时最大的困惑就是"为什么一切都要封装到类里?",这是因为面向对象的核心优势在于代码复用 和维护性------当项目规模达到数万行时,面向过程的函数式写法会变得极难维护,而面向对象通过合理的类设计能让代码结构保持清晰。学完本文你将能够:独立定义类和创建对象、理解引用变量和堆内存的关系、正确使用构造方法初始化对象、用this解决变量同名问题。本文将从面向对象的核心概念开始,讲解类与对象的定义、对象的创建和使用、构造方法、this关键字以及对象的内存分析。
一、面向对象的基本概念
1.1 面向过程 vs 面向对象
在了解面向对象之前,我们先看看它的对比范式------面向过程:
| 维度 | 面向过程 | 面向对象 |
|---|---|---|
| 核心思想 | 关注过程/步骤 | 关注对象/实体 |
| 程序单元 | 函数/方法 | 类/对象 |
| 数据管理 | 数据与操作分离 | 数据与操作封装在一起 |
| 典型语言 | C语言 | Java、C++ |
| 适用场景 | 简单任务、算法实现 | 大型项目、复杂业务 |
举例说明:用洗衣机洗衣服
- 面向过程思维:打开洗衣机 → 放衣服 → 加洗衣液 → 选择模式 → 启动 → 等待 → 取出
- 面向对象思维:洗衣机对象(含洗衣方法)、衣服对象、洗衣液对象。洗衣机.洗衣服(衣服, 洗衣液)
1.2 面向对象的三大特性(预习)
| 特性 | 说明 | 关键词 |
|---|---|---|
| 封装 | 隐藏内部实现,对外暴露接口 | private、getter/setter |
| 继承 | 子类继承父类的属性和方法 | extends、父子关系 |
| 多态 | 同一行为的不同表现形式 | 方法重写、向上转型 |
本文重点讲类和对象的基础,下一章将详细展开三大特性的讲解。
二、类与对象
2.1 什么是类?什么是对象?
- 类(Class) :是对一类事物的抽象描述,是创建对象的模板/蓝图
- 对象(Object) :是类的具体实例,是一个真实存在的实体
类比理解:类 是"汽车设计图纸",对象是根据图纸造出的"一辆真实的汽车"。
2.2 定义一个类
一个标准的Java类包含属性(成员变量)和行为(成员方法)。在设计一个类时,我们需要思考:这个事物有哪些特征(属性)?它能做什么事情(行为)?这其实是面向对象分析(OOA)的核心------将现实世界的事物特征和功能映射到代码中。
举个例子:学生有姓名、年龄、学号等属性(用成员变量描述),有学习、考试等行为(用成员方法描述)。下面是一个标准的Java类定义:
java
/**
* 学生类------描述一个学生应该有哪些属性和行为
*/
public class Student {
// ====== 属性(成员变量)======
String name; // 姓名
int age; // 年龄
String studentId; // 学号
double score; // 成绩
// ====== 行为(成员方法)======
public void study() {
System.out.println(name + "正在努力学习...");
}
public void exam() {
System.out.println(name + "正在参加考试...");
}
public void showInfo() {
System.out.println("姓名:" + name);
System.out.println("学号:" + studentId);
System.out.println("年龄:" + age);
System.out.println("成绩:" + score);
}
}
2.3 创建与使用对象
定义好类只是画好了"图纸",接下来需要根据"图纸"创建真正的"汽车"------也就是实例化对象 。创建对象的核心关键字是new,它会做三件事:①在堆内存中分配空间;②调用构造方法初始化对象的属性;③返回对象的引用地址。
使用对象时需要注意区分.操作符的两个用途:访问属性用对象名.属性名,调用方法用对象名.方法名()。初学者容易忘记方法调用时的括号,导致编译错误。
java
public class StudentTest {
public static void main(String[] args) {
// 创建对象:使用new关键字
// 类名 对象名 = new 类名();
Student stu1 = new Student();
Student stu2 = new Student();
// 访问属性并赋值
stu1.name = "张三";
stu1.age = 20;
stu1.studentId = "2024001";
stu1.score = 88.5;
stu2.name = "李四";
stu2.age = 21;
stu2.studentId = "2024002";
stu2.score = 92.0;
// 调用方法
stu1.showInfo();
System.out.println("------------------------");
stu2.showInfo();
System.out.println("------------------------");
stu1.study();
stu2.exam();
}
}
常见错误 :很多初学者会写出
Student stu1;然后直接调用stu1.study(),此时stu1还没有指向任何对象(值为null),运行时会抛出NullPointerException(空指针异常) ------这是Java开发中最常见的异常之一。必须先new出来才能使用。
2.4 对象的内存分析
了解对象在内存中的存储方式,对于理解Java运行机制非常重要。JVM内存主要分为:
- 栈(Stack) :存储局部变量 和方法调用信息,方法执行完毕自动释放
- 堆(Heap) :存储new出来的对象和数组,由GC(垃圾回收器)管理
- 方法区(Method Area) :存储类的信息(.class文件)、静态变量、常量池
java
// 内存分析示例
Student s1 = new Student(); // 在堆中创建一个Student对象,s1(在栈中)存其地址
s1.name = "王五"; // 通过地址找到堆中的对象,修改其name属性
Student s2 = s1; // s2(在栈中)也存储同一个地址,s1和s2指向同一个对象
s2.name = "赵六"; // 修改的是同一个对象
System.out.println(s1.name); // 输出"赵六",因为s1和s2指向同一对象
关键理解 :
s1和s2是引用变量,存在栈中,存储的是堆中对象的地址。new Student()创建的对象存在堆中。
面试常见问题 :"Java是值传递还是引用传递?"------这个问题的答案与本节的内存模型直接相关。Java总是值传递 ,但当传递的参数是引用类型时,传递的值是引用的副本 (即对象地址的拷贝)。这意味着在被调方法中修改对象的属性会影响到原对象(因为地址指向同一个堆中的对象),但修改引用本身(重新指向新对象)不会影响原引用。理解了Student s2 = s1的内存模型(两个栈变量指向同一个堆对象),这个问题就不需要死记硬背了。
三、成员变量与局部变量
成员变量 和局部变量看似都是"变量",但它们在内存中的位置、生命周期和作用范围完全不同。理解这个区别对后续学习继承、多态中的变量访问规则非常重要。特别是"同名变量屏蔽"的现象,是面试中经常被问到的细节。
核心记忆口诀:成员变量------类中方法外,有默认值,跟对象走;局部变量------方法内,无默认值,必须初始化。
java
public class VariableScope {
// 成员变量:定义在类中,方法外
// 有默认值,生命周期与对象同步
String name; // 默认值 null
int age; // 默认值 0
public void method() {
// 局部变量:定义在方法内
// 没有默认值,必须手动初始化
int x = 10;
String localStr = "我是局部变量";
// 局部变量可以和成员变量同名?
String name = "局部name"; // 可以,但会屏蔽成员变量
System.out.println("局部name:" + name); // 局部name
System.out.println("成员name:" + this.name); // 成员name(null)
}
public void anotherMethod() {
// 这里不能访问method()中的x和localStr
System.out.println(name); // 可以访问成员变量
}
}
| 区别 | 成员变量 | 局部变量 |
|---|---|---|
| 定义位置 | 类中,方法外 | 方法内或代码块内 |
| 默认值 | 有(0、null等) | 无,必须初始化 |
| 内存位置 | 堆内存 | 栈内存 |
| 生命周期 | 随对象存在 | 随方法调用结束 |
| 作用范围 | 整个类 | 所属的方法/代码块 |
四、构造方法
4.1 什么是构造方法?
构造方法(Constructor)是一种特殊的方法,在创建对象时自动调用 ,用于初始化对象的属性 。可以理解为对象的"出厂设置"------每当你用new创建一个对象,构造方法就会执行一次,确保对象一诞生就处在合理状态。
构造方法与普通方法最大的区别:①方法名必须等于类名且区分大小写;②绝对不能写返回值类型(连void都不能写);③只能用new关键字触发调用,不能像普通方法那样手动调用。
java
public class Person {
String name;
int age;
// 构造方法的定义格式:
// public 类名(参数列表) { 初始化代码 }
// 无参构造方法(默认构造方法)
public Person() {
System.out.println("无参构造方法被调用了!");
name = "未知";
age = 0;
}
// 有参构造方法
public Person(String n, int a) {
System.out.println("有参构造方法被调用了!");
name = n;
age = a;
}
public void show() {
System.out.println("姓名:" + name + ",年龄:" + age);
}
public static void main(String[] args) {
Person p1 = new Person(); // 调用无参构造
p1.show(); // 姓名:未知,年龄:0
Person p2 = new Person("张三", 25); // 调用有参构造
p2.show(); // 姓名:张三,年龄:25
}
}
4.2 构造方法的注意事项
构造方法虽然是"特殊方法",但它仍然遵循方法重载的规则。以下是需要牢记的五个要点,特别是第3和第4条------这是新手最容易踩的坑:
- 构造方法名必须与类名完全相同
- 构造方法没有返回值 ,连
void都不能写 - 如果没有定义 任何构造方法,Java会自动提供默认的无参构造方法
- 如果定义了任意一个构造方法 ,默认的无参构造方法就消失了,需要手动定义
- 构造方法支持重载
第4条的含义是:假设你写了一个带有参数String name的构造方法用于创建Person对象,但又想用new Person()创建无需指定姓名的对象,编译器会报错------因为默认无参构造在你定义了任何构造方法之后就被移除了。解决方案就是手动再写一个无参构造,这是很多框架(如Spring、MyBatis)要求实体类必须有无参构造的原因。
java
public class ConstructorDemo {
String name;
// 如果只写了这个有参构造,默认无参构造就没了
public ConstructorDemo(String name) {
this.name = name;
}
public static void main(String[] args) {
ConstructorDemo obj1 = new ConstructorDemo("测试"); // 正确
// ConstructorDemo obj2 = new ConstructorDemo(); // 编译错误!无参构造没了
}
}
实际开发建议 :在编写POJO(Plain Old Java Object)或实体类时,养成同时提供无参构造和全参构造的习惯。无参构造是框架反射创建对象的必要条件,全参构造方便测试和业务代码中快速构建对象。使用Lombok的
@NoArgsConstructor和@AllArgsConstructor可以自动生成,但理解手动写法是基础。
4.3 构造方法的重载
构造方法重载是一种常见的代码优化技巧------多个构造方法通过this()链式调用,可以避免重复编写初始化逻辑。这种设计模式的核心思想是"单一职责":最全参数的构造方法负责所有初始化工作,其他构造方法只需调用它并传入默认值。
java
public class BankAccount {
String accountNo;
String owner;
double balance;
// 无参构造:创建默认账户
public BankAccount() {
this("00000000", "匿名用户", 0.0);
}
// 两个参数:开户必备
public BankAccount(String accountNo, String owner) {
this(accountNo, owner, 0.0); // 通过this调用另一个构造方法
}
// 三个参数:完整开户
public BankAccount(String accountNo, String owner, double balance) {
this.accountNo = accountNo;
this.owner = owner;
this.balance = balance;
}
public void showAccount() {
System.out.println("账号:" + accountNo);
System.out.println("户主:" + owner);
System.out.println("余额:" + String.format("%.2f", balance));
}
public static void main(String[] args) {
BankAccount acc1 = new BankAccount();
BankAccount acc2 = new BankAccount("6222-001", "张三");
BankAccount acc3 = new BankAccount("6222-002", "李四", 10000.0);
acc1.showAccount();
System.out.println("---");
acc2.showAccount();
System.out.println("---");
acc3.showAccount();
}
}
五、this关键字
5.1 this是什么?
this是Java中的一个引用变量 ,它指向当前对象 ------谁调用这个方法,this就指向谁。理解this的本质是理解面向对象的关键一步:在Java中,同一个类的多个对象各自拥有独立的成员变量(存储在各自的堆内存区域),但它们共享同一份方法代码(存储在方法区)。那么方法执行时如何知道要操作哪个对象的数据?答案是:每个非静态方法都隐式携带了一个this参数,指向调用它的那个对象。
面试中常问:"this能出现在静态方法中吗?"------不能,因为静态方法属于类而非对象,调用时根本没有"当前对象"这个概念。
java
public class ThisDemo {
String name;
// 使用this区分成员变量和局部变量
public void setName(String name) {
this.name = name; // this.name是成员变量,右边的name是局部变量
}
public String getName() {
return this.name; // 这里的this可以省略
}
public void printAddress() {
System.out.println("当前对象的地址:" + this);
}
public static void main(String[] args) {
ThisDemo obj1 = new ThisDemo();
ThisDemo obj2 = new ThisDemo();
obj1.printAddress(); // this指向obj1
obj2.printAddress(); // this指向obj2
obj1.setName("对象1");
obj2.setName("对象2");
System.out.println(obj1.getName()); // 对象1
System.out.println(obj2.getName()); // 对象2
}
}
5.2 this的三种用法
java
public class ThisUsage {
private String name;
private int age;
// 用法1:this.成员变量 ← 区分同名的成员变量和局部变量
public void setName(String name) {
this.name = name;
}
// 用法2:this.成员方法 ← 调用本类的其他方法
public void init() {
this.setName("默认用户"); // this可省略
this.setAge(18); // 等价于直接调用setAge(18)
}
public void setAge(int age) {
this.age = age;
}
// 用法3:this()构造方法 ← 在构造方法中调用另一个构造方法
public ThisUsage() {
this("未知", 0); // 必须写在本构造方法的第一行
System.out.println("无参构造完成");
}
public ThisUsage(String name) {
this(name, 0); // 调用两个参数的构造方法
}
public ThisUsage(String name, int age) {
this.name = name;
this.age = age;
}
public void show() {
System.out.println("姓名:" + name + ",年龄:" + age);
}
public static void main(String[] args) {
ThisUsage u1 = new ThisUsage(); // 姓名:未知,年龄:0
ThisUsage u2 = new ThisUsage("张三"); // 姓名:张三,年龄:0
ThisUsage u3 = new ThisUsage("李四", 30); // 姓名:李四,年龄:30
u1.show();
u2.show();
u3.show();
}
}
注意 :
this()调用其他构造方法时,必须写在构造方法的第一行,且不能形成循环调用。
六、封装思想的初探
虽然封装是下一章的重点,但这里先感受一下。封装 的核心思想是:将数据(属性)和对数据的操作(方法)绑定到一起,同时限制外部对内部数据的直接访问。未封装的数据就像敞开大门的房子,任何人都可以进去修改你的东西。封装后的数据则设置了"门禁系统"------你需要通过getter/setter方法来访问,而这些方法里可以加入验证逻辑。
使用private修饰属性后,外部代码不能直接用对象.属性的方式访问。这带来的好处是:如果有一天你需要修改属性名或添加验证规则,只需要修改getter/setter内部实现,所有调用者不受影响------这就是"高内聚、低耦合"的体现。
java
public class Product {
// 使用private修饰属性,外部不能直接访问
private String name;
private double price;
private int stock;
// 提供public的getter和setter方法来访问属性
public String getName() {
return name;
}
public void setName(String name) {
if (name != null && !name.trim().isEmpty()) {
this.name = name;
} else {
System.out.println("商品名称不能为空!");
}
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
if (price > 0) {
this.price = price;
} else {
System.out.println("价格必须大于0!");
}
}
public int getStock() {
return stock;
}
public void setStock(int stock) {
if (stock >= 0) {
this.stock = stock;
} else {
System.out.println("库存不能为负数!");
}
}
public static void main(String[] args) {
Product product = new Product();
product.setName("笔记本电脑");
product.setPrice(5999.00);
product.setStock(100);
// product.price = -100; // 编译错误!price是private的
product.setPrice(-100); // 会被setter中的校验拦截
System.out.println("商品:" + product.getName()
+ ",价格:" + product.getPrice()
+ ",库存:" + product.getStock());
}
}
七、综合案例------简易通讯录系统
java
public class Contact {
private String name;
private String phone;
private String email;
public Contact() {}
public Contact(String name, String phone, String email) {
this.name = name;
this.phone = phone;
this.email = email;
}
// getter和setter
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public void display() {
System.out.printf("姓名:%s | 电话:%s | 邮箱:%s%n", name, phone, email);
}
public static void main(String[] args) {
Contact[] contacts = new Contact[3];
contacts[0] = new Contact("张三", "138-0000-0001", "zhangsan@mail.com");
contacts[1] = new Contact("李四", "138-0000-0002", "lisi@mail.com");
contacts[2] = new Contact("王五", "138-0000-0003", "wangwu@mail.com");
System.out.println("===== 通讯录 =====");
for (Contact c : contacts) {
c.display();
}
// 使用构造方法重载创建新联系人
Contact newContact = new Contact();
newContact.setName("赵六");
newContact.setPhone("138-0000-0004");
newContact.setEmail("zhaoliu@mail.com");
System.out.println("\n===== 新增联系人 =====");
newContact.display();
}
}
总结
本文是Java面向对象编程的入门篇章,也是整个Java学习路线中最基础也最重要的内容。我们学习了以下核心内容:
- 面向对象概念:对比面向过程,理解OOP三大特性的雏形。面向过程关注"怎么做"(步骤),面向对象关注"谁来做"(对象),这个思维转变是编程能力的质变。
- 类与对象 :类是模板,对象是实例,用
new关键字创建对象。new做了三件事:堆内存分配、构造方法调用、引用地址返回。理解这一点对后续内存分析至关重要。 - 成员变量与局部变量:定义位置、默认值、生命周期、作用域的区别。成员变量有默认值(int为0,引用类型为null),局部变量必须显式初始化;成员变量在堆中,局部变量在栈中。
- 构造方法 :在创建对象时自动调用,支持重载,用
this()复用构造逻辑。没有返回值、方法名与类名相同------这两条规则是区分构造方法与普通方法的根本标志。 - this关键字:指向当前对象的引用,用于区分同名变量、调用成员方法和构造方法。静态方法中没有this,因为静态方法属于类而非对象。
- 封装初探 :用
private隐藏属性,通过getter/setter控制访问。这是"高内聚低耦合"设计原则在代码层面的具体实践。
面向对象是一门需要大量实践才能内化的学问。本文的内容是基础中的基础,只有把这些概念彻底搞懂,才能在下一篇文章的封装、继承、多态学习中游刃有余。建议你将文中的每个代码示例都亲手敲一遍,体会每一行代码的含义。强烈建议亲手运行2.4节的内存分析代码,它是理解Java引用机制的最佳入口。
✅ 亮点总结
- 面向过程 vs 面向对象对比:从把大象装冰箱的分步函数到把大象、冰箱、人抽象为对象,直观理解两种编程范式
- 类与对象的精准比喻 :类是图纸/模板,对象是按照图纸造出来的实体,
new关键字就是"按图纸制造"的动作 - 成员变量与局部变量五大区别:定义位置、默认值、作用域、生命周期、内存位置,表格对比一目了然
- 构造方法重载与
this()链式调用 :无参构造、有参构造、this(参数)复用构造逻辑,避免代码重复 - 封装初探------getter/setter模式 :用
private保护数据,通过getter读取、setter写入实现可控的数据访问
适用场景
- 将现实业务中的"用户""订单""商品"等概念直接映射为Java类,实现业务建模
- 编写JavaBean/DTO/VO等数据传输对象,定义字段和对应的getter/setter方法
- 使用Lombok的
@Data注解自动生成构造方法/getter/setter,体会到手动编写再自动化的学习价值
扩展方向
- 封装继承多态 :面向对象的三大核心特性深入,推荐阅读 07_Java封装继承多态
- UML类图入门:学习用类图描述类与类之间的关系(关联、聚合、组合),提升系统设计能力
- SOLID设计原则:了解单一职责、开闭原则、里氏替换等五大原则,为写出可维护代码打好理论基础