rokevin
移动
前端
语言
  • 基础

    • Linux
    • 实施
    • 版本构建
  • 应用

    • WEB服务器
    • 数据库
  • 资讯

    • 工具
    • 部署
开放平台
产品设计
  • 人工智能
  • 云计算
计算机
其它
GitHub
移动
前端
语言
  • 基础

    • Linux
    • 实施
    • 版本构建
  • 应用

    • WEB服务器
    • 数据库
  • 资讯

    • 工具
    • 部署
开放平台
产品设计
  • 人工智能
  • 云计算
计算机
其它
GitHub
  • JDK 用到的设计模式

  • 一、引言
  • 二、单例模式(Singleton pattern)
    • 2.1 单例模式的定义与作用
    • 2.2 单例模式的典型实现
    • 2.3 JDK 中的单例模式实现
  • 三、工厂模式(Factory pattern)
    • 3.1 工厂模式的定义与作用
    • 3.2 工厂模式在 JDK 中的实现
    • 3.3 工厂模式的典型实现
    • 3.4 JDK 中的工厂模式实现
  • 四、观察者模式(Observer pattern)
    • 4.1 观察者模式的定义与作用
    • 4.2 观察者模式的典型实现
    • 4.3 JDK 中的观察者模式实现
  • 五、迭代器模式(Iterator pattern)
    • 5.1 迭代器模式的定义与作用
    • 5.2 迭代器模式的典型实现
    • 5.3 JDK 中的迭代器模式实现
    • 5.4 迭代器模式在 JDK 中的意义
  • 六、其他设计模式
    • 6.1 装饰器模式(Decorator pattern)
    • 6.2 策略模式(Strategy pattern)
    • 6.3 模板方法模式(Template Method pattern)
    • 6.4 适配器模式(Adapter pattern)
    • 6.5 外观模式(Facade pattern)
    • 6.6 代理模式(Proxy pattern)

JDK 用到的设计模式

一、引言

JDK(Java Development Kit)作为 Java 语言的核心库,其源码蕴含着诸多精妙的设计模式。这些设计模式不仅是 Java 语言强大功能的基石,更是开发者们学习软件设计与架构的绝佳范例。深入研究 JDK 源码中的设计模式,有助于我们理解 Java 语言的底层运作机制,提升代码质量,开发出更高效、可维护和可扩展的软件系统。本文将详细剖析 JDK 源码中常见的设计模式,包括单例模式、工厂模式、观察者模式、迭代器模式等,探讨它们的定义、作用、典型实现以及在 JDK 中的具体应用案例。

二、单例模式(Singleton pattern)

2.1 单例模式的定义与作用

单例模式属于创建型设计模式,核心要义是确保一个类仅有一个实例,并提供一个全局访问点供外部获取该唯一实例。在软件开发进程中,此模式用途广泛,常用于管理只需单一实例的资源或服务。像是配置文件管理,整个应用运行期间只需加载一次配置,单例模式可避免重复加载,节省资源;数据库连接池采用单例,能防止创建多个连接池导致资源浪费与连接冲突;线程池作为全局资源协调线程任务,以单例形式存在可优化资源调度;日志记录系统若为单例,能保证日志输出的一致性与连贯性。

从性能和效率层面考量,单例模式优势显著。它规避了资源的重复创建与销毁开销,降低系统资源消耗,提升运行效率。不过,在多线程环境下,单例模式实现需兼顾线程安全问题,防止多个线程同时创建实例,破坏单例特性。

在 JDK 源码里,单例模式身影频现。Java 运行时环境诸多服务与管理器,如内存管理、垃圾回收、类加载等机制,均以单例模式呈现,确保 JVM 进程内仅有一个实例运行,保障系统稳定运行。以垃圾回收为例,若存在多个垃圾回收实例同时工作,不仅会造成资源浪费,还可能引发数据不一致等严重问题,单例模式有效杜绝此类隐患。

2.2 单例模式的典型实现

单例模式常见实现方式有饿汉式、懒汉式与双重检查锁。

  • 饿汉式单例模式:类加载时就完成实例初始化。优点是获取对象速度快,因实例早已创建好,调用时可直接返回;缺点是类加载相对缓慢,若实例创建过程涉及复杂初始化操作或占用大量资源,而后续又不一定立即使用该实例,会造成资源闲置。例如,某些配置类在应用启动时就加载所有配置信息,若部分配置使用频率极低,就可能浪费内存。代码示例:
public class EagerSingleton {
    private static final EagerSingleton instance = new EagerSingleton();
 
    private EagerSingleton() {}
 
    public static EagerSingleton getInstance() {
        return instance;
    }
}
  • 懒汉式单例模式:将实例创建延迟到首次使用时。好处是类加载快,初始资源消耗少;弊端是运行时获取对象较慢,因首次调用需等待实例创建,且多线程环境下若未妥善处理同步问题,易产生多个实例,破坏单例规则。例如,在一个 Web 应用中,用户登录模块的日志记录器,若用户未进行登录操作(即未触发日志记录需求),日志记录器实例便不会提前创建,节省资源。代码示例:
public class LazySingleton {
    private static LazySingleton instance;
 
    private LazySingleton() {}
 
