黑马学习笔记Day08

2025-11-13 黑马程序员学习笔记 53 0

Java 面向对象核心知识总结:static、继承与final

一、static关键字

1. 核心作用

static 是类级别的修饰符,用于标记成员(变量、方法、代码块)属于类本身,而非类的实例对象,实现“所有对象共享同一资源”的效果。

2. static修饰变量(静态变量/类变量)

特点

  • 存储位置:存于 JVM 元空间(方法区),而非堆内存的对象中。
  • 生命周期:随类加载而创建,随类卸载而销毁(生命周期长于对象)。
  • 共享性:该类所有实例共享同一静态变量,一个对象修改后,所有对象可见。
  • 访问方式:推荐 类名.变量名(如 Student.total),也可通过对象访问(不推荐,易混淆)。

适用场景

  • 全局计数器(如统计对象创建数量)。
  • 定义常量(需结合 final,如 public static final double PI = 3.14159)。

代码示例

class Student {
    String name; // 实例变量(每个对象独有)
    static int totalStudents; // 静态变量(所有对象共享)

    public Student(String name) {
        this.name = name;
        totalStudents++; // 每创建一个学生,总数+1
    }
}

public class TestStaticVar {
    public static void main(String[] args) {
        System.out.println("初始学生总数:" + Student.totalStudents); // 0(类加载时初始化)
        
        Student s1 = new Student("张三");
        System.out.println("创建s1后总数:" + Student.totalStudents); // 1
        
        Student s2 = new Student("李四");
        System.out.println("创建s2后总数:" + Student.totalStudents); // 2
        
        // 不推荐:通过对象访问静态变量
        System.out.println("通过s1访问总数:" + s1.totalStudents); // 2
    }
}

3. static修饰方法(静态方法/类方法)

特点

  • 调用方式:无需创建对象,直接 类名.方法名()(如 Math.abs(-5))。
  • 访问限制

    • 不能直接访问非静态成员(变量/方法),因静态方法加载时实例可能未创建。
    • 不能使用 thissuper 关键字(无“当前对象”绑定)。

适用场景

  • 工具类方法(如 Math 类的 sqrt()Arrays 类的 sort())。
  • 与类相关但不依赖实例状态的操作(如对象创建校验)。

代码示例

class Calculator {
    // 静态方法(工具方法,不依赖实例状态)
    public static int add(int a, int b) {
        return a + b;
    }

    // 非静态方法(依赖实例,需创建对象调用)
    public int subtract(int a, int b) {
        return a - b;
    }
}

public class TestStaticMethod {
    public static void main(String[] args) {
        // 直接调用静态方法
        int sum = Calculator.add(5, 10);
        System.out.println("和:" + sum); // 15

        // 调用非静态方法:需先创建对象
        Calculator calc = new Calculator();
        int diff = calc.subtract(10, 5);
        System.out.println("差:" + diff); // 5
    }
}

4. static注意事项

  • 内存问题:静态变量生命周期长,若持有大量引用对象未清理,可能导致内存泄漏。
  • 滥用风险:过度使用静态成员会增加代码耦合度,降低可测试性(静态方法无法被Mock)。
  • 静态导入:通过 import static 可直接使用静态成员,无需类名(如 import static java.lang.Math.PI)。

二、继承

1. 核心概念

继承是面向对象四大特性之一,允许子类(派生类)通过 extends 关键字继承父类(基类)的非私有成员(属性/方法),实现代码复用和类层次结构设计。

2. 继承的核心特点

(1)单一继承

Java 不支持多继承(子类不能同时继承多个父类),但可通过接口实现“类似多继承”的效果(如 class A implements B, C)。
错误示例class A extends B, C {}(编译报错)。

(2)多层继承(层次继承)

子类可作为父类被再次继承,形成类链。例如:A extends BB extends C,则 A 间接继承 C 的成员。

(3)传递性

B 继承 AC 继承 B,则 C 不仅拥有 B 的成员,还拥有 A 的非私有成员。

代码示例(多层继承与传递性)

// 顶层父类 A
class A {
    public int a = 1;
    public String name = "张三";
}

