JavaSE面向对象(下)

抽象类的定义

  • 使用 abstract 关键字修饰类:

    java 复制代码
    abstract class AbstractPlayer {}
  • 按规范,抽象类命名应以 Abstract 或 Base 开头(如:AbstractPlayer、BaseService)。

抽象类的特征

  1. 不能被实例化:

    java 复制代码
    AbstractPlayer player = new AbstractPlayer(); // ❌ 编译错误
  2. 可以被继承 :子类使用 extends 继承抽象类

    java 复制代码
    public class BasketballPlayer extends AbstractPlayer {}
  3. 包含抽象方法:

    抽象方法必须在抽象类中定义

    抽象方法没有方法体,只定义方法签名

    子类必须实现父类中的所有抽象方法,否则子类也必须是抽象类

    java 复制代码
    abstract void play();
  4. 可以包含普通方法:抽象类可以混合抽象与非抽象方法

    java 复制代码
    public abstract class AbstractPlayer {
        abstract void play();
        public void sleep() {
            System.out.println("运动员也要休息");
        }
    }

抽象类的应用场景

1️⃣ 代码复用场景

  • 抽象类可以定义多个子类共有的功能 (如 sleep() 方法),避免重复代码。
  • 子类继承后可以直接使用这些公共方法。

2️⃣ 统一接口、延迟实现场景

  • 抽象类可作为一种模板或框架,规定子类必须实现的功能。

  • 例如:play() 方法定义了所有运动员都要"从事某项运动"的行为,但不同子类各自实现不同内容。

    java 复制代码
    abstract class AbstractPlayer {
        abstract void play();
    }

抽象类的进阶示例:文件读取器

基类定义(模板模式思想)

java 复制代码
abstract class BaseFileReader {
    protected Path filePath;

    protected BaseFileReader(Path filePath) {
        this.filePath = filePath;
    }

    public List<String> readFile() throws IOException {
        return Files.lines(filePath)
                    .map(this::mapFileLine)
                    .collect(Collectors.toList());
    }

    protected abstract String mapFileLine(String line);
}
  • readFile() 定义了文件读取的整体流程。
  • mapFileLine() 是一个钩子方法(由子类实现不同转换逻辑)。

子类实现不同的读取方式

java 复制代码
class LowercaseFileReader extends BaseFileReader {
    @Override
    protected String mapFileLine(String line) {
        return line.toLowerCase();
    }
}

class UppercaseFileReader extends BaseFileReader {
    @Override
    protected String mapFileLine(String line) {
        return line.toUpperCase();
    }
}

测试类

java 复制代码
public class FileReaderTest {
    public static void main(String[] args) throws Exception {
        URL location = FileReaderTest.class.getClassLoader().getResource("helloworld.txt");
        Path path = Paths.get(location.toURI());
        BaseFileReader lower = new LowercaseFileReader(path);
        BaseFileReader upper = new UppercaseFileReader(path);
        System.out.println(lower.readFile());
        System.out.println(upper.readFile());
    }
}

抽象类的总结

特征 说明
实例化 不可直接实例化
方法类型 可包含抽象方法与普通方法
子类要求 必须实现所有抽象方法或自身声明为抽象类
用途 代码复用、定义模板、约束子类行为

核心思想

抽象类是"模板"与"约束"的结合。

它既能封装通用逻辑(复用代码),又能通过抽象方法约束子类实现特定行为。

接口的定义与语法

  • 使用 interface 关键字定义接口:

    java 复制代码
    public interface Electronic {
        String workconsistent = "workconsistent";                  // 常量
        int getElectricityUse();             // 抽象方法
        static boolean isEnergyEfficient(String type) { ... } // 静态方法 (Java 8+)
        default void printDescription() { ... }               // 默认方法 (Java 8+)
    }

接口成员特征

成员类型 默认修饰符 说明
变量 public static final 即常量,值不可修改
抽象方法 public abstract 无方法体,需由实现类实现
静态方法 public static 需用接口名调用,不可由实例调用
默认方法 public default 可有方法体,供实现类直接复用

接口可包含静态方法与默认方法,用以增强接口的扩展性和兼容性。