    public static synchronized LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

这里使用synchronized关键字确保线程安全,但同步开销较大,每次获取实例都需竞争锁资源。

  • 双重检查锁单例模式:是对懒汉式的优化。在懒汉式基础上,通过双重检查锁定,首次检查若实例已存在则直接返回,避免不必要的同步;若未创建,才进入同步块再次检查并创建实例。既保障线程安全,又减少同步开销。不过,其实现需在 Java 5 及以上版本,借助volatile关键字确保多线程环境下的可见性,禁止指令重排序优化,防止因重排序导致线程获取到未完全初始化的实例。示例代码:
public class DoubleCheckedLockingSingleton {
    private static volatile DoubleCheckedLockingSingleton instance;
 
    private DoubleCheckedLockingSingleton() {}
 
    public static DoubleCheckedLockingSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckedLockingSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckedLockingSingleton();
                }
            }
        }
        return instance;
    }
}

实际应用中,开发者要依据具体需求与场景抉择合适的单例模式实现。像电力行业远程抄表系统,若表计规约繁杂且系统开销大,为降低初始化成本,可选用懒汉式或双重检查锁单例模式,确保每个规约对应唯一实例,提升性能;分布式发电微网系统暂态仿真程序,为保证模块低耦合、高内聚,增强程序扩展性与复用性,核心模块采用饿汉式单例模式维护全局唯一性为宜。

单例模式还能与其他设计模式联用,解决复杂设计难题。如软件界面国际化开发,单例模式保障国际化资源管理全局唯一,结合观察者模式,当语言环境切换时,能及时通知界面元素更新,实现界面动态刷新,充分发挥各模式优势,提升系统灵活性与可维护性。

2.3 JDK 中的单例模式实现

在 JDK 里,单例模式运用广泛。以Runtime类为例,它采用饿汉式单例模式,类加载时就创建唯一实例。Runtime类封装 Java 运行时环境,每个 Java 应用程序都有且仅有一个该实例,由 JVM 负责实例化。开发者通过getRuntime方法便捷获取当前运行时环境引用,执行垃圾回收、内存管理等操作,确保运行时环境操作的一致性,避免资源浪费与冲突。代码如下:

public class Runtime {
    private static Runtime currentRuntime = new Runtime();
 
    public static Runtime getRuntime() {
        return currentRuntime;
    }
 
    private Runtime() {}
 
    // 其他与运行时环境相关的方法,如gc()用于垃圾回收等
}

再看java.lang.System类,其中out和err字段分别代表标准输出流与错误输出流,类加载时初始化,应用程序执行全程保持不变。开发者能直接使用,无需操心实例创建管理,像System.out.println("Hello, World!");这般便捷输出信息,得益于单例模式保障输出流的唯一性与稳定性。

深入 JDK 内部实现,处理系统级服务或功能时,单例模式屡见不鲜。它确保服务全局唯一且可访问,提升系统稳定性,降低代码复杂性,让开发者专注业务逻辑。如 JDK 底层的系统配置读取服务,以单例形式存在,保证整个 JVM 进程使用同一套配置信息,避免配置混乱。

然而,使用单例模式需谨慎斟酌适用场景,滥用会致代码可测试性、可维护性下滑。例如,单例对象持有大量资源或执行耗时操作,在应用程序生命周期内资源持续被占用,无法释放,易引发内存泄漏风险。故而开发中要依实际需求与设计准则,合理决策是否运用单例模式。

通过钻研 JDK 中的单例模式实现,开发者能深化对其原理与应用场景的理解,在项目中灵活运用,提升代码质量与系统性能。同时,探究 JDK 源码其他设计模式同样意义重大,全方位助力开发者提升软件设计与开发能力。

三、工厂模式(Factory pattern)

3.1 工厂模式的定义与作用

工厂模式是一种创建对象的设计模式,核心思想是将对象的创建与使用分离。它把复杂的对象创建逻辑封装在工厂类中,客户端代码只需关心获取所需对象,无需了解对象具体的创建过程与细节。这极大降低了代码耦合度,提高系统的可维护性与可扩展性。

当系统创建对象的过程复杂多变,例如涉及多种初始化参数、不同的创建条件判断,或者对象创建依赖于特定环境配置时,工厂模式的优势就凸显出来。以数据库连接对象创建为例,不同数据库(如 MySQL、Oracle)的连接配置、驱动加载方式各异,若直接在业务代码中编写创建逻辑,代码将充斥大量与业务无关的数据库连接细节,后期切换数据库或修改连接参数时,牵一发而动全身。使用工厂模式,将这些复杂的创建过程封装在工厂类,业务代码只需向工厂请求所需数据库连接对象,使代码结构清晰,易于维护。

工厂模式还能方便地实现对象创建的复用,避免重复代码。若多个地方需要创建相似但稍有差异的对象,工厂类可统一处理这些差异,返回符合需求的对象实例,提高代码复用率,减少潜在错误。

3.2 工厂模式在 JDK 中的实现

在 JDK 中,工厂模式应用广泛,极大优化了对象创建流程。以java.util.Calendar类为典型,它是抽象类,提供日期和时间字段转换及处理方法,但自身不直接实例化,而是通过静态工厂方法getInstance()获取特定于当前默认时区和语言环境的Calendar对象。代码如下:

public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar> {
    //... 省略其他成员变量和方法
    public static Calendar getInstance() {
        return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
    }
    private static Calendar createCalendar(TimeZone zone, Locale aLocale) {
        // 根据时区和语言环境创建相应的Calendar实例
        //... 省略实现细节
    }
}

在此例中,getInstance()充当工厂方法角色,依据当前默认时区和语言环境创建并返回适配的Calendar子类实例。客户端代码调用该方法就能获取合适的日历对象,无需知晓具体子类实现,降低耦合度,后续若需调整时区或语言环境下的日历表现,只需修改工厂方法内部创建逻辑,不影响外部使用。

除Calendar类,java.util.Collections类提供多个静态工厂方法,用于创建不可变集合、同步集合等。像Collections.unmodifiableList(List<? extends T> list)可将普通列表转换为不可变列表,防止外部误修改数据,内部通过工厂模式封装创建不可变集合的复杂过程,开发者轻松获取所需特性集合对象。

java.net.URL类中的URLStreamHandlerFactory接口和setURLStreamHandlerFactory方法构成简单工厂模式,用于自定义URL流处理器创建过程。不同协议(如http、https、ftp)的URL在打开连接时需要不同的流处理器,工厂模式使得URL类能依据协议类型灵活创建对应的流处理器,拓展网络访问功能,增强代码灵活性。

3.3 工厂模式的典型实现

工厂模式常见的有简单工厂、工厂方法、抽象工厂三种类型。

  • 简单工厂模式:简单工厂模式将对象创建逻辑封装在一个工厂类的静态方法中,根据传入参数决定创建哪种具体产品对象。上述java.util.Calendar类的getInstance()方法就类似简单工厂,它隐藏了具体Calendar子类的创建细节,依据系统默认信息返回合适实例。优点是实现简单,缺点是不符合开闭原则,若新增产品类型,需修改工厂类方法逻辑。示例:
public class SimpleFactory {
    public static Product createProduct(String type) {
        if ("productA".equals(type)) {
            return new ConcreteProductA();
        } else if ("productB".equals(type)) {
            return new ConcreteProductB();
        }
        return null;
    }
}

这里Product是抽象产品类,ConcreteProductA和ConcreteProductB是具体产品类,工厂类根据传入的产品类型字符串创建相应对象。

  • 工厂方法模式:工厂方法模式是在简单工厂基础上,将工厂类的创建方法抽象成抽象方法,由具体工厂子类实现。这样当新增产品时,只需新增具体工厂子类,无需修改已有工厂代码,符合开闭原则。以游戏开发为例,不同风格游戏(如角色扮演、射击、策略)可能有不同角色创建需求,定义抽象CharacterFactory类,有抽象方法createCharacter(),各游戏风格对应具体工厂子类实现该方法创建专属角色。示例:
public abstract class CharacterFactory {
    public abstract Character createCharacter();
}
 
public class RPGCharacterFactory extends CharacterFactory {
    @Override
    public Character createCharacter() {
        return new RPGCharacter();
    }
}
 
public class ShooterCharacterFactory extends CharacterFactory {
    @Override
    public Character createCharacter() {
        return new ShooterCharacter();
    }
}

其中Character是抽象角色类,RPGCharacter、ShooterCharacter是具体角色类。

  • 抽象工厂模式:抽象工厂模式最为复杂,工厂类创建一系列相关产品对象,客户端调用抽象工厂创建所需的一组产品。常用于创建产品族,产品族内各产品相互搭配使用。如界面开发,不同操作系统(Windows、Mac、Linux)有对应的按钮、文本框、菜单等组件,抽象GUIFactory类定义创建按钮、文本框等抽象方法,各操作系统具体工厂子类实现这些方法,创建适配该系统的组件族。示例:
public abstract class GUIFactory {
    public abstract Button createButton();
    public abstract TextField createTextField();
    public abstract Menu createMenu();
}
 
public class WindowsGUIFactory extends GUIFactory {
    @Override
    public Button createButton() {
        return new WindowsButton();
    }
    @Override
    public TextField createTextField() {
        return new WindowsTextField();
    }
    @Override
    public Menu createMenu() {
        return new WindowsMenu();
    }
}

这里Button、TextField、Menu是抽象组件类,WindowsButton等是具体组件类。

3.4 JDK 中的工厂模式实现

在 JDK 的反射机制中,Class类提供多个工厂方法用于创建不同类型对象。通过newInstance方法,能创建一个类的新实例,隐藏对象创建细节,客户端代码无需关心类的构造过程,降低耦合度。示例:

public class MyClass {
    //... 类定义
}
 
public class Client {
    public static void main(String[] args) throws Exception {
        Class<MyClass> clazz = MyClass.class;
        MyClass obj = clazz.newInstance(); // 使用Class类的工厂方法创建对象
        //... 使用obj对象
    }
}

四、观察者模式(Observer pattern)

4.1 观察者模式的定义与作用