// 子类 B 继承 A
class B extends A {
    public int b = 2;
    // 重写父类 name(就近原则)
    public String name = "李四";
}

// 子类 C 继承 B(多层继承)
class C extends B {
    public int c = 3;
    // 重写父类 name
    public String name = "王五";

    // 访问不同层级的 name
    public void show(String name) {
        System.out.println("局部name:" + name); // 方法参数(局部)
        System.out.println("本类name:" + this.name); // C类的name
        System.out.println("父类B的name:" + super.name); // B类的name
        System.out.println("祖父类A的name:" + super.super.name); // 错误!super不能跨层,需通过B类间接访问
    }
}

public class TestInheritance {
    public static void main(String[] args) {
        C c = new C();
        System.out.println("a=" + c.a + ", b=" + c.b + ", c=" + c.c); // 1,2,3(传递性)
        c.show("局部变量"); // 调用show方法,验证就近原则
    }
}

3. 继承中成员的访问规则

(1)成员变量:就近原则

访问顺序:局部变量 → 本类成员变量 → 父类成员变量(未找到则编译报错)。

  • 若父子类变量重名,子类默认访问本类变量;
  • 需访问父类变量时,用 super.父类变量名

(2)成员方法:动态绑定(运行看右边)

访问顺序:子类重写方法 → 父类方法(未找到则报错)。

  • 方法调用时,实际执行的是“对象真实类型”的方法(多态基础);
  • 需访问父类方法时,用 super.父类方法名()

(3)访问控制修饰符

子类仅能访问父类的 publicprotected 成员,private 成员完全不可见(需通过父类的 getter/setter 访问)。

修饰符本类同包子类任意类
private
缺省
protected
public

4. 方法重写(Override)

核心概念

子类定义与父类方法名、参数列表完全一致的方法,覆盖父类方法的实现(需满足访问权限和返回值规则)。

重写规则

  1. 访问权限:子类方法权限 ≥ 父类方法(如父类 protected,子类可 protectedpublic)。
  2. 返回值类型

    • 基本类型/void:必须与父类完全一致;
    • 引用类型:子类返回值类型 ≤ 父类(如父类返回 Object,子类可返回 String)。
  3. 不可重写的方法private 方法(子类不可见)、static 方法(属于类,不参与动态绑定)。
  4. 注解:推荐添加 @Override,让编译器校验重写语法正确性。

代码示例(方法重写)

// 父类 Animal
class Animal {
    // 父类方法(protected权限)
    protected void sound() {
        System.out.println("动物发出声音");
    }
}

// 子类 Dog 重写 sound 方法
class Dog extends Animal {
    // 访问权限:public ≥ protected(符合规则)
    @Override
    public void sound() {
        System.out.println("狗叫:汪汪汪");
    }
}

// 子类 Cat 重写 sound 方法
class Cat extends Animal {
    @Override
    public void sound() {
        System.out.println("猫叫:喵喵喵");
    }
}

public class TestOverride {
    public static void main(String[] args) {
        Animal a1 = new Dog();
        Animal a2 = new Cat();
        a1.sound(); // 运行Dog的sound:汪汪汪(动态绑定)
        a2.sound(); // 运行Cat的sound:喵喵喵(动态绑定)
    }
}

5. 构造器的调用顺序

核心规则

创建子类对象时,父类构造器先执行,子类构造器后执行(确保父类成员先初始化)。

细节说明

  1. 默认调用:子类构造器第一行隐含 super()(调用父类无参构造器);
  2. 显式调用:若父类无无参构造器,子类必须在构造器第一行显式调用 super(参数)(指定父类有参构造器);
  3. this与super冲突this(...)(调用本类其他构造器)和 super(...) 不能同时存在于构造器第一行。

代码示例(构造器调用顺序)

// 父类 Car
class Car {
    private String brand;
    private double price;

    // 父类无参构造器
    public Car() {
        System.out.println("Car无参构造器执行");
    }

    // 父类有参构造器
    public Car(String brand, double price) {
        this.brand = brand;
        this.price = price;
        System.out.println("Car有参构造器执行(品牌:" + brand + ",价格:" + price + ")");
    }
}

// 子类 Truck 继承 Car
class Truck extends Car {
    private double load;