接口的限制与特性

  1. 接口 不能被实例化 ,必须由类通过 implements 实现:

    java 复制代码
    public class Computer implements Electronic { ... }
  2. 接口 不能使用 final 修饰(否则无法被实现)。

  3. 抽象方法 不能是 private、protected 或 final。

  4. 接口变量是常量(public static final),值不可修改。

  5. 接口可以是空接口(标记接口),如:

    java 复制代码
    public interface Serializable {}

    实现该接口的类具有某种"标识性"功能(例如可被序列化)。

接口的作用

1️⃣ 提供功能约定

实现接口的类具有特定功能,例如:

  • Cloneable → 支持克隆
  • Serializable → 支持序列化
  • Comparable / Comparator → 支持比较

示例:Cloneable 接口

java 复制代码
public class CloneableTest implements Cloneable {
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

2️⃣ 支持多重继承

Java 不支持类的多继承,但接口可以实现"多继承行为":

java 复制代码
public interface Fly { void fly(); }
public interface Run { void run(); }

public class Pig implements Fly, Run {
    public void fly() { System.out.println("会飞的猪"); }
    public void run() { System.out.println("会跑的猪"); }
}

一个类可实现多个接口,避免了"菱形继承问题"。

3️⃣ 实现多态(Polymorphism)

通过接口引用不同实现类,实现同一方法不同表现:

java 复制代码
interface Shape { String name(); }

class Circle implements Shape {
    public String name() { return "圆"; }
}
class Square implements Shape {
    public String name() { return "正方形"; }
}

List<Shape> shapes = List.of(new Circle(), new Square());
for (Shape s : shapes){
	// 圆、正方形
	System.out.println(s.name());
}

多态的三个前提:

  1. 有继承或实现关系;
  2. 子类重写父类或接口方法;
  3. 父类引用指向子类对象。

接口的三种典型设计模式

策略模式(Strategy Pattern)

  • 定义一组算法(策略)并封装,调用方通过接口使用不同实现。
java 复制代码
// 策略模式示例:多种支付方式

// 1. 定义支付策略接口
interface PaymentStrategy {
    void pay(double amount);
}

// 2. 定义不同的支付策略实现
class AlipayStrategy implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("使用支付宝支付 " + amount + " 元");
    }
}

class WeChatPayStrategy implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("使用微信支付 " + amount + " 元");
    }
}

class CreditCardStrategy implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("使用信用卡支付 " + amount + " 元");
    }
}

// 3. 支付上下文类,负责调用具体策略
class PaymentContext {
    private PaymentStrategy paymentStrategy;

    public PaymentContext(PaymentStrategy paymentStrategy) {
        this.paymentStrategy = paymentStrategy;
    }

    public void executePayment(double amount) {
        paymentStrategy.pay(amount);
    }
}

// 4. 测试类(主程序)
public class StrategyDemo {
    public static void main(String[] args) {
        PaymentContext alipayContext = new PaymentContext(new AlipayStrategy());
        PaymentContext wechatContext = new PaymentContext(new WeChatPayStrategy());
        PaymentContext creditContext = new PaymentContext(new CreditCardStrategy());

        alipayContext.executePayment(199.99);
        wechatContext.executePayment(88.88);
        creditContext.executePayment(500.00);
    }
}

适配器模式(Adapter Pattern)

  • 用抽象类实现接口并置空方法,新类继承该抽象类,只需覆盖必要方法。
java 复制代码
//  定义播放器接口
interface MediaPlayer {
    void playMp3(String fileName);
    void playMp4(String fileName);
    void playFlac(String fileName);
}

//  抽象适配器类:实现接口但提供空方法(防止子类必须实现所有方法)
abstract class MediaAdapter implements MediaPlayer {
    public void playMp3(String fileName) {}
    public void playMp4(String fileName) {}
    public void playFlac(String fileName) {}
}

//  子类根据需要只实现自己关心的方法
class Mp3Player extends MediaAdapter {
    @Override
    public void playMp3(String fileName) {
        System.out.println(" 正在播放 MP3 文件:" + fileName);
    }
}

class Mp4Player extends MediaAdapter {
    @Override
    public void playMp4(String fileName) {
        System.out.println(" 正在播放 MP4 文件:" + fileName);
    }
}

class FlacPlayer extends MediaAdapter {
    @Override
    public void playFlac(String fileName) {
        System.out.println(" 正在播放 FLAC 文件:" + fileName);
    }
}