在软件设计架构里,观察者模式构建起一种高效处理对象间依赖关系的机制。其核心在于将观察者与被观察者(主题)解耦分离,允许二者独立变更,极大增强系统的可维护性与可扩展性。

观察者模式涉及两大关键角色:主题(Subject)与观察者(Observer)。主题作为被观察对象,内部维护一组观察者引用,状态变动时负责通知这些观察者;观察者则订阅主题,一旦收到主题状态变更通知,即刻执行相应响应动作。

这种模式为软件系统带来诸多优势。一方面,实现事件驱动编程模型,将事件发送者(主题)与接收者(观察者)解耦。以 GUI 编程为例,按钮作为主题,用户点击按钮时,按钮状态改变,它会通知所有注册的观察者,如关联的事件处理程序,这些观察者随即更新 UI、触发后台任务等,各组件职责分明,系统交互逻辑清晰流畅。另一方面,在日志记录场景,日志系统作为主题,当新日志产生(状态变化),注册的观察者如日志存储模块、日志分析模块能及时收到通知,分别执行存储和分析操作,实现不同功能模块的松耦合协作,系统易于扩展新的日志处理需求。

然而,观察者模式并非完美无缺,使用不当可能引发性能问题或内存泄漏风险。若主题频繁通知大量观察者,系统需耗费大量资源用于通知传递与处理,导致性能下降;若观察者未正确注销,长期占用内存,随着系统运行,内存资源逐渐耗尽。所以应用时需权衡利弊,依具体场景审慎抉择。

4.2 观察者模式的典型实现

典型的观察者模式实现通常涵盖以下关键组件:

  • 定义观察者接口:该接口规定了观察者在收到主题状态变化通知时需执行的更新方法。例如在 JDK 的java.util.Observer接口中,定义了update(Observable o, Object arg)方法,此方法接收主题对象及附带参数,供观察者依据通知信息做出针对性响应。
  • 定义主题接口:主题接口负责声明与观察者交互的方法,主要包括注册观察者、移除观察者以及通知观察者状态变更的方法。JDK 中的java.util.Observable类就承担此角色,它提供addObserver(Observer o)、deleteObserver(Observer o)和notifyObservers(Object arg)等方法,使主题能有效管理观察者集合,并在合适时机触发通知流程。
  • 实现具体主题:具体主题类需实现或继承主题接口,在内部维护观察者列表,并在自身状态改变时,精准调用通知方法,确保所有注册观察者能即时收到状态更新信息。例如,一个温度监测系统中的温度传感器类作为具体主题,当监测到温度变化,通过调用notifyObservers方法,向关注温度的各类显示设备、报警系统等观察者传递新温度值。
  • 实现具体观察者:具体观察者类负责实现观察者接口,在update方法中定义收到通知后的具体行为逻辑。如上述温度监测场景中,一个 LED 显示屏观察者类,实现Observer接口后,在update方法内解析接收到的温度数据,并将其格式化显示在屏幕上。

4.3 JDK 中的观察者模式实现

在 JDK 中,观察者模式的实现主要依托java.util.Observable类和java.util.Observer接口。不过自 Java 9 起,这二者因不支持泛型且违背单一职责原则被标记为废弃。但剖析其机制仍有助于理解观察者模式在 JDK 中的运用。

Observable类作为可观察的主题抽象,内部以列表形式维护观察者对象,并提供添加、删除及通知观察者的完备方法集。一旦自身状态有变,setChanged方法被调用标记状态已更新,接着notifyObservers方法启动通知流程,按注册顺序逐一调用观察者的update方法。

Observer接口定义的update方法则是观察者接收通知的入口,不同观察者依据自身业务逻辑在该方法内定制响应行为。

以下是简化示例展示其用法:

import java.util.Observable;
import java.util.Observer;
 
public class ObservableDemo {
    public static void main(String[] args) {
        // 创建被观察对象
        MyObservable observable = new MyObservable();
        // 创建观察者对象并注册到被观察对象中
        Observer observer1 = new MyObserver("Observer 1");
        Observer observer2 = new MyObserver("Observer 2");
        observable.addObserver(observer1);
        observable.addObserver(observer2);
        // 改变被观察对象的状态,触发通知机制
        observable.setData("New data");
    }
}
 
class MyObservable extends Observable {
    private String data;
 
    public void setData(String data) {
        if (!this.data.equals(data)) {
            this.data = data;
            // 状态发生变化,通知所有观察者
            setChanged();
            notifyObservers(data);
        }
    }
}
 
class MyObserver implements Observer {
    private String name;
 
    public MyObserver(String name) {
        this.name = name;
    }
 
    @Override
    public void update(Observable o, Object arg) {
        System.out.println(name + " received update: " + arg);
    }
}

尽管java.util.Observable和java.util.Observer渐显颓势,但 JDK 众多组件与框架仍对观察者模式青睐有加,且实现更为灵活强大。如 JavaBeans 的PropertyChangeListener,在 JavaBean 属性变更时,相关监听器(观察者)能即时响应,支持更精细的事件过滤与自定义事件对象,精准适配业务需求;Swing 中的EventListener,在 GUI 组件交互事件触发时,高效驱动对应事件处理逻辑,保障界面交互的流畅性与响应性。

