Java面向对象编程(OOP)理论知识详解
一、面向对象编程核心思想
1. 核心概念
面向对象编程(Object-Oriented Programming,简称OOP)是一种以“对象”为核心的编程指导思想,它将现实世界中的事物抽象为程序中的“对象”,每个对象包含描述事物特征的属性(如手机的品牌、价格)和描述事物行为的方法(如手机的打电话、发短信功能)。
这种思想的本质是“模拟现实世界的思维方式组织代码”,相比面向过程(按步骤指令编程),OOP能更好地解决复杂问题,同时提升代码的可维护性(修改一个对象的逻辑不影响其他对象)、复用性(对象可重复使用)和扩展性(新增功能只需新增对象或方法)。
2. 学习重点框架
OOP的学习围绕“数据封装-逻辑组织-功能实现”展开,核心知识点包括:
- 基础层:类和对象的定义与使用、对象内存逻辑、成员变量与局部变量区别
- 核心层:this关键字、构造器、封装机制、JavaBean规范
- 实践层:基于OOP思想的案例开发(如电影信息系统、手机管理系统)
二、类和对象:OOP的基础载体
1. 类的定义:对象的“设计图”
类是一组具有相同属性和行为的对象的抽象描述,相当于“设计图”——例如“手机类”是对所有手机的共性(品牌、价格、打电话功能)的定义,而具体的“小米手机”“华为手机”是根据这个设计图创建的“对象”。
类的组成结构
| 组成部分 | 定义位置 | 作用 | 语法格式 |
|---|---|---|---|
| 成员变量 | 类中、方法外 | 存储对象的属性(特征) | 数据类型 变量名;(如String brand;) |
| 成员方法 | 类中 | 描述对象的行为(操作) | 去掉static关键字的方法(如public void call() {}) |
类的定义代码示例(以Student类为例)
// 定义Student类,描述学生的共性
public class Student {
// 成员变量:学生的属性(姓名、年龄)
String name; // 姓名(默认值为null)
int age; // 年龄(默认值为0)
// 成员方法:学生的行为(学习、吃饭)
public void study() {
// 方法体:描述行为的具体逻辑
System.out.println(name + "正在学习Java");
}
public void eat() {
System.out.println(name + "正在吃饭");
}
}代码解释:
String name;和int age;是成员变量,属于类的“属性”,用于存储每个学生的具体信息(如“张三”“23岁”)。study()和eat()是成员方法,属于类的“行为”,方法体内的逻辑会根据对象的属性动态执行(如调用stu.study()时,会打印“张三正在学习Java”)。
2. 对象的创建与使用:从“设计图”到“实物”
对象是类的具体实例,必须先定义类,才能通过new关键字创建对象。创建对象后,通过“对象名.属性”访问成员变量,通过“对象名.方法()”调用成员方法。
核心语法格式
- 创建对象:
类名 对象名 = new 类名();
(new 类名()会在内存中创建对象实例,对象名存储对象的内存地址,相当于“引用”) 访问成员变量:
- 取值:
对象名.变量名; - 赋值:
对象名.变量名 = 值;
- 取值:
- 调用成员方法:
对象名.方法名(实际参数);(无参数则括号为空)
对象使用代码示例
// 测试类:创建并使用Student对象
public class TestStudent {
public static void main(String[] args) {
// 1. 创建Student对象(根据Student类的设计图实例化)
Student stu = new Student();
// 2. 访问成员变量:赋值(给stu对象的name和age设置具体值)
stu.name = "张三"; // 将“张三”赋值给stu对象的name属性
stu.age = 23; // 将23赋值给stu对象的age属性
// 3. 访问成员变量:取值(打印stu对象的属性值)
System.out.println("姓名:" + stu.name); // 输出:姓名:张三
System.out.println("年龄:" + stu.age); // 输出:年龄:23
// 4. 调用成员方法(执行stu对象的行为)
stu.study(); // 输出:张三正在学习Java
stu.eat(); // 输出:张三正在吃饭
}
}代码解释:
Student stu = new Student();:创建一个Student类型的对象,stu是“对象引用”,指向内存中真正的Student对象实例。stu.name = "张三";:通过引用修改对象的属性值,每个对象的属性是独立的(若再创建Student stu2,修改stu2.name不会影响stu)。stu.study();:调用对象的方法,方法会根据当前对象的属性执行逻辑(若name未赋值,会打印“null正在学习Java”)。
3. 类与对象的关系
- 类是“抽象模板”,对象是“具体实例”:一个类可以创建多个对象(如一个“手机类”可创建“小米手机”“华为手机”等多个对象)。
- 对象的属性值不同,但行为逻辑相同:例如
stu1和stu2都是Student对象,都有study()方法,但stu1.name是“张三”,stu2.name是“李四”。
三、对象内存逻辑:理解引用与实例的关系
Java内存分为元空间、堆内存和栈内存,对象的创建和使用会涉及这三个区域的协作:
- 元空间:存储类的字节码(如
Student.class),包括类的成员变量定义、成员方法逻辑,类加载时只加载一次。 - 堆内存:存储对象的实例(即成员变量的具体值),每个对象在堆中占用独立空间,对象创建时分配内存,对象无引用时会被垃圾回收。
- 栈内存:存储对象引用(如
stu变量)和局部变量,方法调用时在栈中开辟空间,方法结束后栈空间释放。
关键逻辑(以单个对象为例)
- 执行
Student stu = new Student();时,JVM先加载Student.class到元空间。 - 在堆内存中创建Student对象实例,成员变量
name默认值为null,age默认值为0。 - 在栈内存中创建
stu引用变量,存储堆内存中对象的地址(如0x0011)。 - 执行
stu.name = "张三";时,通过stu的地址找到堆中的对象,修改name的值为“张三”。 - 执行
stu.study();时,通过stu找到对象,再根据对象中的方法引用,调用元空间中study()的逻辑。
四、成员变量与局部变量的区别
在类中,变量按定义位置分为“成员变量”和“局部变量”,二者在初始化、内存、生命周期等方面有本质区别,是OOP中容易混淆的知识点:
| 区别维度 | 成员变量 | 局部变量 |
|---|---|---|
| 类中位置 | 类中、方法外 | 方法内、方法参数、代码块内 |
| 初始化值 | 有默认初始化值(按数据类型:int=0、String=null、double=0.0) | 无默认值,必须手动赋值后才能使用(否则编译报错) |
| 内存位置 | 堆内存(跟随对象存储) | 栈内存(跟随方法/代码块存储) |
| 生命周期 | 随对象创建而存在,随对象被垃圾回收而消失 | 随方法调用/代码块执行而存在,随方法结束/代码块执行完而消失 |
| 作用域 | 整个类(所有非静态方法可访问) | 仅在定义它的方法/代码块内可访问 |
代码示例(对比二者区别)
public class VariableDemo {
// 成员变量:类中、方法外,有默认值
int memberVar;
public void testMethod(int paramVar) { // paramVar是局部变量(方法参数)
// 局部变量:方法内,无默认值,必须赋值
int localVar = 10;
// 1. 成员变量可直接使用(默认值0)
System.out.println(memberVar); // 输出:0
// 2. 局部变量必须赋值后使用(注释掉localVar=10会编译报错)
System.out.println(localVar); // 输出:10
// 3. 方法参数也是局部变量,可直接使用(由调用者传值)
System.out.println(paramVar); // 输出:调用方法时传入的值
}
public static void main(String[] args) {
VariableDemo demo = new VariableDemo();
demo.testMethod(20); // 传入paramVar的值为20
}
}五、this关键字:解决变量重名问题
1. 核心作用
当局部变量与成员变量重名时,Java会遵循“就近原则”(优先访问局部变量),此时需要用this关键字明确指定“访问当前对象的成员变量”。
this的本质是“当前对象的引用”——哪个对象调用包含this的方法,this就指向哪个对象。
代码示例(this解决重名问题)
public class ThisDemo {
// 成员变量
String name;
int age;
// 带参方法:局部变量name、age与成员变量重名
public void setInfo(String name, int age) {
// 若不写this:name和age是局部变量,无法修改成员变量
this.name = name; // this.name:当前对象的成员变量name
this.age = age; // this.age:当前对象的成员变量age
}
public void showInfo() {
// 无重名时,this可省略(默认访问成员变量)
System.out.println("姓名:" + this.name + ",年龄:" + this.age);
}
public static void main(String[] args) {
ThisDemo demo = new ThisDemo();
demo.setInfo("张三", 23); // 调用方法时,this指向demo对象
demo.showInfo(); // 输出:姓名:张三,年龄:23
}
}代码解释:
setInfo(String name, int age)中,name和age是局部变量(方法参数),与成员变量重名。this.name = name:将局部变量name的值(“张三”)赋值给当前对象的成员变量name,明确区分了二者。- 即使无重名(如
showInfo()中的this.name),this也可省略,但写this能更清晰地表示“访问成员变量”。
2. 拓展:this的隐含使用
在成员方法中,即使未显式写this,JVM也会自动将this作为方法的第一个参数传入。例如demo.setInfo("张三",23),实际执行时JVM会处理为setInfo(demo, "张三",23),this就是demo对象的引用。
六、构造器:对象的“初始化工具”
1. 核心概念
构造器(Constructor)是类中一种特殊的方法,专门用于创建对象时初始化对象的属性,确保对象创建后就具有合理的初始状态(避免成员变量为默认值的“无效状态”)。
构造器的语法规则
- 构造器名必须与类名完全相同(大小写一致),如
Student类的构造器名必须是Student。 - 无返回值类型(连
void都不写)——这是构造器与普通方法的核心区别。 - 不能用
return带回具体值(但可写return;表示提前结束构造器)。
2. 构造器的核心特性
(1)默认构造器
若类中未显式定义任何构造器,JVM会自动提供一个无参构造器(空方法体),格式为:
public 类名() {} // 如Student类的默认构造器:public Student() {}(2)构造器的重载
构造器支持重载(多个构造器,参数列表不同:参数个数、类型、顺序不同),用于满足不同的初始化需求(如无参初始化、带部分属性初始化、带全部属性初始化)。
(3)手动定义构造器的注意事项
若手动定义了构造器(无论有无参数),JVM将不再提供默认无参构造器。因此推荐手动定义无参构造器和带参构造器,避免其他代码创建对象时报错。
3. 构造器的使用代码示例
public class ConstructorDemo {
// 成员变量
String name;
int age;
// 1. 手动定义无参构造器(推荐)
public ConstructorDemo() {
// 可在无参构造器中设置默认值
this.name = "未知姓名";
this.age = 0;
}
// 2. 带参构造器(重载:参数列表与无参构造器不同)
public ConstructorDemo(String name, int age) {
// 初始化成员变量(用this区分局部变量和成员变量)
this.name = name;
this.age = age;
}
public void showInfo() {
System.out.println("姓名:" + name + ",年龄:" + age);
}
public static void main(String[] args) {
// 调用无参构造器创建对象(属性为默认值)
ConstructorDemo demo1 = new ConstructorDemo();
demo1.showInfo(); // 输出:姓名:未知姓名,年龄:0
// 调用带参构造器创建对象(属性为传入的值)
ConstructorDemo demo2 = new ConstructorDemo("张三", 23);
demo2.showInfo(); // 输出:姓名:张三,年龄:23
}
}代码解释:
new ConstructorDemo();:调用无参构造器,对象的name被初始化为“未知姓名”,age为0。new ConstructorDemo("张三",23);:调用带参构造器,对象的name和age被初始化为传入的“张三”和23。- 构造器的核心价值是“对象创建即初始化”,避免创建对象后还要手动调用
set方法赋值的繁琐操作。
七、封装:OOP的核心特性之一
1. 核心思想
封装(Encapsulation)的本质是“合理隐藏,合理暴露”——将对象的内部属性(成员变量)隐藏起来,不允许外部直接访问,仅通过公开的方法(getter和setter)控制对属性的访问,从而保护数据的安全性和完整性。
例如现实中的“手机”:用户无需知道手机内部的芯片、电路(隐藏),只需通过屏幕、按键(暴露的接口)使用手机功能——这就是封装的思想。
2. 封装的实现步骤
步骤1:用private修饰成员变量(隐藏属性)
private是Java的权限修饰符,被private修饰的成员变量,仅在当前类中可访问,外部类无法直接通过“对象名.属性”访问(编译报错)。
步骤2:提供public的getter和setter方法(暴露接口)
getter方法:用于“获取”成员变量的值,命名格式为public 数据类型 get属性名() {}(如getAge())。setter方法:用于“修改”成员变量的值,命名格式为public void set属性名(数据类型 参数) {}(如setAge(int age))。
步骤3:在setter方法中添加校验逻辑(保护数据)
通过setter方法可以对传入的值进行校验,避免非法数据(如年龄不能为负数、姓名不能为空),这是封装的核心价值之一。
3. 封装的代码示例(以Person类为例)
public class Person {
// 步骤1:用private隐藏成员变量(外部无法直接访问)
private String name; // 姓名
private int age; // 年龄
// 步骤2:提供getter方法(获取name的值)
public String getName() {
return name; // 返回成员变量name的值
}
// 步骤2:提供setter方法(修改name的值,添加校验)
public void setName(String name) {
// 校验逻辑:姓名不能为null或空字符串
if (name != null && !name.trim().isEmpty()) {
this.name = name; // 校验通过,赋值给成员变量
} else {
System.out.println("姓名不能为空!"); // 校验失败,提示错误
}
}
// 步骤2:提供getter方法(获取age的值)
public int getAge() {
return age;
}
// 步骤2:提供setter方法(修改age的值,添加校验)
public void setAge(int age) {
// 校验逻辑:年龄必须在0~150之间
if (age >= 0 && age <= 150) {
this.age = age;
} else {
System.out.println("年龄必须在0~150之间!");
}
}
}
// 测试封装效果
class TestPerson {
public static void main(String[] args) {
Person person = new Person();
// 1. 无法直接访问private成员变量(编译报错)
// person.name = "张三"; // 错误:name has private access in Person
// 2. 通过setter方法修改属性(会执行校验)
person.setName(""); // 输出:姓名不能为空!(校验失败,name未修改)
person.setName("张三"); // 校验通过,name被赋值为“张三”
person.setAge(-5); // 输出:年龄必须在0~150之间!(校验失败)
person.setAge(23); // 校验通过,age被赋值为23
// 3. 通过getter方法获取属性值
System.out.println("姓名:" + person.getName()); // 输出:姓名:张三
System.out.println("年龄:" + person.getAge()); // 输出:年龄:23
}
}代码解释:
private String name;:name被隐藏,外部无法直接修改,避免了“直接赋值非法值”(如person.name = "")。setName(String name):通过校验逻辑确保姓名不为空,只有合法值才能赋值给name。getName():外部只能通过该方法获取name的值,无法修改——实现了“只读”的控制(若只提供getter不提供setter,则属性为只读)。
4. 权限修饰符补充
Java有4种权限修饰符,用于控制成员的访问范围,封装的实现依赖于private和public:
| 修饰符 | 本类 | 同一包中的类 | 子孙类 | 任意类 | 核心用途 |
|---|---|---|---|---|---|
| private | √ | 隐藏类的内部成员(如成员变量) | |||
| 缺省(无) | √ | √ | 包内可见(一般用于内部类协作) | ||
| protected | √ | √ | √ | 子孙类可见(继承时使用) | |
| public | √ | √ | √ | √ | 完全公开(如getter、setter方法) |
八、JavaBean规范:标准化的数据封装类
1. 核心定义
JavaBean是一种符合特定规范的Java类,专门用于封装数据(如用户信息、商品信息、电影信息),是企业级开发中“数据传输载体”的标准形式(如前后端交互、数据库映射的实体类)。
2. JavaBean的规范要求
必须满足以下3个条件,才能称为标准的JavaBean:
- 成员变量私有:所有成员变量用
private修饰(隐藏数据)。 - 提供无参构造器:必须有一个
public的无参构造器(便于框架动态创建对象,如Spring、MyBatis)。 - 提供getter/setter:为每个成员变量提供
public的getter(获取值)和setter(修改值)方法,方法命名严格遵循“get属性名”“set属性名”(首字母大写)。
补充:可选规范
- 实现
Serializable接口:支持对象的序列化(便于网络传输或持久化到文件)。 - 成员变量类型为基本类型或包装类(如
int/Integer、String),避免复杂引用类型。
3. JavaBean的代码示例(以Movie类为例)
// 标准JavaBean:封装电影数据(id、名称、价格)
public class Movie implements java.io.Serializable { // 实现Serializable(可选)
// 1. 成员变量私有
private int id; // 电影ID
private String name; // 电影名称
private double price; // 电影价格
// 2. 无参构造器(必须)
public Movie() {
}
// 3. 带参构造器(可选,便于快速初始化)
public Movie(int id, String name, double price) {
this.id = id;
this.name = name;
this.price = price;
}
// 3. getter方法:获取成员变量值
public int getId() {
return id;
}
// 3. setter方法:修改成员变量值
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}代码解释:
implements Serializable:支持序列化,框架(如Redis)可将Movie对象转为字节流存储或传输。- 无参构造器:即使有带参构造器,也必须手动定义无参构造器(否则JVM不提供),避免框架创建对象时报错。
getter/setter:命名严格遵循规范(如getId()、setName()),框架会通过反射自动调用这些方法获取/修改数据。
4. JavaBean的应用场景
JavaBean的核心用途是“数据载体”,常见场景包括:
- 数据库表映射:JavaBean的成员变量对应数据库表的字段(如
Movie类对应movie表的id、name、price字段)。 - 前后端数据交互:后端将数据封装为JavaBean,转为JSON格式传给前端(如接口返回的“电影列表”是
List<Movie>)。 - 框架中的数据传递:如SpringMVC的方法参数、MyBatis的查询结果,都依赖JavaBean的规范实现自动封装。
九、OOP实战案例:简易电影信息系统(核心代码)
基于上述OOP知识,实现一个简易的电影信息系统,核心功能:存储电影数据、打印所有电影信息、根据ID查询电影。
1. 案例结构
Movie:JavaBean类(封装电影数据)。MovieOperation:业务类(管理电影数组,提供打印、查询功能)。TestMovieOperation:测试类(创建数据,调用业务方法)。
2. 核心代码实现与解释
(1)Movie类(JavaBean,已在第八章示例)
// 省略,同第八章JavaBean示例代码(封装id、name、price)(2)MovieOperation类(业务逻辑封装)
import java.util.Scanner;
public class MovieOperation {
// 成员变量:存储电影数组(private隐藏,通过构造器初始化)
private Movie[] movies;
// 带参构造器:接收外部传入的Movie数组,初始化成员变量
public MovieOperation(Movie[] movies) {
this.movies = movies; // 将外部数组赋值给内部成员变量
}
// 功能1:打印所有电影信息
public void printAllMovies() {
// 校验:若数组为null或空,提示无数据
if (movies == null || movies.length == 0) {
System.out.println("暂无电影数据!");
return;
}
// 遍历数组,打印每个电影的信息(通过getter获取属性)
System.out.println("=== 所有电影信息 ===");
for (Movie movie : movies) {
System.out.println("电影ID:" + movie.getId() +
",名称:" + movie.getName() +
",价格:" + movie.getPrice() + "元");
}
}
// 功能2:根据ID查询电影
public void findMovieById() {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入要查询的电影ID:");
int targetId = scanner.nextInt();
// 校验数组
if (movies == null || movies.length == 0) {
System.out.println("暂无电影数据!");
scanner.close();
return;
}
// 遍历数组查询
boolean isFound = false;
for (Movie movie : movies) {
// 通过getter获取电影ID,与目标ID比较
if (movie.getId() == targetId) {
System.out.println("=== 找到电影 ===");
System.out.println("ID:" + movie.getId() +
",名称:" + movie.getName() +
",价格:" + movie.getPrice() + "元");
isFound = true;
break; // 找到后退出循环,避免冗余遍历
}
}
// 未找到时提示
if (!isFound) {
System.out.println("未找到ID为" + targetId + "的电影!");
}
scanner.close();
}
// getter/setter:提供电影数组的访问接口(可选)
public Movie[] getMovies() {
return movies;
}
public void setMovies(Movie[] movies) {
this.movies = movies;
}
}代码解释:
private Movie[] movies;:用private隐藏电影数组,外部无法直接修改,通过构造器初始化(依赖注入)。printAllMovies():遍历数组,通过movie.getXXX()获取属性值(遵循封装原则,不直接访问movie的私有成员变量)。findMovieById():通过用户输入的ID遍历查询,找到后打印信息,未找到则提示——体现了“业务逻辑封装”(查询逻辑集中在该方法中,便于维护)。
(3)TestMovieOperation类(测试)
public class TestMovieOperation {
public static void main(String[] args) {
// 1. 创建6个Movie对象(通过带参构造器快速初始化)
Movie m1 = new Movie(1, "流浪地球2", 59.9);
Movie m2 = new Movie(2, "满江红", 39.9);
Movie m3 = new Movie(3, "哪吒之魔童降世", 45.0);
Movie m4 = new Movie(4, "唐人街探案3", 35.0);
Movie m5 = new Movie(5, "少年的你", 42.0);
Movie m6 = new Movie(6, "我不是药神", 48.0);
// 2. 将Movie对象存入数组
Movie[] movieArray = {m1, m2, m3, m4, m5, m6};
// 3. 创建MovieOperation对象(传入电影数组初始化)
MovieOperation operation = new MovieOperation(movieArray);
// 4. 调用业务方法测试功能
operation.printAllMovies(); // 打印所有电影
System.out.println(); // 换行
operation.findMovieById(); // 根据ID查询电影
}
}代码解释:
- 测试类的核心是“创建数据+调用业务方法”,不包含业务逻辑——符合OOP的“单一职责原则”(测试类只负责测试,业务类只负责业务逻辑)。
new MovieOperation(movieArray):通过构造器将电影数组传入业务类,实现“数据与逻辑分离”(若需更换电影数据,只需修改movieArray,无需修改MovieOperation)。
3. 案例运行结果(控制台输出)
=== 所有电影信息 ===
电影ID:1,名称:流浪地球2,价格:59.9元
电影ID:2,名称:满江红,价格:39.9元
电影ID:3,名称:哪吒之魔童降世,价格:45.0元
电影ID:4,名称:唐人街探案3,价格:35.0元
电影ID:5,名称:少年的你,价格:42.0元
电影ID:6,名称:我不是药神,价格:48.0元
请输入要查询的电影ID:3
=== 找到电影 ===
ID:3,名称:哪吒之魔童降世,价格:45.0元十、OOP核心知识总结
| 知识点 | 核心作用 | 关键注意事项 |
|---|---|---|
| 类与对象 | 类是模板,对象是实例;封装属性与行为 | 先定义类,再创建对象;对象的属性独立,方法共享 |
| this关键字 | 区分局部变量与成员变量;代表当前对象引用 | 仅在非静态成员方法中使用;不能在static方法中用 |
| 构造器 | 对象创建时初始化属性 | 手动定义构造器后,需补写无参构造器;支持重载 |
| 封装 | 隐藏内部数据,通过接口控制访问 | 用private隐藏属性,用public提供getter/setter |
| JavaBean | 标准化数据封装类 | 必须有私有属性、无参构造器、getter/setter |
OOP的核心是“以对象为中心”,通过类的定义、封装的实现、构造器的初始化,将复杂问题拆解为多个“对象协作”的简单问题,是Java开发中必须掌握的基础思想。