//  测试类
public class AdapterDemo {
    public static void main(String[] args) {
        MediaPlayer mp3 = new Mp3Player();
        MediaPlayer mp4 = new Mp4Player();
        MediaPlayer flac = new FlacPlayer();

        mp3.playMp3("周杰伦 - 晴天.mp3");
        mp4.playMp4("电影 - 功夫.mp4");
        flac.playFlac("林俊杰 - 江南.flac");
    }
}

工厂模式(Factory Pattern)

  • 定义接口规范产品与工厂,通过不同工厂创建不同对象。
java 复制代码
//  图形接口
interface Shape {
    void draw();
}

//  具体图形类:圆形、矩形、三角形
class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("正在画一个圆形 🟢");
    }
}

class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("正在画一个矩形 🟥");
    }
}

class Triangle implements Shape {
    @Override
    public void draw() {
        System.out.println("正在画一个三角形 🔺");
    }
}

//  抽象工厂接口
interface ShapeFactory {
    Shape createShape();
}

//  具体工厂类
class CircleFactory implements ShapeFactory {
    @Override
    public Shape createShape() {
        return new Circle();
    }
}

class RectangleFactory implements ShapeFactory {
    @Override
    public Shape createShape() {
        return new Rectangle();
    }
}

class TriangleFactory implements ShapeFactory {
    @Override
    public Shape createShape() {
        return new Triangle();
    }
}

//  测试类
public class FactoryDemo {
    // 静态方法,接收不同的工厂对象
    public static void drawShape(ShapeFactory factory) {
        Shape shape = factory.createShape();
        shape.draw();
    }

    public static void main(String[] args) {
        drawShape(new CircleFactory());
        drawShape(new RectangleFactory());
        drawShape(new TriangleFactory());
    }
}

抽象类 vs 接口

对比点 抽象类 接口
关键字 abstract class interface
是否可包含实现 可包含具体方法 仅方法声明
成员变量 任意类型 public static final 常量
静态代码块 可以 不允许
继承 / 实现数量 单继承 多实现
设计目的 对类的抽象(模板式设计) 对行为的抽象(规范式设计)
关系表达 "是一个(is-a)" "具有......能力(has-a)"
修改影响 改抽象类 → 子类不一定改 改接口 → 所有实现类需同步改动

接口是行为规范,抽象类是模板设计。

  • 接口 关注"能力",强调"能做什么";
  • 抽象类 关注"共性",强调"是什么";
  • 抽象类提供"部分实现",接口提供"统一约定";
  • 接口支持多继承,抽象类支持代码复用。

内部类的四种类型

成员内部类

定义在外部类的成员位置 ,没有使用 static 修饰。

java 复制代码
public class Icheng {
    private String hobby = "写代码";
    static int age = 25;

    class Friend {
        public void introduce() {
            System.out.println("一成的爱好是:" + hobby);
            System.out.println("一成今年:" + age + " 岁");
        }
    }

    public static void main(String[] args) {
        Icheng icheng = new Icheng();
        Icheng.Friend friend = icheng.new Friend();
        friend.introduce();
    }
}

要点:

  • Friend 是成员内部类。

  • 可以访问外部类的:

    • 私有成员变量 hobby;
    • 静态成员变量 age。
  • 创建方式:外部类对象.new 内部类()。

局部内部类

java 复制代码
public class IchengCafe {
    private String cafeName = "一成的下午茶屋";

    public void closeCafe() {
        // 局部内部类:用于记录当天的结算信息
        class DailyReport {
            private int customers = 42;
            private double income = 589.5;

            public void printReport() {
                System.out.println(" 店铺:" + cafeName);
                System.out.println(" 今日接待顾客数:" + customers);
                System.out.println(" 今日营业额:" + income + " 元");
            }
        }

        // 在方法内创建并使用局部内部类对象
        DailyReport report = new DailyReport();
        report.printReport();
    }

    public static void main(String[] args) {
        IchengCafe cafe = new IchengCafe();
        cafe.closeCafe();
        //  店铺:一成的下午茶屋  
        // 今日接待顾客数:42
        // 今日营业额:589.5 元
    }
}