此外,Java 还支持其他实现观察者模式的途径,如利用回调接口,业务代码能灵活定制通知回调逻辑;借助 Java 8 的 Lambda 表达式和函数式接口,以更简洁、函数式编程风格实现观察者模式,适配现代编程习惯,满足多样化应用场景需求。

总之,观察者模式在 Java 编程领域占据重要地位,广泛应用于事件处理、状态监听、UI 更新等关键场景。深入理解并掌握其原理与实现,开发者便能在构建 Java 应用程序时,灵活运用该模式打造高效、可扩展、易维护的软件系统。

在 JDK 中,观察者模式的实现不仅体现在java.util.Observable类和java.util.Observer接口上,还深度融入许多 Java 库和框架,尤其在事件处理机制中大放异彩。

java.util.Observable类和java.util.Observer接口虽提供基础观察者模式实现,但功能相对局限,难以应对复杂事件处理诉求。故而在 JDK 的 GUI 库(如 Swing 和 AWT)以及并发库等关键领域,衍生出更灵活、强大的观察者模式变种。

在 Java 的 GUI 库中,观察者模式堪称事件处理的基石。以按钮点击事件为例,按钮作为主题,内部状态因用户点击发生改变,随即触发通知机制,将点击事件传递给已注册的事件监听器(观察者),这些观察者可能是负责更新界面显示的组件、执行后台数据校验的逻辑模块等,它们依自身职责对事件做出响应,实现丰富多元的用户交互功能,如弹窗提示、数据刷新、导航跳转等,让 GUI 应用灵动鲜活。

Java 的并发库同样是观察者模式的重要舞台。例如java.util.concurrent.Future接口和java.util.concurrent.ExecutionException类构建的异步编程模型,Future对象代表异步计算结果,当计算完成,若其他线程(观察者)事先通过addCallback等方式为其绑定回调函数(监听器),计算结果就绪时,回调函数自动触发。这一机制允许开发者在异步任务完成后,无缝衔接后续处理逻辑,无需阻塞主线程,提升系统并发性能与响应速度,确保复杂业务流程高效流转。

综上,JDK 中的观察者模式实现丰富多样、灵活多变,全方位覆盖从简单状态更新到复杂事件处理、异步编程等多元场景。深入研习这些实现细节,开发者将精准拿捏观察者模式的应用精髓,在实战开发中巧妙化解各类难题,雕琢出健壮且优雅的软件佳作。

五、迭代器模式(Iterator pattern)

5.1 迭代器模式的定义与作用

迭代器模式作为一种行为型设计模式,精妙之处在于提供一种访问集合元素的规范方式,它能在不暴露集合内部结构的前提下,有序遍历集合中的元素。通过将集合的遍历操作从集合本身的结构逻辑中剥离出来,用户得以使用统一接口穿梭于不同集合类型之间,轻松访问集合内容。

此模式显著简化集合遍历流程,降低使用集合的复杂度,让开发者无需深入了解集合底层实现细节,即可按序获取元素。同时,代码可读性与可维护性得到极大提升,当集合内部结构调整,只要迭代器接口稳定,依赖该集合遍历的代码几乎无需改动,遵循开闭原则。例如,在处理数据列表展示的业务逻辑中,无论底层是数组实现的ArrayList,还是链表结构的LinkedList,借助迭代器,统一的遍历代码都能正常工作,清晰简洁。

5.2 迭代器模式的典型实现

迭代器模式典型实现分为内部迭代器和外部迭代器:

  • 内部迭代器:将遍历逻辑内置于集合类内部,用户只需调用集合类提供的特定遍历方法,如forEach,集合便在内部自动完成元素遍历,用户无需操心迭代细节,操作简便,但灵活性欠佳,难以满足复杂遍历需求,且集合类与遍历逻辑紧耦合。例如 JavaScript 中的数组forEach方法,开发者传入回调函数,数组内部按序执行回调处理每个元素。
  • 外部迭代器:独立于集合类构建迭代器对象,该对象封装遍历逻辑,用户通过操作迭代器显式控制遍历进程,如获取下一个元素、判断是否还有后续元素等。这种方式解耦集合与遍历逻辑,用户能依据需求灵活定制遍历策略,如跳跃式遍历、逆向遍历等,适应性强,是 Java 等编程语言中常用的迭代器实现形式。

5.3 JDK 中的迭代器模式实现

在 JDK 里,迭代器模式核心体现于java.util.Iterator接口和java.util.Collection接口的iterator()方法。

Iterator接口明确定义遍历集合的基本操作契约,涵盖hasNext()用于判断集合后续是否还有元素,next()获取当前位置下一个元素并将迭代器游标前移一位,remove()可选操作,用于删除迭代器刚返回的元素,部分集合因结构特性可能不支持该操作而抛出异常。

Collection接口作为集合体系的顶层抽象,iterator()方法承诺返回一个遵循Iterator接口规范的对象,为用户遍历集合元素开辟通道。

