20260723-笔记.md 13 KB

JavaSE 基础 — Day09 笔记

日期:2026-07-23
项目c260723
作者:WanJL


一、抽象类(Abstract Class)

1. 抽象类的引出

文件Demo01.java

在父子类继承时,提取子类的共性功能时,有些方法在父类中并没有具体的实现。

例如员工体系:

员工类
  |- 开发工程师
     |- Java开发工程师
     |- 前端开发工程师
  |- 测试工程师
     |- 后端测试工程师
     |- IOS测试工程师

每一个子类的工作内容都是不一样的,所以都需要重写父类的 work() 方法,导致父类的 work() 方法代码没有实际作用。

public class 员工 {
    public void work() {
        // 每一个子类的工作内容都不一样,此方法体没有意义
    }
}

在 Java 中,一个没有方法体的方法,应该定义为 抽象方法;如果类中包含抽象方法,那么这个类必须是 抽象类

2. 抽象类的定义

文件Demo01.java

使用 abstract 关键字来定义抽象类和抽象方法。

定义抽象类

public abstract class 类名 {
    // 成员
}

定义抽象方法

public abstract 返回值类型 方法名(参数列表);

注意:抽象方法没有方法体(没有 {},直接以分号结尾)。

3. 抽象类的特点

文件Demo01.java

特性 说明
关键字 抽象类和抽象方法必须使用 abstract 关键字修饰
抽象方法所在类 有抽象方法的类必须是抽象类
抽象类中的抽象方法 抽象类中不一定要有抽象方法,但抽象方法必须在抽象类中
实例化 抽象类不能实例化(不能创建对象)
构造方法 抽象类可以有构造方法(供子类通过 super() 调用)
子类选择 ① 重写父类所有的抽象方法;② 子类也是抽象类,直接继承父类的抽象方法
继承关系 抽象父类和具体子类之间是继承关系,子类仍然属于父类类型

二、抽象类代码示例

示例 1:动物体系(Animal → Cat / Dog)

抽象父类 Animal

文件Animal.java

package course;

public abstract class Animal {
    public Animal() {
        System.out.println("调用动物的构造方法....");
    }

    public abstract void eat();
    public abstract void sleep();
    public abstract void move();
    public abstract void play();
}
  • 定义了 4 个抽象方法:eat()sleep()move()play()
  • 包含一个构造方法,虽然抽象类不能直接创建对象,但构造方法可以通过子类 super() 调用

子类 Cat

文件Cat.java

package course;

public class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }

    @Override
    public void sleep() {
        // 空实现
    }

    @Override
    public void move() {
        // 空实现
    }

    @Override
    public void play() {
        // 空实现
    }
}
  • 使用 extends 继承抽象类 Animal
  • 使用 @Override 注解重写父类的所有抽象方法(必须全部重写,否则子类也要声明为抽象类)

子类 Dog

文件Dog.java

package course;

public class Dog extends Animal {
    public void eat() {
        System.out.println("狗吃肉");
    }

    @Override
    public void sleep() { }

    @Override
    public void move() { }

    @Override
    public void play() { }
}
  • 同样继承 Animal,重写所有抽象方法
  • eat() 方法没有写 @Override 注解,但仍然是对父类抽象方法的重写(注解只是编译检查,不写也能重写)

测试类 Test01

文件Test01.java

package course;

public class Test01 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.eat();  // 输出:狗吃肉
    }
}
  • 创建 Dog 对象,调用重写后的 eat() 方法
  • 验证了抽象类的子类可以正常创建对象和使用成员方法

示例 2:员工体系(Emp → javaEmp)

抽象父类 Emp

文件Emp.java

package course;

// 有抽象方法的类必须是抽象类
public abstract class Emp {
    /**
     * 抽象方法——没有方法体
     */
    public abstract void work();
}
  • 只定义了一个抽象方法 work()
  • 类必须使用 abstract 修饰

子类 javaEmp

文件javaEmp.java

package course;

public class javaEmp extends Emp {
    @Override
    public void work() {
        System.out.println("Java开发工程师在开发Java项目");
    }
}
  • 重写父类的 work() 抽象方法,给出具体的实现
  • 不同子类可以提供不同的实现(例如还可以有 前端开发工程师后端测试工程师 等)

示例 3:服务层体系(BaseService → UserService)

抽象父类 BaseService

文件BaseService.java

package course;

public abstract class BaseService {
    public abstract void add();
    public abstract void remove();
    public abstract void update();
    public abstract void query();
}
  • 定义了标准的 CRUD(增删改查)四个抽象方法
  • 在实际项目中常用来定义业务层接口规范

子类 UserService

文件UserService.java

package course;

public class UserService extends BaseService {
    @Override
    public void add() { }

    @Override
    public void remove() { }

    @Override
    public void update() { }

    @Override
    public void query() { }
}
  • 重写父类的所有抽象方法
  • 方法体暂时为空,后续可添加具体的业务逻辑

三、final 关键字

1. final 的引出

文件Demo02.java

在实际开发中,有些类、方法或变量不希望被改变:

  • 某个工具类,只能被调用,不希望被继承
  • 某些核心方法,不希望被子类重写(篡改)
  • 某些固定值,希望成为常量,不允许修改

final 关键字就是用来解决这些问题的——用于限制类、方法或变量被修改

2. final 修饰类——禁止继承

文件Demo02.javaFinalClass.java

// 使用 final 修饰的类,不能被继承
public final class 类名 {
    // ...
}

应用场景

  • 工具类(如 MathArrays),只能被调用,不能被继承
  • 特殊功能的类,如 String 类就是用 final 修饰的,不能被继承

3. final 修饰方法——禁止重写