说明:

  • DailyReport 定义在 closeCafe() 方法内部,只能在此方法中使用。
  • 生命周期与 closeCafe() 方法一致,方法执行完就销毁。
  • 不能使用 public、private、protected 或 static 修饰。
  • 常用于在方法中临时封装逻辑或实现接口。

3️⃣ 匿名内部类

没有名字的内部类 ,常用于简化

一次性类的使用(尤其是回调和线程)。

java 复制代码
public class ThreadDemo {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        });
        t.start();
    }
}

特征与要点:

  • 无类名、无构造方法。
  • 直接通过 new 接口/父类() 创建对象并重写方法。
  • 常用于:事件监听、线程、回调等。
  • 本质上是一个语法糖,用于快速定义临时子类或实现类。

4️⃣ 静态内部类

java 复制代码
public class Icheng {
    static int age = 21;
    double money = 99.9;

    static class Friend {
        public Friend() {
            // 21
            System.out.println(age);   // ✅ 可以访问外部类静态成员
            // System.out.println(money); ❌ 非静态成员不能访问
        }
    }

    public static void main(String[] args) {
        new Friend(); // 直接创建静态内部类对象
    }
}

说明:

  • Friend 是 静态内部类 ,与外部类 Icheng 没有实例绑定关系。
  • 可以直接访问外部类的 静态变量 age ,
    但不能访问 非静态变量 money。
  • 创建方式简洁:new Icheng.Friend() 或在同类中直接 new Friend()。

使用内部类的原因与优势

💬 引用《Think in Java》:

"使用内部类最吸引人的原因是:每个内部类都能独立地继承一个(接口的)实现。"

内部类的优势

  1. 实现多重继承效果
    内部类可以在外部类继承某个类的同时,实现另一个接口或类的功能。
  2. 实例独立性
    每个内部类对象都可拥有自己的状态,与外部类实例独立。
  3. 多样实现同一接口
    在一个外部类中,可以有多个内部类分别以不同方式实现同一个接口。
  4. 延迟创建
    内部类对象可在任意时刻创建,不依赖外部类的初始化。
  5. 更好的封装性
    内部类可隐藏实现细节,对外部完全不可见。

对比表

类型 定义位置 是否静态 是否依附外部类对象 可访问外部类成员 修饰符限制 使用场景
成员内部类 外部类成员处 否 是 所有成员 无限制 外部类的辅助逻辑
局部内部类 方法/作用域内 否 是 可访问局部变量(final 或 effectively final) 不能用 public/private/protected/static 方法内临时使用
匿名内部类 表达式内 否 是 可访问外部成员 无修饰符 简化接口或抽象类实现
静态内部类 外部类成员处 是 否 仅可访问静态成员 可用 public/private/protected 辅助类、工具类、Builder 模式

核心思想

内部类 = 一种在类中再定义类的机制,强化封装、灵活扩展。

  • 成员内部类 → 外部类的"成员"
  • 局部内部类 → 方法的"局部变量"
  • 匿名内部类 → 一次性"快捷实现类"
  • 静态内部类 → 独立于外部类存在的"嵌套类"
相关推荐
一水鉴天3 小时前
映射、哈希表与哈斯图:计算机科学的三种基线 20261003(元宝)
开发语言·人工智能
Frank_refuel3 小时前
C++11之一场名为“搬家”的 C++ 之旅
开发语言·c++
Wang's Blog4 小时前
Java 项目实战: 外卖平台优化-Nginx配置文件结构与块层级
java·开发语言·nginx
2601_962071574 小时前
类变量和全局变量的查找路径有什么区别?
开发语言·python
\光辉岁月/5 小时前
5.java-数组
java·开发语言
Java后端的Ai之路5 小时前
Python进阶探索29_eval内置函数
开发语言·python·探索·eval·内置函数
谢亮_vipxieliang6 小时前
Spring 事务失效的常见场景
java·开发语言·数据库·spring boot
时间的拾荒人6 小时前
Qt 多线程详解:从 QThread 到实战
开发语言·qt·面试
郑州光合科技余经理6 小时前
海外版外卖加盟:总站与分站配送规则怎么分开管
java·开发语言·前端·后端·uni-app·php·ai编程
2601_962071576 小时前
类变量和全局变量的生命周期有什么区别?
java·开发语言·jvm