深入 JDK 集合框架底层,ArrayList、LinkedList等具体集合类均精心打造适配自身结构的迭代器实现。以ArrayList为例,内部定义私有内部类Itr实现Iterator接口:

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    //... 其他成员变量和方法
    public Iterator<E> iterator() {
        return new Itr();
    }
 
    private class Itr implements Iterator<E> {
        int cursor;       // 下一个要返回的元素的索引
        int lastRet = -1; // 上一个返回的元素的索引
        int expectedModCount = modCount; // 在迭代过程中,如果列表被修改,则抛出ConcurrentModificationException异常
 
        // 判断是否还有下一个元素
        public boolean hasNext() {
            return cursor!= size;
        }
 
        // 返回下一个元素,并将光标移动到下一个位置
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }
 
        // 移除当前元素(可选操作)
        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();
            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
 
        // 检查在迭代过程中列表是否被修改过(快速失败行为)
        final void checkForComodification() {
            if (modCount!= expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
}

Itr内部细致维护遍历状态,如当前游标位置,严格遵循Iterator接口规范,通过hasNext与next方法有序暴露ArrayList元素,同时借助checkForComodification机制实现快速失败,一旦集合在迭代期间被外部不当修改,立即抛出异常,保障数据一致性与遍历安全。

此外,JDK 中的for-each循环语法糖底层正是依托迭代器模式运作。当使用for-each遍历集合,编译器自动将其转换为使用迭代器的等效代码,让开发者以更简洁优雅的语法实现集合遍历,降低代码书写负担。

5.4 迭代器模式在 JDK 中的意义

迭代器模式在 JDK 中广泛渗透,为代码生态带来诸多红利。一方面,大幅提升代码复用性与可维护性,统一的Iterator接口让不同集合类的遍历代码具备通用性,减少重复开发,且集合内部结构调整时,外部遍历代码受影响极小。另一方面,赋予开发者灵活驾驭不同集合类型遍历的能力,以一致方式穿梭各类集合,如HashSet、TreeSet等,无需针对每种集合学习独特遍历技巧,降低学习成本,加速开发进程。

从设计原则审视,迭代器模式完美契合开闭原则与单一职责原则。开闭原则下,新增集合类型只要适配Iterator接口,已有遍历代码无需变动即可兼容;单一职责原则层面,集合类专注数据存储与管理,迭代器专职遍历逻辑,二者分工明确,系统结构清晰,易于拓展优化。

迭代器模式在 JDK 的疆域不止于集合框架,在 IO 流、网络编程等领域同样发光发热。如处理文件读取时,BufferedReader等流对象可借助迭代器模式按行读取内容,网络编程中解析数据包序列,迭代器模式助力有序提取数据,为复杂数据处理流程提供简洁有序的访问机制,全方位助力开发者轻松应对多样编程挑战。

六、其他设计模式

6.1 装饰器模式(Decorator pattern)

装饰器模式是一种结构型设计模式,它能够在不改变原有对象结构的基础上,动态地为对象添加额外的功能。其核心原理是创建一个装饰器类,该类与被装饰对象实现相同的接口,然后在装饰器类中持有被装饰对象的引用,并在需要时对被装饰对象的行为进行扩展或修改。

在 JDK 中,java.io包下的诸多类就巧妙运用了装饰器模式。以BufferedReader为例,它是对Reader的装饰。当我们需要从文件或其他数据源读取字符流时,如果直接使用FileReader,每次读取操作都可能直接与底层存储介质交互,效率较低。而BufferedReader通过在内部维护一个缓冲区,先将数据批量读入缓冲区,后续读取操作从缓冲区获取数据,大大提高了读取效率。代码示例如下:

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
 
public class DecoratorExample {
    public static void main(String[] args) throws IOException {
        // 使用FileReader读取文件
        FileReader fileReader = new FileReader("example.txt");
        // 使用BufferedReader装饰FileReader,增强读取功能
        BufferedReader bufferedReader = new BufferedReader(fileReader);
 
        String line;
        while ((line = bufferedReader.readLine())!= null) {
            System.out.println(line);
        }
 
        bufferedReader.close();
        fileReader.close();
    }
}

在这个例子中,BufferedReader就是装饰器,它装饰了FileReader,为其添加了缓冲功能,同时对外仍然表现为一个Reader,遵循相同的接口规范,使得代码在不改变原有读取逻辑的基础上,性能得到显著提升。

类似地,InputStream系列也有诸多装饰器应用。如BufferedInputStream对InputStream进行装饰,提供缓冲机制,加快数据读取速度;DataInputStream能在基础InputStream上,方便地读取基本数据类型,满足不同的数据处理需求。这些装饰器类可以层层嵌套,根据实际需求灵活组合,为对象动态赋予多种额外特性,让系统功能扩展变得轻松自如。

6.2 策略模式(Strategy pattern)

策略模式属于行为型设计模式,它的主旨是定义一系列算法,将每个算法封装成独立的策略类,使得这些算法可以相互替换,并且让算法的变化独立于使用算法的客户端。

在 JDK 的java.util.Comparator接口中就隐含着策略模式的思想。Comparator用于定义比较两个对象的策略,不同的实现类可以按照不同的规则来比较对象。例如,我们有一个Person类,包含姓名和年龄属性:

class Person {
    String name;
    int age;
 
    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

若要按照年龄对Person列表进行排序,我们可以创建一个实现Comparator<Person>接口的类:

import java.util.Comparator;
 
class AgeComparator implements Comparator<Person> {
    @Override
    public int compare(Person p1, Person p2) {
        return p1.age - p2.age;
    }
}

同样,如果想按照姓名排序,可以再创建一个NameComparator类。在排序时,客户端代码只需将相应的Comparator实现类传递给排序方法(如Collections.sort或Arrays.sort),就可以轻松切换排序策略:

import java.util.ArrayList;
import java.util.Collections;
 
public class StrategyExample {
    public static void main(String[] args) {
        ArrayList<Person> people = new ArrayList<>();
        people.add(new Person("Alice", 25));
        people.add(new Person("Bob", 30));
        people.add(new Person("Charlie", 20));
 
        // 使用年龄比较策略进行排序
        Collections.sort(people, new AgeComparator());
        for (Person person : people) {
            System.out.println(person.name + ", " + person.age);
        }
 
        // 使用姓名比较策略进行排序
        Collections.sort(people, new NameComparator());
        for (Person person : people) {
            System.out.println(person.name + ", " + person.age);
        }
    }
}

通过这种方式,排序算法的变化(即不同的比较策略)与执行排序的客户端代码分离,使得代码的扩展性和维护性大大增强,能够轻松应对各种复杂多变的排序需求。

6.3 模板方法模式(Template Method pattern)

模板方法模式是一种行为型设计模式,它在父类中定义一个算法的骨架,将一些步骤的实现延迟到子类中。这样既确保了算法的整体结构稳定,又给予子类一定的灵活性来定制部分步骤,使得子类可以在不改变算法整体流程的前提下,根据自身需求重写特定步骤。

在 JDK 中,java.util.AbstractList类就是模板方法模式的典型范例。它作为List接口的抽象实现类,定义了许多列表操作的通用框架。以addAll方法为例,它在AbstractList中的定义如下:

public boolean addAll(int index, Collection<? extends E> c) {
    rangeCheckForAdd(index);
    boolean modified = false;
    for (E e : c) {
        add(index++, e);
        modified = true;
    }
    return modified;
}

这里的add方法就是一个抽象方法,留给具体的子类(如ArrayList、LinkedList等)去实现。因为不同的列表实现对于元素添加的底层操作方式各异,ArrayList可能基于数组扩容和元素赋值,LinkedList则侧重于链表节点的插入操作。

再看get方法,同样在AbstractList中有基本定义:

public E get(int index) {
    rangeCheck(index);
    return elementData(index);
}

其中elementData是抽象方法,具体子类根据自身存储结构来实现该方法以获取指定索引处的元素。这种设计模式使得子类在遵循AbstractList设定的列表操作大框架下,能够精细优化各自的关键实现细节,提升整体代码复用性与扩展性。

当开发人员使用ArrayList或LinkedList时,无需关心底层复杂的模板搭建,只需专注于业务逻辑,而底层共性操作在父类得到统一保障,降低代码开发与维护成本。例如在一个简单的列表数据处理程序中:

import java.util.ArrayList;
import java.util.List;
 
public class TemplateMethodExample {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("Apple");
        list.add("Banana");
        list.add("Cherry");
 
        // 直接使用父类定义好的框架方法
        list.addAll(1, List.of("Date", "Elderberry"));
 
        for (String fruit : list) {
            System.out.println(fruit);
        }
    }
}

程序借助AbstractList及其子类构建的模板机制,便捷地实现列表元素添加与遍历,底层细节封装得当,上层代码简洁明了。

6.4 适配器模式(Adapter pattern)

适配器模式作为一种结构型设计模式,旨在将一个类的接口转换成客户端所期望的另一种接口,使原本不兼容的类能够协同工作。这就好比为不同规格的插头和插座之间制作一个转接头,让电器能顺利通电运行。

在 JDK 中,java.util.Arrays类的asList方法就运用了适配器模式。它能够把数组转换为List接口的实现,使得数组能以列表的形式参与各种操作。例如:

import java.util.Arrays;
import java.util.List;
 
public class AdapterExample {
    public static void main(String[] args) {
        String[] array = {"One", "Two", "Three"};
        // 将数组适配成List
        List<String> list = Arrays.asList(array);
 
        for (String element : list) {
            System.out.println(element);
        }
    }
}

原本的数组操作相对受限,而通过asList方法适配成List后,就能利用List丰富的接口方法,如contains、indexOf等进行更便捷的元素查找、判断等操作。

另外,在 Java 的图形界面编程(如 Swing、AWT)领域,适配器模式也大显身手。以MouseAdapter为例,它实现了MouseListener接口,为开发者提供了便捷的鼠标事件处理方式。通常情况下,直接实现MouseListener需要重写多个方法,哪怕只关注其中一两个鼠标事件(如点击、拖动),也得为其他不关心的方法提供空实现。而MouseAdapter作为适配器,默认提供了所有MouseListener方法的空实现,开发者只需继承它并重写感兴趣的方法即可,大大简化了代码编写过程:

import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
 
public class AdapterInGUIExample {
    public static void main(String[] args) {
        JFrame frame = new JFrame("Adapter Example");
        JLabel label = new JLabel("Click me!");
 
        // 使用MouseAdapter简化鼠标事件处理
        label.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                label.setText("Clicked!");
            }
        });
 
        frame.add(label);
        frame.setSize(300, 200);
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
}