文件FinalClass.javaSubClass.java

// 父类中的 final 方法
public class FinalClass {
    final String SPRING = "春天";

    public final void showInfo() {
        System.out.printf(SPRING);
    }
}

// 子类无法重写父类的 final 方法
public class SubClass extends FinalClass {
    // ❌ 编译报错:不能重写父类的 final 方法
    // public final void showInfo() {
    //     System.out.printf(SPRING);
    // }
}
  • FinalClass 类本身没有用 final 修饰,所以 SubClass 可以继承它
  • FinalClass 中的 showInfo() 方法用 final 修饰,子类 不能重写 该方法
  • 注掉了 SubClass 中的重写代码,否则编译报错

4. final 修饰变量——变成常量

文件Demo02.javaTest02.java

// final 修饰基本类型变量——值不可修改
final int i = 10;
// i = 20;  // ❌ 编译报错:不能修改 final 变量的值

// final 修饰引用类型变量——引用地址不可变,但对象内容可以修改
final Person p = new Person("1001", "张三");
// p = new Person("1002", "李四");  // ❌ 编译报错:不能修改引用地址
p.setName("李四");  // ✅ 可以修改对象的属性
p.setId("1002");
System.out.printf(p.showInfo());  // 输出:Person{id='1002', name='李四'}

关键区别

变量类型 final 的作用 示例
基本类型(int、double 等) 数值本身不可修改 final int i = 10;i=20; 报错
引用类型(对象、数组等) 引用地址不可修改,但对象 属性 可修改 final Person p = new Person(...)p.setName(...) 可以

注意final 修饰引用类型变量时,只是保证这个变量不再指向其他对象,但该对象内部的 成员变量 仍然可以通过 setter 修改。

5. final 与 abstract 的对比

关键字 修饰类 修饰方法 修饰变量
abstract 类必须被继承 方法必须在子类中实现(无方法体) 不能修饰变量
final 类不能被继承 方法不能被重写 变量变成常量

⚠️ finalabstract 不能同时使用,二者语义完全冲突:

  • abstract 表示「需要被继承/重写」
  • final 表示「不能被继承/重写」

四、随堂练习代码示例——Shape 抽象类(计算周长 + 计算面积)

代码位于 exercise/exercise02 包中,对应课堂练习题第 2 题。

抽象父类 Shape

文件exercise/exercise02/Shape.java

package exercise.exercise02;

public abstract class Shape {
    private String color;

    public Shape(String color) {
        this.color = color;
    }

    /** 计算面积——抽象方法 */
    public abstract double getArea();

    /** 计算周长——抽象方法 */
    public abstract double getPerimeter();

    /** 输出信息——普通方法(调用抽象方法) */
    public void showInfo() {
        System.out.print("颜色:" + this.color);
        System.out.printf(",面积:%.2f", getArea());
        System.out.printf(",周长:%.2f", getPerimeter());
        System.out.println();
    }
}
  • 定义了两个抽象方法:getArea()getPerimeter()
  • 定义了一个普通方法 showInfo(),内部调用抽象方法输出结果
  • 体现了 模板方法 的思想——父类定义输出框架,子类提供具体的面积和周长计算

子类 Circle

文件exercise/exercise02/Circle.java

package exercise.exercise02;

public class Circle extends Shape {
    private double radius;
    private double pi = 3.1415926;

    public Circle(String color, double radius) {
        super(color);           // 调用父类构造方法
        this.radius = radius;   // 初始化子类属性
    }

    @Override
    public double getArea() {
        return pi * radius * radius;
    }

    @Override
    public double getPerimeter() {
        return 2 * pi * radius;
    }
}

测试类 Test

文件exercise/exercise02/Test.java

package exercise.exercise02;

public class Test {
    public static void main(String[] args) {
        Circle circle = new Circle("红色", 5);
        circle.showInfo();
        // 输出:颜色:红色,面积:78.54,周长:31.42
    }
}

五、随堂练习要点

练习目标:理解抽象类的定义和使用

  1. 定义抽象父类:使用 public abstract class 声明,定义抽象方法时不要写方法体
  2. 实现具体子类:使用 extends 继承抽象类,使用 @Override 重写所有抽象方法
  3. 测试类验证:创建子类对象,调用重写后的方法验证正确性

六、面试常见问题

Q1:抽象类能不能被实例化?

不能。抽象类不能直接 new 创建对象。

Q2:抽象类中一定要有抽象方法吗?

不一定。抽象类中可以没有抽象方法,但有抽象方法的类必须是抽象类。

Q3:抽象类的子类必须重写所有抽象方法吗?

不一定。子类有两个选择:① 重写父类所有抽象方法变为具体子类;② 子类也声明为抽象类,继承父类的抽象方法。

Q4:抽象类可以有构造方法吗?

可以。虽然抽象类不能直接创建对象,但其构造方法可以在子类实例化时通过 super() 调用,用于初始化父类成员。

Q5:abstractfinal 能否同时修饰一个类或方法?

不能。abstract 表示需要被继承/重写,final 表示不能被继承/重写,二者语义冲突。

Q6:final 修饰引用类型变量时,对象内容还能修改吗?

可以。final 修饰引用类型变量时,只是保证引用地址不变(不能重新指向其他对象),但该对象内部的成员变量仍然可以通过 setter 修改。

Q7:final 修饰的类有哪些常见例子?

String 类、Math 类、Integer 等包装类都是用 final 修饰的,它们不能被继承。


七、拓展阅读

  • Java 抽象类(Oracle 官方文档)
  • 抽象类 vs 接口(Interface)的区别(将在后续课程中讲解)
  • 模板方法设计模式(Template Method Pattern)—— 抽象类的经典应用场景