    // 子类无参构造器(隐含 super())
    public Truck() {
        // super(); // 隐含,调用父类无参构造器
        System.out.println("Truck无参构造器执行");
    }

    // 子类有参构造器(显式调用父类有参构造器)
    public Truck(String brand, double price, double load) {
        super(brand, price); // 显式调用父类有参构造器
        this.load = load;
        System.out.println("Truck有参构造器执行(载重:" + load + "吨)");
    }
}

public class TestConstructorOrder {
    public static void main(String[] args) {
        System.out.println("--- 创建Truck无参对象 ---");
        new Truck(); // 先执行Car无参,再执行Truck无参

        System.out.println("\n--- 创建Truck有参对象 ---");
        new Truck("东风", 280000, 10.5); // 先执行Car有参,再执行Truck有参
    }
}

运行结果

--- 创建Truck无参对象 ---
Car无参构造器执行
Truck无参构造器执行

--- 创建Truck有参对象 ---
Car有参构造器执行(品牌:东风,价格:280000.0)
Truck有参构造器执行(载重:10.5吨)

6. this与super的区别

关键字访问成员变量访问成员方法访问构造器
thisthis.本类变量this.本类方法()this() / this(参数)(本类)
supersuper.父类变量super.父类方法()super() / super(参数)(父类)

三、final关键字

1. 核心作用

final 表示“最终的、不可变的”,可修饰类、方法、变量,核心是禁止修改

2. final修饰类

  • 效果:类不可被继承(最终类),避免子类篡改核心逻辑。
  • 典型示例:Java 标准库的 String 类(确保字符串不可变性)。
  • 代码示例

    final class FinalClass { // 最终类,不可被继承
        public void show() {
            System.out.println("这是final类的方法");
        }
    }
    
    // class SubClass extends FinalClass {} // 编译报错:Cannot inherit from final 'FinalClass'

3. final修饰方法

  • 效果:方法不可被子类重写,确保核心逻辑稳定性。
  • 注意final 方法可被继承和重载,但不能被重写。
  • 代码示例

    class Parent {
        // final方法,不可重写
        public final void finalMethod() {
            System.out.println("父类final方法");
        }
    
        // 普通方法,可重写
        public void normalMethod() {
            System.out.println("父类普通方法");
        }
    }
    
    class Child extends Parent {
        // @Override
        // public void finalMethod() {} // 编译报错:Cannot override the final method from Parent
    
        // 重写普通方法
        @Override
        public void normalMethod() {
            System.out.println("子类重写的普通方法");
        }
    }

4. final修饰变量

核心规则

变量初始化后不可再次赋值,具体分两种类型:

  • 基本类型:值不可变(如 final int num = 10; num = 20; 编译报错);
  • 引用类型:引用地址不可变,但对象内部内容可修改(如 final List<String> list = new ArrayList<>(); list.add("a"); 合法,list = new ArrayList<>(); 编译报错)。

初始化时机

  • 局部变量:必须在使用前显式初始化(可先声明后赋值,仅一次);
  • 成员变量:必须在声明时、非静态初始化块、构造器中初始化(三者选其一);
  • 静态变量:必须在声明时、静态初始化块中初始化。

代码示例

import java.util.ArrayList;
import java.util.List;

public class TestFinalVar {
    // 1. 成员变量:声明时初始化
    final String memberVar1 = "声明时赋值";

    // 2. 成员变量:初始化块中赋值
    final String memberVar2;
    {
        memberVar2 = "初始化块赋值";
    }

    // 3. 成员变量:构造器中赋值
    final String memberVar3;
    public TestFinalVar(String var3) {
        this.memberVar3 = var3; // 构造器赋值
    }

    // 4. 静态变量:静态初始化块赋值
    static final int staticVar;
    static {
        staticVar = 100;
    }