如此一来,适配器模式有效填补接口差异鸿沟,让不同接口规范的组件无缝对接,提升开发效率,优化代码结构。

6.5 外观模式(Facade pattern)

外观模式属于结构型设计模式,它为复杂的子系统提供一个统一的、简化的高层接口,隐藏子系统的内部复杂性,使得客户端只需与这个外观接口交互,就能轻松获取子系统的综合服务,降低客户端与子系统之间的耦合度。

在 JDK 的java.util.logging包中,Logger类就扮演着外观角色。它封装了底层复杂的日志记录机制,包括日志级别设置、日志输出目的地配置、格式化等诸多细节。对于开发者而言,只需简单调用Logger的几个关键方法,如info、warning、severe等来记录不同级别信息:

import java.util.logging.Logger;
 
public class FacadeExample {
    private static final Logger LOGGER = Logger.getLogger(FacadeExample.class.getName());
 
    public static void main(String[] args) {
        LOGGER.info("This is an informational message.");
        LOGGER.warning("Something might be wrong.");
        LOGGER.severe("Critical error occurred.");
    }
}

在底层,Logger类协调着多个组件,如LogManager负责全局日志管理,Handler类处理日志输出流向(控制台、文件等),Formatter类将日志信息格式化,但这些复杂操作都被Logger的简洁接口所掩盖。开发者无需深入了解日志系统每一个组件的运作原理,就能高效完成日志记录任务,一旦日志子系统底层升级优化,只要Logger外观接口稳定,上层客户端代码基本不受影响。

