黑马学习资料Day06

2025-11-08 黑马程序员学习笔记 61 0

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关键字创建对象。创建对象后,通过“对象名.属性”访问成员变量,通过“对象名.方法()”调用成员方法。

核心语法格式

  1. 创建对象:类名 对象名 = new 类名();
    new 类名()会在内存中创建对象实例,对象名存储对象的内存地址,相当于“引用”)
  2. 访问成员变量:

    • 取值:对象名.变量名;
    • 赋值:对象名.变量名 = 值;
  3. 调用成员方法:对象名.方法名(实际参数);(无参数则括号为空)

对象使用代码示例

// 测试类:创建并使用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. 类与对象的关系

  • 类是“抽象模板”,对象是“具体实例”:一个类可以创建多个对象(如一个“手机类”可创建“小米手机”“华为手机”等多个对象)。
  • 对象的属性值不同,但行为逻辑相同:例如stu1stu2都是Student对象,都有study()方法,但stu1.name是“张三”,stu2.name是“李四”。

三、对象内存逻辑:理解引用与实例的关系

Java内存分为元空间堆内存栈内存,对象的创建和使用会涉及这三个区域的协作:

  • 元空间:存储类的字节码(如Student.class),包括类的成员变量定义、成员方法逻辑,类加载时只加载一次。
  • 堆内存:存储对象的实例(即成员变量的具体值),每个对象在堆中占用独立空间,对象创建时分配内存,对象无引用时会被垃圾回收。
  • 栈内存:存储对象引用(如stu变量)和局部变量,方法调用时在栈中开辟空间,方法结束后栈空间释放。

关键逻辑(以单个对象为例)

  1. 执行Student stu = new Student();时,JVM先加载Student.class到元空间。
  2. 在堆内存中创建Student对象实例,成员变量name默认值为nullage默认值为0
  3. 在栈内存中创建stu引用变量,存储堆内存中对象的地址(如0x0011)。
  4. 执行stu.name = "张三";时,通过stu的地址找到堆中的对象,修改name的值为“张三”。
  5. 执行stu.study();时,通过stu找到对象,再根据对象中的方法引用,调用元空间中study()的逻辑。

四、成员变量与局部变量的区别

在类中,变量按定义位置分为“成员变量”和“局部变量”,二者在初始化、内存、生命周期等方面有本质区别,是OOP中容易混淆的知识点:

区别维度成员变量局部变量
类中位置类中、方法外方法内、方法参数、代码块内
初始化值有默认初始化值(按数据类型:int=0String=nulldouble=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)中,nameage是局部变量(方法参数),与成员变量重名。
  • 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)是类中一种特殊的方法,专门用于创建对象时初始化对象的属性,确保对象创建后就具有合理的初始状态(避免成员变量为默认值的“无效状态”)。

构造器的语法规则

  1. 构造器名必须与类名完全相同(大小写一致),如Student类的构造器名必须是Student
  2. 无返回值类型(连void都不写)——这是构造器与普通方法的核心区别。
  3. 不能用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);:调用带参构造器,对象的nameage被初始化为传入的“张三”和23。
  • 构造器的核心价值是“对象创建即初始化”,避免创建对象后还要手动调用set方法赋值的繁琐操作。

七、封装:OOP的核心特性之一

1. 核心思想

封装(Encapsulation)的本质是“合理隐藏,合理暴露”——将对象的内部属性(成员变量)隐藏起来,不允许外部直接访问,仅通过公开的方法(gettersetter)控制对属性的访问,从而保护数据的安全性和完整性。

例如现实中的“手机”:用户无需知道手机内部的芯片、电路(隐藏),只需通过屏幕、按键(暴露的接口)使用手机功能——这就是封装的思想。

2. 封装的实现步骤

步骤1:用private修饰成员变量(隐藏属性)

private是Java的权限修饰符,被private修饰的成员变量,仅在当前类中可访问,外部类无法直接通过“对象名.属性”访问(编译报错)。

步骤2:提供publicgettersetter方法(暴露接口)

  • 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种权限修饰符,用于控制成员的访问范围,封装的实现依赖于privatepublic

修饰符本类同一包中的类子孙类任意类核心用途
private 隐藏类的内部成员(如成员变量)
缺省(无) 包内可见(一般用于内部类协作)
protected 子孙类可见(继承时使用)
public完全公开(如gettersetter方法)

八、JavaBean规范:标准化的数据封装类

1. 核心定义

JavaBean是一种符合特定规范的Java类,专门用于封装数据(如用户信息、商品信息、电影信息),是企业级开发中“数据传输载体”的标准形式(如前后端交互、数据库映射的实体类)。

2. JavaBean的规范要求

必须满足以下3个条件,才能称为标准的JavaBean:

  1. 成员变量私有:所有成员变量用private修饰(隐藏数据)。
  2. 提供无参构造器:必须有一个public的无参构造器(便于框架动态创建对象,如Spring、MyBatis)。
  3. 提供getter/setter:为每个成员变量提供publicgetter(获取值)和setter(修改值)方法,方法命名严格遵循“get属性名”“set属性名”(首字母大写)。

补充:可选规范

  • 实现Serializable接口:支持对象的序列化(便于网络传输或持久化到文件)。
  • 成员变量类型为基本类型或包装类(如int/IntegerString),避免复杂引用类型。

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表的idnameprice字段)。
  • 前后端数据交互:后端将数据封装为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开发中必须掌握的基础思想。