    public static void main(String[] args) {
        // 局部变量:先声明后赋值(仅一次)
        final int localVar;
        localVar = 5;
        // localVar = 6; // 编译报错:不可二次赋值

        // 引用类型变量:地址不可变,内容可改
        final List<String> list = new ArrayList<>();
        list.add("Java"); // 合法:修改内容
        // list = new ArrayList<>(); // 编译报错:不可修改地址

        // 测试成员变量
        TestFinalVar obj = new TestFinalVar("构造器赋值");
        System.out.println(obj.memberVar1); // 声明时赋值
        System.out.println(obj.memberVar2); // 初始化块赋值
        System.out.println(obj.memberVar3); // 构造器赋值
        System.out.println(staticVar); // 100
    }
}

5. final的使用场景

场景示例目的
定义常量public static final double PI = 3.14避免魔法值,提高可维护性
保护核心方法public final void validate()防止子类修改核心校验逻辑
设计不可变类final class String确保对象状态安全(如线程安全)
固定对象引用final User user = new User()防止意外修改引用指向

四、常见面试题与作业

1. 面试题(判断题)

  1. Java支持多继承?(×,仅支持单一继承)
  2. 子类可直接访问父类的private成员?(×,需通过getter/setter)
  3. final修饰的引用类型变量,其指向的对象内容不可修改?(×,仅地址不可改)
  4. 子类构造器默认调用父类的无参构造器?(√,隐含super())
  5. 方法重写时,子类方法的访问权限必须大于父类?(×,≥即可)

2. 作业题:黑马管理系统角色设计

需求

  1. 设计 Employee(员工)、Teacher(讲师)、Consultant(咨询师)类,TeacherConsultant 继承 Employee
  2. Employeenameagesalary 字段和 work() 方法;
  3. Teacher 新增 subject 字段和 teach() 方法;
  4. Consultant 新增 special 字段和 advice() 方法;
  5. 创建测试类,实例化对象并调用方法。

解答代码

// 父类:Employee
class Employee {
    public String name;
    public int age;
    public double salary;

    // 构造器
    public Employee(String name, int age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    // 父类方法
    public void work() {
        System.out.println(name + "(" + age + "岁,薪资:" + salary + ")正在工作");
    }
}

// 子类:Teacher(讲师)
class Teacher extends Employee {
    public String subject; // 新增字段:教授学科

    // 构造器:调用父类构造器
    public Teacher(String name, int age, double salary, String subject) {
        super(name, age, salary);
        this.subject = subject;
    }

    // 新增方法:授课
    public void teach() {
        System.out.println(name + "教授" + subject + "课程");
    }

    // 重写父类work方法
    @Override
    public void work() {
        System.out.println(name + "(讲师)负责" + subject + "教学,薪资:" + salary);
    }
}

// 子类:Consultant(咨询师)
class Consultant extends Employee {
    public String special; // 新增字段:咨询领域

    // 构造器:调用父类构造器
    public Consultant(String name, int age, double salary, String special) {
        super(name, age, salary);
        this.special = special;
    }

    // 新增方法:提供咨询
    public void advice() {
        System.out.println(name + "提供" + special + "领域咨询");
    }

    // 重写父类work方法
    @Override
    public void work() {
        System.out.println(name + "(咨询师)专注" + special + "咨询,薪资:" + salary);
    }
}

// 测试类
public class TestEmployeeSystem {
    public static void main(String[] args) {
        // 实例化讲师
        Teacher t = new Teacher("张三", 30, 8000.0, "Java");
        t.work(); // 调用重写的work方法
        t.teach(); // 调用子类特有方法

        // 实例化咨询师
        Consultant c = new Consultant("李四", 35, 12000.0, "企业管理");
        c.work(); // 调用重写的work方法
        c.advice(); // 调用子类特有方法

        // 多态:父类引用指向子类对象
        Employee emp1 = new Teacher("王五", 28, 7500.0, "Python");
        Employee emp2 = new Consultant("赵六", 32, 11000.0, "人力资源");
        emp1.work(); // 执行Teacher的work(动态绑定)
        emp2.work(); // 执行Consultant的work(动态绑定)
    }
}

运行结果

张三(讲师)负责Java教学,薪资:8000.0
张三教授Java课程
李四(咨询师)专注企业管理咨询,薪资:12000.0
李四提供企业管理领域咨询
王五(讲师)负责Python教学,薪资:7500.0
赵六(咨询师)专注人力资源咨询,薪资:11000.0