同样,在数据库连接操作方面,JDBC(Java Database Connectivity)提供的DriverManager类也运用了外观模式。它统一管理各种数据库驱动的加载与连接获取过程,开发人员只需传入数据库连接字符串等基本信息,就能从DriverManager获取数据库连接,而无需操心不同数据库驱动(如 MySQL、Oracle 驱动)加载方式、协议细节等差异:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
 
public class FacadeInJDBCExample {
    public static void main(String[] args) throws SQLException {
        String url = "jdbc:mysql://localhost:3606/mydb";
        String username = "root";
        String password = "password";
 
        // 通过DriverManager获取数据库连接,它隐藏了底层驱动加载等复杂过程
        Connection connection = DriverManager.getConnection(url, username, password);
 
        // 后续进行数据库操作
 
        connection.close();
    }
}

外观模式通过简化接口,将客户端从复杂子系统解脱出来,既提升开发便捷性,又增强系统整体可维护性。

6.6 代理模式(Proxy pattern)

代理模式是一种结构型设计模式,它引入一个代理对象来控制对真实对象的访问。代理对象与真实对象实现相同的接口,客户端调用时先接触代理对象,代理对象根据预设规则决定是否以及如何调用真实对象,常用于在访问真实对象前进行权限验证、懒加载、缓存等附加操作。

在 JDK 动态代理机制中,java.lang.reflect.Proxy类和InvocationHandler接口协同实现代理功能。例如,假设有一个简单的服务接口Service及其实现类ServiceImpl:

interface Service {
    void doSomething();
}
 
class ServiceImpl implements Service {
    @Override
    public void doSomething() {
        System.out.println("Doing something in the real service.");
    }
}

要为ServiceImpl创建代理,首先实现InvocationHandler接口:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
 
class ServiceProxy implements InvocationHandler {
    private final Object target;
 
    ServiceProxy(Object target) {
        this.target = target;
    }
 
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 在调用真实对象方法前,可以添加权限验证等逻辑
        System.out.println("Before invoking the real method.");
        Object result = method.invoke(target, args);
        // 在调用真实对象方法后,可以添加日志记录等逻辑
        System.out.println("After invoking the real method.");
        return result;
    }
}

然后通过Proxy类创建代理对象:

import java.lang.reflect.Proxy;
 
public class ProxyExample {
    public static void main(String[] args) {
        Service realService = new ServiceImpl();
        ServiceProxy proxy = new ServiceProxy(realService);
        Service serviceProxy = (Service) Proxy.newProxyInstance(Service.class.getClassLoader(),
                new Class[]{Service.class}, proxy);
 
        serviceProxy.doSomething();
    }
}

在上述示例中,ServiceProxy作为代理,在invoke方法中灵活嵌入前置、后置操作,客户端调用代理对象serviceProxy的doSomething方法时,会先执行代理中的前置逻辑,再调用真实对象ServiceImpl的对应方法,最后执行后置逻辑,实现对真实对象访问的有效管控。

在网络编程领域,代理模式也有诸多应用。如网络代理服务器,它充当客户端与目标服务器之间的代理,一方面可以缓存目标服务器返回的数据,当客户端再次请求相同资源时,直接从缓存返回,减少网络传输;另一方面可以对客户端请求进行过滤、转发等操作,保障网络访问的安全性与高效性。代理模式凭借其独特的间接访问管控特性,为软件系统架构优化、功能拓展提供强大助力。

最近更新:: 2025/10/22 15:36
Contributors: luokaiwen