JDK源码里藏了多少设计模式?我帮你扒出来了
面试官:”你说说JDK源码里用了哪些设计模式?”
我:”…”
相信我,你不是一个人在战斗。多少人在这个问题上翻过车。设计模式书本上都看过,但一到面试就说不出来,因为不知道这些模式在源码里到底是怎么用的。
今天我就把这些藏在JDK源码里的设计模式全部扒出来,不仅告诉你是什么,还告诉你为什么,更告诉你怎么用。保证你面试的时候能讲得面试官频频点头。
先来一张总览图,看看JDK里都藏了哪些模式:

别慌,一个一个来。
一、创建型模式:对象是怎么造出来的
创建型模式关注的是”对象怎么创建”这个问题。为什么要关注创建方式?因为创建方式直接影响代码的可维护性、可扩展性,甚至性能。
1.1 单例模式:我就一个实例
核心原理:保证一个类只有一个实例,并提供一个全局访问点。
使用场景:
-
配置管理器,全局只需要一个实例 -
连接池、线程池 -
日志管理器 -
缓存管理器
为什么用单例?因为有些对象真的只需要一个就够了。你搞两个 Runtime 试试?垃圾回收会乱的。
JDK里的应用:
Runtime:每个JVM只有一个Runtime
publicclassRuntime{privatestatic Runtime currentRuntime = new Runtime();publicstatic Runtime getRuntime(){return currentRuntime; }privateRuntime(){}}
看看你怎么用:
Runtime runtime = Runtime.getRuntime();runtime.gc(); // 调用垃圾回收runtime.maxMemory(); // 最大内存runtime.totalMemory(); // 总内存
Integer 的缓存池:-128到127的单例
publicstatic Integer valueOf(int i){if (i >= IntegerCache.low && i <= IntegerCache.high)return IntegerCache.cache[i + (-IntegerCache.low)];returnnew Integer(i);}
// IntegerCache 内部实现privatestaticclassIntegerCache{staticfinalint low = -128;staticfinalint high;staticfinal Integer cache[];static {// 默认缓存 -128 到 127 high = 127; cache = new Integer[(high - low) + 1];// 初始化缓存数组 }}
这就是为什么面试官总问 ==和 equals的区别:
Integer a = 127;Integer b = 127;System.out.println(a == b); // true,因为从缓存取的是同一个对象Integer c = 128;Integer d = 128;System.out.println(c == d); // false,因为超过缓存范围是新对象
双重检查锁实现单例:
publicclassSingleton{privatevolatilestatic Singleton instance;publicstatic Singleton getInstance(){if (instance == null) {synchronized (Singleton.class) {if (instance == null) { instance = new Singleton(); } } }return instance; }}
这里为什么要用 volatile?因为 new Singleton()不是原子操作,会被指令重排导致返回一个不完整的对象。
枚举单例:
publicenum SingletonEnum { INSTANCE;publicvoiddoSomething(){// ... }}
《Effective Java》推荐的方式,天然防反射、防序列化破坏。
1.2 工厂模式:我不直接new
核心原理:定义一个创建对象的接口,但让子类决定实例化哪个类。工厂方法把对象的创建延迟到子类。
使用场景:
-
对象创建逻辑复杂 -
需要根据配置或环境创建不同实现 -
想要封装创建逻辑,不让调用者知道具体类名
为什么用工厂?直接 new 多简单?但如果创建逻辑变了,你得改所有调用方。工厂模式把创建逻辑集中管理,改一处就行。
JDK里的应用:
Calendar:日历工厂
publicabstractclassCalendarimplementsSerializable, Cloneable{publicstatic Calendar getInstance(){return createCalendar(TimeZone.getDefault(), Locale.getDefault()); }privatestatic Calendar createCalendar(TimeZone zone, Locale aLocale){// 根据地区和时间zone创建不同的日历实现if (aLocale.hasExtensions()) { String caltype = aLocale.getUnicodeLocaleType("ca");if (caltype != null) {switch (caltype) {case"buddhist":returnnew BuddhistCalendar(zone, aLocale);case"japanese":returnnew JapaneseImperialCalendar(zone, aLocale);case"gregory":returnnew GregorianCalendar(zone, aLocale); } } }returnnew GregorianCalendar(zone, aLocale); }}
你调用 Calendar.getInstance(),根本不知道返回的是 GregorianCalendar 还是 BuddhistCalendar,工厂模式帮你屏蔽了具体实现。
// 在中国返回 GregorianCalendarCalendar c1 = Calendar.getInstance(); // 2026-03-27// 在泰国返回 BuddhistCalendarCalendar c2 = Calendar.getInstance(Locale.forLanguageTag("th-TH"), TimeZone.getDefault());
DriverManager:数据库驱动工厂
publicclassDriverManager{publicstatic Connection getConnection(String url)throws SQLException {// 根据url自动选择合适的驱动// jdbc:mysql://localhost:3306/db -> MySQL驱动// jdbc:oracle:thin:@localhost:1521:orcl -> Oracle驱动 }}
你只需要写一个 URL,DriverManager 自动帮你找对应的驱动。这也就是为什么你只需要引入一个 mysql-connector-j jar 包就能连 MySQL。
简单工厂 vs 工厂方法 vs 抽象工厂:
// 简单工厂:一个工厂类,根据参数创建不同产品classAnimalFactory{publicstatic Animal create(String type){if ("dog".equals(type)) returnnew Dog();if ("cat".equals(type)) returnnew Cat();thrownew IllegalArgumentException(); }}// 工厂方法:抽象工厂抽象方法,子类决定创建什么interfaceAnimalFactory{Animal create();}classDogFactoryimplementsAnimalFactory{public Animal create(){ returnnew Dog(); }}// 抽象工厂:创建一系列相关对象interfaceProductFactory{Phone createPhone();Tablet createTablet();}
1.3 建造者模式:一点点组装
核心原理:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
使用场景:
-
对象构造参数很多,很多是可选的 -
希望创建不同表示的对象 -
想把对象构建和表示分离
为什么用建造者?如果一个类有 20 个参数,其中 10 个是可选的,用构造方法你得写多少个重载?建造者模式完美解决这个问题。
JDK里的应用:
StringBuilder:组装字符串
StringBuilder sb = new StringBuilder();String result = sb.append("hello") .append(" ") .append("world") .toString();
链式调用,一步步组装出最终结果,这就是建造者模式的精髓。
StringBuffer,几乎一模一样,也是建造者模式。
JDK9 引入的 List.of()、Set.of()、Map.of():
List<String> list = List.of("a", "b", "c");Set<Integer> set = Set.of(1, 2, 3);Map<String, Integer> map = Map.of("a", 1, "b", 2);
内部实现也是建造者模式:
// JDK 17 List.of 源码简化static <E> List<E> of(E... elements){switch (elements.length) {case0:returnnew EmptyList<>();case1:returnnew SingletonList<>(elements[0]);default:returnnew ListN<>(elements); }}// ListN 内部使用 Objects.checkIndex 确保不越界
使用建造者模式优化一个复杂对象:
// 不用建造者 - 构造函数地狱User user = new User("张三", 25, "北京", "朝阳区", "xxx@email.com", "13800138000", true);// 用建造者 - 清晰优雅User user = User.builder() .name("张三") .age(25) .city("北京") .district("朝阳区") .email("xxx@email.com") .phone("13800138000") .vip(true) .build();
建造者模式的最佳实践 – Lombok @Builder:
@BuilderpublicclassUser{private String name;privateint age;private String city;private String email;private String phone;privateboolean vip;}
一行注解,生成完整的建造者类。
1.4 原型模式:复制才是最快的
核心原理:用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。
使用场景:
-
创建对象成本较高(数据库查询、网络请求) -
需要避免复杂的创建过程 -
对象状态相同,只有少量差异
为什么用原型?直接 new 一个对象要从头初始化,但如果我已经有了一个差不多一样的对象,复制它不香吗?
JDK里的应用:
Cloneable:原型模式
publicclassUserimplementsCloneable{private String name;privateint age;private List<String> hobbies;@Overrideprotected User clone()throws CloneNotSupportedException {return (User) super.clone(); }}
浅拷贝 vs 深拷贝:
// 浅拷贝 - 引用类型字段不拷贝,只拷贝引用@Overrideprotected User clone(){return (User) super.clone();}// 深拷贝 - 引用类型也新建一份@Overrideprotected User clone(){ User user = (User) super.clone(); user.hobbies = new ArrayList<>(this.hobbies); // 新建Listreturn user;}
Spring 中的 Prototype scope 就是原型模式的应用:
@Component@Scope("prototype")publicclassPrototypeBean{// 每次 getBean 都创建新实例}
二、结构型模式:类和类怎么拼
结构型模式关注的是”类和类怎么组装成更大的结构”。这些模式帮我们设计出灵活、可复用的类结构。
2.1 代理模式:我要控制你
核心原理:为其他对象提供一种代理以控制对这个对象的访问。
使用场景:
-
远程代理:调用远程服务 -
虚代理:延迟加载 -
保护代理:权限控制 -
智能引用:访问对象时增加额外操作
为什么用代理?你不能直接访问目标对象,或者访问目标对象时需要增加额外控制。代理模式在不改变目标对象的情况下,增强其行为。
JDK里的应用:
JDK Dynamic Proxy:动态代理
publicinterfaceInvocationHandler{Object invoke(Object proxy, Method method, Object[] args)throws Throwable;}
来看看怎么用:
publicclassUserServiceProxyimplementsInvocationHandler{private Object target;public Object bind(Object target){this.target = target;return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(),this ); }@Overridepublic Object invoke(Object proxy, Method method, Object[] args){ System.out.println("方法执行前..." + method.getName());long startTime = System.currentTimeMillis(); Object result = method.invoke(target, args);long endTime = System.currentTimeMillis(); System.out.println("方法执行后..." + method.getName() + ", 耗时: " + (endTime - startTime) + "ms");return result; }}
使用方式:
UserService userService = new UserServiceImpl();UserService proxy = (UserService) new UserServiceProxy().bind(userService);proxy.findById(1); // 自动打印日志和耗时
Spring 的 AOP 底层就是靠这个实现的。在方法前后加日志、加事务、加权限控制,都靠代理模式。
CGLIB vs JDK Dynamic Proxy:
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Spring 5.x 默认使用 CGLIB,因为性能更好。
静态代理 vs 动态代理:
// 静态代理 - 手动写代理类classUserServiceStaticProxyimplementsUserService{private UserService target;publicUserServiceStaticProxy(UserService target){this.target = target; }@Overridepublic User findById(Long id){// 增强逻辑return target.findById(id); }}
静态代理需要为每个类写一个代理类,动态代理一个类搞定所有。
2.2 装饰器模式:我要增强你
核心原理:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
使用场景:
-
想在运行时动态增加功能 -
需要增强的对象很多,不适合用继承 -
想增强单个对象,而不是整个类
为什么用装饰器?继承也能增强功能,但继承是静态的,装饰器是动态的。而且继承会产生类爆炸,装饰器不会。
JDK里的应用:
IO流:层层包装
// 原始流FileInputStream fis = new FileInputStream("test.txt");// 加缓冲BufferedInputStream bis = new BufferedInputStream(fis);// 加行号LineNumberInputStream lnis = new LineNumberInputStream(bis);// 加数据转换DataInputStream dis = new DataInputStream(lnis);
每个装饰器都在上一层的基础上增强功能,但不需要修改原始类。这就是装饰器模式的威力:
// 你以为你用的是 FileInputStream?// 其实你用的是 DataInputStream -> LineNumberInputStream -> BufferedInputStream -> FileInputStreamDataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream("test.txt") ));// 读取数据,自动带缓冲、自动行号dis.readInt();dis.readUTF();
Collections.unmodifiableList():只读装饰器
List<String> list = new ArrayList<>();list.add("a");List<String> unmodifiable = Collections.unmodifiableList(list);// unmodifiable.add("b"); // 抛 UnsupportedOperationExceptionlist.add("c"); // 原始list可以添加System.out.println(unmodifiable); // [a, c],也能看到变化
返回的是一个装饰后的集合,你调用 add() 会抛异常,但原始 list 毫发无损。
Collections.synchronizedList():线程安全装饰器
List<String> syncList = Collections.synchronizedList(new ArrayList<>());syncList.add("a"); // 线程安全
装饰器 vs 代理:傻傻分不清?
|
|
|
|
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
简单来说:装饰器是”我想增强你”,代理是”你先找我,我再找他”。
2.3 适配器模式:接口不一致怎么办
核心原理:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
使用场景:
-
旧系统接口与新系统不兼容 -
想使用一个已经存在的类,但其接口不符合你的需求 -
统一多个类的接口
为什么用适配器?你的系统和第三方系统对接,接口不一样怎么办?自己改代码?第三方代码你能改吗?适配器帮你解决。
JDK里的应用:
InputStreamReader:字节转字符
publicclassInputStreamReaderextendsReader{privatefinal StreamDecoder sd;publicInputStreamReader(InputStream in){super(in); sd = new StreamDecoder(in, this); }@Overridepublicintread(char[] cbuf, int offset, int length)throws IOException {return sd.read(cbuf, offset, length); }}
InputStream 是字节流,Reader 是字符流,中间隔了十万八千里,适配器帮你打通:
// InputStream -> InputStreamReader -> BufferedReaderBufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("test.txt") ));String line = reader.readLine(); // 按行读取
Arrays.asList():数组转集合
String[] array = {"a", "b", "c"};List<String> list = Arrays.asList(array);
数组转 List,适配器模式了解一下。数组没有 List 的那些方法,asList() 帮你适配。
Enumeration -> Iterator 适配器:
// 老代码可能还在用 EnumerationEnumeration<String> e = vector.elements();// 想用 Iterator?没问题,适配一下Iterator<String> iterator = Collections.list(e);
对象适配器 vs 类适配器:
// 对象适配器:组合classAdapter{private Adaptee adaptee;publicvoidrequest(){ adaptee.specificRequest(); }}// 类适配器:继承(Java不支持多继承,局限性大)classAdapterextendsAdapteeimplementsTarget{publicvoidrequest(){ specificRequest(); }}
2.4 组合模式:树形结构了解一下
核心原理:将对象组合成树形结构以表示”部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
使用场景:
-
需要表示对象的部分-整体层次结构 -
希望用户忽略组合对象与单个对象的差异 -
树形菜单、文件系统、组织结构
为什么用组合?单个对象和组合对象在操作上应该是一致的。文件有文件,文件夹有文件夹,但”遍历”这个操作对两者都应该有效。
JDK里的应用:
HashMap:Map里放Map
publicinterfaceMap<K,V> {interfaceEntry<K,V> {K getKey();V getValue(); }}
HashMap 里的 Entry 可以是另一个 HashMap,树形结构轻松实现:
Map<String, Object> user = new HashMap<>();user.put("name", "张三");user.put("age", 25);Map<String, Object> address = new HashMap<>();address.put("city", "北京");address.put("street", "朝阳区");user.put("address", address); // 嵌套结构// 递归遍历publicvoidprintMap(Map<String, Object> map, int indent){for (Map.Entry<String, Object> entry : map.entrySet()) {if (entry.getValue() instanceof Map) { System.out.println(entry.getKey() + ":"); printMap((Map<String, Object>) entry.getValue(), indent + 2); } else { System.out.println(" ".repeat(indent) + entry.getKey() + ": " + entry.getValue()); } }}
javax.swing:UI组件组合
JPanel panel = new JPanel();panel.add(new JButton("按钮"));panel.add(new JLabel("标签"));panel.add(new JPanel()); // 嵌套
JPanel 里可以放 JButton、JLabel,也可以放另一个 JPanel,这就是组合模式。
2.5 外观模式:统一入口
核心原理:为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
使用场景:
-
简化复杂系统的调用 -
为复杂的模块提供统一入口 -
层次化架构中,每层一个外观
为什么用外观?子系统很复杂,调用者不需要知道那么多。外观模式提供统一的入口,简化调用。
JDK里的应用:
ProcessBuilder:进程构建外观
ProcessBuilder pb = new ProcessBuilder("ls", "-la");Process process = pb.start();// 不用知道如何创建进程、如何设置环境变量
Files:文件操作外观
Path path = Paths.get("test.txt");byte[] bytes = Files.readAllBytes(path); // 不用知道底层怎么读List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);
三、行为型模式:对象怎么干活
行为型模式关注的是”对象之间的职责分配和算法设计”。这些模式帮我们设计出灵活的对象交互方式。
3.1 观察者模式:状态变了通知你
核心原理:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
使用场景:
-
事件处理系统 -
消息推送 -
状态同步 -
MVC架构中的Model和View
为什么用观察者?对象A变了,需要通知B、C、D。如果A耦合B、C、D,代码没法维护。观察者模式让A只关心”通知”这件事,具体通知谁,A不关心。
JDK里的应用:
PropertyChangeSupport:属性变更监听
publicclassPropertyChangeSupport{private Vector<PropertyChangeListener> listeners = new Vector<>();publicvoidaddPropertyChangeListener(PropertyChangeListener listener){ listeners.add(listener); }publicvoidremovePropertyChangeListener(PropertyChangeListener listener){ listeners.remove(listener); }publicvoidfirePropertyChange(String propertyName, Object oldValue, Object newValue){for (PropertyChangeListener listener : listeners) { listener.propertyChange(new PropertyChangeEvent(this, propertyName, oldValue, newValue)); } }}
Javabean 的属性变化监听、Spring 的事件机制、GUI 的按钮点击,都是观察者模式:
// 使用观察者模式User user = new User();user.addPropertyChangeListener(evt -> { System.out.println("属性变化: " + evt.getPropertyName() + ", " + evt.getOldValue() + " -> " + evt.getNewValue());});user.setName("张三"); // 触发监听器
Observable:被观察者(JDK自带,已标记@Deprecated但思想永不过时)
publicclassObservable{privateboolean changed = false;private Vector<Observer> observers = new Vector<>();publicsynchronizedvoidaddObserver(Observer o){if (o == null) thrownew NullPointerException();if (!observers.contains(o)) { observers.addElement(o); } }publicvoidnotifyObservers(Object arg){ Object[] arrLocal;synchronized (this) {if (!changed) return; arrLocal = observers.toArray(); clearChanged(); }for (int i = arrLocal.length - 1; i >= 0; i--) { ((Observer) arrLocal[i]).update(this, arg); } }}
JDK 自带的观察者模式,虽然后来被 Stream 替代了,但核心思想永远不过时。
Spring Event:事件驱动
// 事件发布applicationContext.publishEvent(new UserRegisterEvent(user));// 事件监听@ComponentpublicclassUserEventListenerimplementsApplicationListener<UserRegisterEvent> {@OverridepublicvoidonApplicationEvent(UserRegisterEvent event){// 发送邮件、短信、优惠券 }}
3.2 模板方法:骨架我来搭,具体你来实现
核心原理:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
使用场景:
-
多个类有相同的流程,只有部分步骤不同 -
框架扩展 -
代码复用
为什么用模板方法?代码复用!公共逻辑提取到父类,差异逻辑在子类实现。子类只需要关注自己的特殊逻辑。
JDK里的应用:
HttpServlet:HTTP请求处理模板
publicabstractclassHttpServletextendsGenericServlet{protectedvoiddoGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {// 默认实现:405 Method Not Allowed String protocol = req.getProtocol();if ("HTTP/1.1".equals(protocol)) { resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); } else { resp.sendError(HttpServletResponse.SC_BAD_REQUEST); } }protectedvoiddoPost(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {// 默认实现:405 Method Not Allowed }protectedvoidservice(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException { String method = req.getMethod();if (method.equals("GET")) { doGet(req, resp); } elseif (method.equals("POST")) { doPost(req, resp); } elseif (method.equals("PUT")) { doPut(req, resp); } elseif (method.equals("DELETE")) { doDelete(req, resp); }// ... 其他HTTP方法 }}
骨架是 service 搭的,你只需要重写 doGet/doPost,这就是模板方法模式:
@WebServlet("/user")publicclassUserServletextendsHttpServlet{@OverrideprotectedvoiddoGet(HttpServletRequest req, HttpServletResponse resp){// 只关心自己的业务逻辑// 序列化和反序列化、参数校验、异常处理都由父类搞定 }}
AbstractList:列表模板
publicabstractclassAbstractList<E> extendsAbstractCollection<E> implementsList<E> {// add 的默认实现publicbooleanadd(E e){ add(size(), e);returntrue; }// 必须由子类实现abstractpublic E get(int index);// 可选重写publicvoidclear(){ removeRange(0, size()); }}
ArrayList、LinkedList 只需要实现几个抽象方法,其他方法直接继承。
3.3 策略模式:算法任你选
核心原理:定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。策略模式使得算法可独立于使用它的客户而变化。
使用场景:
-
需要在运行时选择算法 -
多种排序方式 -
多种校验规则 -
多种支付方式
为什么用策略?if-else 写多了就是灾难。策略模式把每个算法封装成独立类,可以动态替换。
JDK里的应用:
Comparator:排序策略
// 按年龄排序Comparator<User> byAge = (u1, u2) -> u1.getAge() - u2.getAge();// 按姓名排序Comparator<User> byName = (u1, u2) -> u1.getName().compareTo(u2.getName());// 按工资排序Comparator<User> bySalary = Comparator.comparingInt(User::getSalary);list.sort(byAge); // 用年龄策略排序list.sort(byName); // 用姓名策略排序list.sort(bySalary); // 用工资策略排序
排序算法是固定的,但比较策略可以随便换,这就是策略模式。
ThreadPoolExecutor:拒绝策略
// AbortPolicy - 抛异常// CallerRunsPolicy - 调用者执行// DiscardPolicy - 丢弃// DiscardOldestPolicy - 丢弃最老的ExecutorService pool = new ThreadPoolExecutor(2, 4, 60, TimeUnit.SECONDS,new LinkedBlockingQueue<>(100),new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略);
Spring MVC:HandlerAdapter 策略
// Controller 返回值处理策略HandlerAdapter adapter = new RequestMappingHandlerAdapter();// 支持 @ResponseBody 返回 JSON// 支持 ModelAndView 返回视图
3.4 迭代器模式:遍历与我无关
核心原理:提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露该对象的内部表示。
使用场景:
-
统一遍历不同数据结构 -
不想暴露内部结构 -
需要在遍历时对元素进行操作
为什么用迭代器?ArrayList 用 for 遍历,LinkedList 用迭代器,哪个快?迭代器屏蔽了底层差异,统一遍历方式。
JDK里的应用:
Iterator:遍历神器
publicinterfaceIterator<E> {booleanhasNext();E next();voidremove(); // 可选defaultvoidforEachRemaining(Consumer<? super E> action){while (hasNext()) { action.accept(next()); } }}
Collection 家族都用这个遍历:
List<String> list = Arrays.asList("a", "b", "c");Iterator<String> it = list.iterator();while (it.hasNext()) { String s = it.next(); System.out.println(s);}// for-each 底层就是迭代器for (String s : list) { System.out.println(s);}
你不需要知道 List 底层是数组还是链表,统一接口,爱咋遍历咋遍历。
ListIterator:双向迭代器
ListIterator<String> lit = list.listIterator();while (lit.hasNext()) { System.out.println(lit.next());}while (lit.hasPrevious()) { System.out.println(lit.previous()); // 可以往回遍历}// 可以在遍历中添加/修改元素lit.add("new");lit.set("modified");
Java8 forEach 迭代器
list.forEach(System.out::println);// 底层defaultvoidforEach(Consumer<? super E> action){for (E e : this) { action.accept(e); }}
3.5 责任链模式:一关一关过
核心原理:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。
使用场景:
-
过滤器链 -
审批流程 -
拦截器链 -
异常处理链
为什么用责任链?一个请求需要经过多个处理者,每个处理者处理一部分。责任链让处理者解耦,可以灵活组装。
JDK里的应用:
FilterChain:过滤器链
publicinterfaceFilter{voiddoFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException;}// 执行顺序:Filter1 -> Filter2 -> Filter3 -> Servlet -> Filter3 -> Filter2 -> Filter1
Tomcat Valve:阀门链
// Request请求经过一个个Valve处理StandardPipeline.invoke(request, response);
javax.servlet.Filter 例子:
publicclassLoggingFilterimplementsFilter{@OverridepublicvoiddoFilter(ServletRequest request, ServletResponse response, FilterChain chain){ System.out.println("请求前"); chain.doFilter(request, response); // 传递给下一个Filter System.out.println("响应后"); }}
// 配置Filter链@WebFilter("/*")publicclassAuthFilterimplementsFilter{@OverridepublicvoiddoFilter(ServletRequest request, ServletResponse response, FilterChain chain){ HttpServletRequest req = (HttpServletRequest) request;if (req.getSession().getAttribute("user") != null) { chain.doFilter(request, response); // 有登录信息,放行 } else { response.setStatus(401); // 没登录,拒绝 } }}
3.6 享元模式:共享对象
核心原理:运用共享技术有效地支持大量细粒度对象。
使用场景:
-
大量相似对象 -
对象的大部分状态可变为外部状态 -
需要缓存对象池
为什么用享元?创建对象消耗资源,如果能共享就共享,减少内存占用。
JDK里的应用:
Integer 缓存:享元模式
// -128 到 127 都是共享的Integer a = 100; // 从缓存取Integer b = 100; // 从缓存取a == b // true
String 常量池
String a = "hello"; // 从常量池取String b = "hello"; // 从常量池取a == b // trueString c = new String("hello"); // 新建对象a == c // false
数据库连接池、线程池:
ExecutorService pool = Executors.newFixedThreadPool(10);// 线程是复用的,不是每次都新建
四、实战运用:看完你也得会用
光看不练假把式。知道这些模式在JDK里怎么用,面试怎么说也有牌面了。
场景一:日志收集系统
// 装饰器模式:层层增强功能LogHandler handler = new TimeCostHandler(new ExceptionHandler(new FileHandler("app.log") ));handler.handle("业务逻辑");
场景二:权限控制系统
// 代理模式:方法执行前后加权限校验publicclassPermissionProxyimplementsInvocationHandler{private Object target;@Overridepublic Object invoke(Object proxy, Method method, Object[] args){// 权限校验if (!hasPermission(method)) {thrownew SecurityException("没有权限调用: " + method.getName()); }// 记录日志 log.info("调用方法: " + method.getName());// 执行方法long start = System.currentTimeMillis(); Object result = method.invoke(target, args);long end = System.currentTimeMillis(); log.info("方法执行耗时: " + (end - start) + "ms");return result; }privatebooleanhasPermission(Method method){// 检查用户权限returntrue; }}
场景三:多种排序
// 策略模式:排序算法随便换publicclassSorter<T> {publicvoidsort(List<T> list, Comparator<T> comparator){ list.sort(comparator); }}// 价格从低到高sorter.sort(products, Comparator.comparingInt(Product::getPrice));// 销量从高到低sorter.sort(products, Comparator.comparingInt(Product::getSales).reversed());// 评分从高到低sorter.sort(products, Comparator.comparingDouble(Product::getRating).reversed());
场景四:多层验证
// 责任链模式:多层验证Validator handler1 = new NotNullValidator();Validator handler2 = new LengthValidator();Validator handler3 = new PatternValidator();handler1.setNext(handler2);handler2.setNext(handler3);ValidationResult result = handler1.validate(user);if (!result.isValid()) {// 处理错误}
场景五:事件监听系统
// 观察者模式:用户注册后发送通知UserService userService = new UserService();userService.addListener(new EmailListener());userService.addListener(new SmsListener());userService.addListener(new CouponListener());userService.register("zhangsan", "zhangsan@example.com", "13800138000");// 自动触发:发邮件、发短信、送优惠券
总结:设计模式不是背的,是用的
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
你天天用的 HashMap、ArrayList、InputStream、HttpServlet,里面全是设计模式。看完这篇文章,下次面试官问你 JDK 里用了哪些设计模式,你完全可以给他讲半小时。
参考资料:
-
面试官:JDK中都用了哪些设计模式?– https://developer.aliyun.com/article/1586780 -
JDK设计模式大揭秘:23种模式藏在你每天在用的类里– https://juejin.cn/post/7539120426934435892 -
Java中JDK里用到了哪些设计模式?让面试官眼前一亮!– https://juejin.cn/post/7482753184655589426 -
两万字盘点被玩烂了的9种设计模式– https://www.cnblogs.com/zzyang/p/16895538.html -
设计模式实战教程 – GitHub– https://github.com/liyupi/design-pattern-guide
夜雨聆风