乔人尹 - java
type: channel
link: https://underestimated.cn/tag/java/
items: 《如何写好一个状态机》—— 从入门到放弃系列
items: java系统安全框架
items: Java常用开发工具总结
items: 谈单元测试怎么写(java篇)
items: 那些年被淘汰的技术
items: 关于OOM
items: 关于jdk的版本和如何跟上oracle的节奏
items: apache-commons包总结
items: 面向guava编程（一）

《如何写好一个状态机》—— 从入门到放弃系列
type: item
date: 2023-10-13T03:00:00+00:00
description: 前言以前在做支付系统的入金模块时，遇到了一个经典问题：交易状态流转。你以为状态机就是 if-else 一把梭？年轻人，图样图森破。当你的状态从 3 个变成 10 个，事件从 5 个变成 20 个，你就会发现自己写的代码比《百年孤独》里的家谱还难理清。今天，我们就来聊聊如何优雅地实现一个状态机，让你的代码不至于成为同事口中的"祖传屎山"。什么是状态机？概念定义状态机（State Machine），全称是有限状态机（Finite State Machine，简称 FSM），是一种数学计算模型。它由以下几个核心要素组成：graph LR subgraph 状态机核心要素 S[状态 State] --> E[事件 Event] E --> T[转换 Transition] T --> A[动作 Action] A --> S end要素说明支付场景示例状态（State）系统在某一时刻的状况待验证、处理中、成功、失败事件（Event）触发状态变化的外部输入用户提交、审核通过、支付回调转换（Transition）从一个状态到另一个状态的变化待验证 → 已验证动作（Action）状态转换时执行的操作发送通知、更新余额、记录日志守卫（Guard）转换前的条件检查余额是否充足、账户是否激活用人话说：状态机就是一套规则，定义了"在什么状态下，发生什么事件，会转换到什么状态，并且执行什么动作"。为什么状态机如此重要？(尤其对于支付系统)1. 业务逻辑的"契约化"没有状态机的代码通常长这样：// 💀 地狱级代码示例 public void processPayment(Payment payment, String action) { if (payment.getStatus().equals("PENDING") && action.equals("approve")) { if (payment.getAmount() > 10000) { if (payment.getUserLevel().equals("VIP")) { payment.setStatus("APPROVED"); } else { payment.setStatus("NEED_REVIEW"); } } else { payment.setStatus("APPROVED"); } } else if (payment.getStatus().equals("APPROVED") && action.equals("pay")) { // 又是一堆 if-else... } else if (...) { // 无限嵌套... } }这种代码的问题在于：状态流转规则散落在各处，没有人能说清楚系统到底有多少种状态、多少种流转路径。而有了状态机，业务规则变成了"契约"：状态A + 事件X → 状态B（必须满足条件C）新人接手项目，看一眼状态流转图就能理解业务；产品经理提需求，先画状态图再讨论；出了 bug，对照状态机排查一目了然。2. 防止"非法状态转换"在支付系统中，状态的正确性直接关系到钱。想象一下这些恐怖场景：一笔已经退款成功的交易，又被标记为支付成功（用户白嫖）一笔处理中的交易，直接跳到已完成（跳过了风控检查）一笔已取消的订单，又被重新激活（幽灵订单）状态机通过白名单机制杜绝这些问题：只有明确定义的流转路径才是合法的，其他一律拒绝。// 状态机会直接拒绝非法操作 stateMachine.fire(DepositEvent.DEPOSIT_SUCCESS); // IllegalStateException: 状态[REFUND_SUCCESS]不支持事件[DEPOSIT_SUCCESS]3. 天然的审计追踪能力金融系统对审计的要求非常高。有了状态机，每一次状态变更都是一个明确的"事件"：2024-01-15 10:23:45 | TXN_001 | WAIT_VERIFY → VERIFIED | EVENT: VERIFY_PASS | OPERATOR: SYSTEM 2024-01-15 10:23:46 | TXN_001 | VERIFIED → PENDING | EVENT: DEPOSIT_PENDING | OPERATOR: SYSTEM 2024-01-15 10:25:12 | TXN_001 | PENDING → REFUND_PROCESSING | EVENT: DEPOSIT_FAILED | OPERATOR: SYSTEM 2024-01-15 14:30:00 | TXN_001 | REFUND_PROCESSING → REFUND_SUCCESS | EVENT: REFUND_SUCCESS | OPERATOR:…

java系统安全框架
type: item
date: 2023-01-01T08:27:00+00:00
description: 前言：为什么你需要读这篇文章还记得那个深夜吗？你的系统被人用 admin/123456 登录了，然后删库跑路。老板问你："我们的安全措施呢？"你支支吾吾地说："我以为用户会自觉的..."朋友，醒醒吧。本文将带你深入了解Java世界的三大安全框架：Spring Security（官方嫡长子）、Apache Shiro（轻量级老将）、Sa-Token（国产新秀）。看完之后，你至少能在老板面前假装很懂安全。一、安全框架核心概念在开始之前，先搞清楚几个灵魂拷问：1.1 认证 vs 授权认证（Authentication）：你是谁？—— 验明正身 授权（Authorization）：你能干啥？—— 权限控制打个比方：认证：门卫检查你的工牌，确认你是公司员工授权：你虽然是员工，但你不能进财务室拿钱1.2 安全框架核心架构flowchart TB subgraph 客户端 A[用户请求] end subgraph 安全框架 B[认证过滤器链] C[身份认证模块] D[授权决策模块] E[会话管理] F[密码加密] end subgraph 业务系统 G[Controller] H[Service] I[数据库] end A --> B B --> C C -->|认证成功| D C -->|认证失败| J[返回401] D -->|授权通过| G D -->|授权失败| K[返回403] G --> H --> I C E C F1.3 主流安全框架对比特性Spring SecurityApache ShiroSa-Token学习曲线🔥🔥🔥🔥🔥 陡峭如珠峰🔥🔥🔥 相对平缓🔥🔥 新手友好功能完整度⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐Spring集成原生支持需要适配良好支持社区活跃度非常活跃一般国内活跃OAuth2支持原生支持需要扩展内置支持文档质量英文为主,较全一般中文友好适用场景企业级复杂系统中小型项目快速开发二、Spring Security：官方嫡长子2.1 简介Spring Security 是 Spring 家族的亲儿子，功能强大到令人发指，配置复杂到令人放弃。但没办法，谁让人家是正统呢？"Spring Security 的学习曲线不是曲线，是悬崖。" —— 某不愿透露姓名的秃头程序员2.2 核心架构flowchart TB subgraph FilterChain[安全过滤器链] F1[SecurityContextPersistenceFilter安全上下文持久化] F2[UsernamePasswordAuthenticationFilter用户名密码认证] F3[BasicAuthenticationFilterBasic认证] F4[ExceptionTranslationFilter异常转换] F5[FilterSecurityInterceptor授权拦截器] end subgraph Core[核心组件] A[AuthenticationManager认证管理器] P[AuthenticationProvider认证提供者] U[UserDetailsService用户详情服务] E[PasswordEncoder密码编码器] end subgraph Authorization[授权组件] AD[AccessDecisionManager访问决策管理器] V1[AffirmativeBased一票通过] V2[ConsensusBased少数服从多数] V3[UnanimousBased一票否决] end F1 --> F2 --> F3 --> F4 --> F5 F2 --> A A --> P P --> U P --> E F5 --> AD AD --> V1 AD --> V2 AD --> V32.3 快速入门2.3.1 添加依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>恭喜你！加完这一行，你的所有接口都需要登录了。是不是很惊喜？😏2.3.2 基础配置（Spring Boot 3.x 风格）@Configuration @EnableWebSecurity @EnableMethodSecurity // 开启方法级别安全 public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http // 关闭CSRF（前后端分离项目通常关闭） .csr…

Java常用开发工具总结
type: item
date: 2022-11-06T08:09:00+00:00
description: 🛠️ "工欲善其事，必先利其器。然后你会发现，光有器不行，还得会用。"前言：为什么工具很重要还记得刚学Java的时候吗？用记事本写代码，手动 javac 编译，然后 java 运行。那种纯粹的快乐，大概持续了...三天。然后你发现：写错一个分号，编译报错一百行想重构一个方法名，要改二十个文件调试全靠 System.out.println打包发布全靠玄学于是你开始寻找工具，然后发现工具比代码还多...本文将系统性地介绍Java开发中的各类工具，帮你从"工具困难症"中解脱出来。一、工具全景图mindmap root((Java开发工具)) IDE集成开发环境 IntelliJ IDEA Eclipse VS Code 构建工具 Maven Gradle Ant 版本控制 Git SVN 代码质量 SonarQube Checkstyle SpotBugs PMD 调试诊断 Arthas JProfiler VisualVM JConsole API测试 Postman Apifox Bruno 数据库工具 DBeaver Navicat DataGrip 容器部署 Docker Kubernetes 文档工具 Swagger Javadoc Markdown 其他工具 Lombok MapStruct JRebel二、IDE：你的第二个家2.1 IntelliJ IDEA —— 宇宙第一IDE"用过IDEA的人，都不想再用别的了。" —— 真实的用户反馈核心优势特性说明智能补全不只是补全，是读心术重构能力改名、提取、移动，一键搞定调试器强大到可以回退执行Git集成图形化操作，告别命令行恐惧插件生态想要啥都有必装插件清单📦 效率提升类 ├── Lombok # 注解生成getter/setter ├── MyBatisX # MyBatis增强，XML与Mapper跳转 ├── GenerateAllSetter # 一键生成所有setter调用 ├── GsonFormatPlus # JSON转Java实体类 ├── String Manipulation # 字符串格式转换神器 └── Key Promoter X # 快捷键提示，帮你戒掉鼠标 📦 代码质量类 ├── SonarLint # 代码质量实时检查 ├── Alibaba Java Coding Guidelines # 阿里巴巴编码规范 ├── CheckStyle-IDEA # 代码风格检查 └── SpotBugs # Bug静态分析 📦 工具增强类 ├── Maven Helper # Maven依赖分析/冲突解决 ├── JRebel # 热部署神器（付费） ├── RestfulTool # RESTful接口快速测试 ├── Translation # 翻译插件，英文文档救星 └── Rainbow Brackets # 彩虹括号，嵌套不迷路 📦 美化类 ├── One Dark Theme # 暗色主题 ├── Atom Material Icons # 文件图标美化 └── Nyan Progress Bar # 彩虹猫进度条（摸鱼必备）常用快捷键速查🔥 高频快捷键（macOS / Windows） 【导航】 ⌘O / Ctrl+N → 查找类 ⌘⇧O / Ctrl+Shift+N → 查找文件 ⌘⌥O / Ctrl+Shift+Alt+N → 查找符号（方法/变量） ⌘E / Ctrl+E → 最近打开的文件 ⌘B / Ctrl+B → 跳转到定义 ⌘⌥B / Ctrl+Alt+B → 跳转到实现 ⌘U / Ctrl+U → 跳转到父类/接口 ⌘[ / Ctrl+Alt+← → 返回上一个位置 ⌘] / Ctrl+Alt+→ → 前进到下一个位置 【编辑】 ⌘D / Ctrl+D → 复制当前行 ⌘⌫ / Ctrl+Y → 删除当前行 ⌥⇧↑ / Alt+Shift+↑ → 上移当前行 ⌘/ / Ctrl+/ → 单行注释 ⌘⌥/ / Ctrl+Shift+/ → 多行注释 ⌘⌥L / Ctrl+Alt+L → 格式化代码 ⌘⌥O / Ctrl+Alt+O → 优化导入 【重构】 ⇧F6 / Shift+F6 → 重命名 ⌘⌥M / Ctrl+Alt+M → 提取方法 ⌘⌥V / Ctrl+Alt+V → 提取变量 ⌘⌥F / Ctrl+Alt+F → 提取字段 ⌘⌥C / Ctrl+Alt+C → 提取常量 【调试】 F8 → 单步跳过 F7 → 单步进入 ⇧F8 / Shift+F8 → 单步跳出 F9 → 继续执行 ⌘F8 / Ctrl+F8 → 切换断点 【生成】 ⌘N / Alt+Insert → 生成代码（构造器/getter/set…

谈单元测试怎么写(java篇)
type: item
date: 2022-10-16T06:03:00+00:00
description: 本文属于《从入门到放弃》系列，但这次我要劝你别放弃，因为不写单元测试的代码，就像没系安全带的汽车——平时感觉挺自由，出事了直接起飞。前言：一个没写单元测试的程序员的一天早上9点，产品经理说："这个需求很简单，就改一行代码。"你信了，改了。下午3点，线上告警疯狂轰炸你的钉钉。晚上11点，你还在公司排查为什么改了A模块，B模块炸了，C模块也顺便炸了。你开始怀疑人生："我明明只改了一行啊？！"这，就是没有单元测试的代价。如果你写了单元测试，这一行代码改完，跑一下测试，3秒钟就知道有没有搞砸。而不是等到线上用户帮你测试。用户测试的成本，那可是真金白银的赔偿啊兄弟！一、单元测试是什么？不就是写代码测代码吗？没错，单元测试就是用代码测试代码。但很多人对单元测试有误解，以为就是启动整个Spring容器，然后调一下接口看看返回值对不对。那叫集成测试，不叫单元测试。来看一张图，理解一下测试金字塔：graph TB subgraph 测试金字塔 E2E["🔺 E2E测试数量最少 | 成本最高 | 速度最慢"] INT["🔶 集成测试数量适中 | 成本中等 | 速度中等"] UNIT["🟢 单元测试数量最多 | 成本最低 | 速度最快"] end E2E --> INT INT --> UNIT style E2E fill:#ff6b6b,stroke:#333,stroke-width:2px,color:#fff style INT fill:#feca57,stroke:#333,stroke-width:2px,color:#333 style UNIT fill:#1dd1a1,stroke:#333,stroke-width:2px,color:#fff单元测试的特点：特性说明快毫秒级执行，跑几百个测试也就几秒钟独立不依赖数据库、Redis、MQ等外部服务可重复跑一万次结果都一样自动化CI/CD流水线自动跑单元测试测的是"单元"，在Java里，通常指的是一个方法，最多是一个类。二、Spring Boot测试全家桶Spring Boot贴心地给我们准备了一套测试工具，加个依赖就能用：<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>这一个依赖，包含了以下豪华套餐：graph LR subgraph spring-boot-starter-test JUnit5["JUnit 5测试框架本体"] Mockito["MockitoMock神器"] AssertJ["AssertJ断言增强"] Hamcrest["Hamcrest匹配器"] JSONPath["JSONPathJSON断言"] Spring["Spring TestSpring测试支持"] end style JUnit5 fill:#6c5ce7,color:#fff style Mockito fill:#00b894,color:#fff style AssertJ fill:#0984e3,color:#fff style Hamcrest fill:#fdcb6e,color:#333 style JSONPath fill:#e17055,color:#fff style Spring fill:#00cec9,color:#fff2.1 JUnit 5 基础写法先来个最简单的例子热热身：import org.junit.jupiter.api.Test; import org.junit.jupiter.api.DisplayName; import static org.junit.jupiter.api.Assertions.*; class CalculatorTest { @Test @DisplayName("1 + 1 应该等于 2，如果不等于，那数学就完蛋了") void testAdd() { Calculator calculator = new Calculator(); int result = calculator.add(1, 1); assertEquals(2, result); } @Test @DisplayName("除以0应该抛异常，不然宇宙会爆炸") void testDivideByZero() { Calculator calculator = new Calculator(); assertThrows(ArithmeticException.class, () -> { calculato…

那些年被淘汰的技术
type: item
date: 2022-08-14T01:41:00+00:00
description: 前言：技术的"生死轮回""在这个行业里，唯一不变的就是变化本身。" —— 某位被迫学了三遍前端框架的后端程序员如果你是一个有着十年以上工作经验的 Java 程序员，那么恭喜你，你已经见证了无数技术的兴衰更替。那些曾经让你熬夜加班、掉光头发的技术，如今可能已经静静躺在技术博物馆的角落里，等待着后人前来"考古"。今天，让我们一起翻开历史的篇章，缅怀那些曾经辉煌一时、如今却逐渐淡出舞台的 Java 技术。别担心，这不是一篇让你伤感的文章——毕竟，送走它们之后，我们又迎来了更多让我们加班的新技术。第一章：被淘汰的主要原因在正式开始"悼念"之前，让我们先来分析一下，为什么这些技术会被淘汰：1. 技术发展的自然规律就像诺基亚被智能手机取代一样，技术的发展是不可阻挡的洪流。新技术往往能够提供更高的开发效率、更好的性能表现和更优雅的解决方案。当 Spring Boot 出现时，那些还在手动配置 XML 的程序员们，眼泪都流下来了——不是因为不舍，而是因为太香了。2. 安全性和可靠性的硬伤某些技术在设计之初就存在安全隐患，随着互联网的发展和攻击手段的升级，这些隐患变得越来越致命。当一个技术三天两头爆出高危漏洞时，再深厚的感情也扛不住运维的怒火。3. 复杂性——程序员的头号敌人"简单就是美，复杂就是加班。"有些技术设计得过于复杂，学习曲线陡峭得像珠穆朗玛峰，配置文件多得像是在写一部长篇小说。当程序员需要花费 80% 的时间在配置和调试上，只有 20% 的时间在实际业务开发上时，这项技术就离被淘汰不远了。4. 社区和生态的萎缩技术的生命力很大程度上取决于其社区的活跃度。当 Stack Overflow 上的相关问题越来越少，GitHub 上的 star 数开始停滞，那些曾经的技术明星就开始走向黄昏了。第二章：那些年，我们一起追过的"前任"1. Servlet/JSP：Web 开发的"老祖宗"生卒年代：1997年至今（名义上还活着，实际上已经退居二线）辉煌时刻：// 曾经，我们是这样写 Web 应用的 public class HelloServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println(" "); out.println(" Hello, World! "); out.println(" "); } }淘汰原因：在 Java 代码里写 HTML？在 HTML 里嵌 Java 代码？这操作简直是代码界的"油水混合"JSP 的 <% %> 标签让代码变得像一锅乱炖没有前后端分离的概念，改个页面样式都要重启服务器接班人：Spring MVC + RESTful API + 前端框架（Vue/React）墓志铭："我不是被打败的，我只是太老了。"2. Struts：曾经的"一哥"生卒年代：2000年 - 2017年（Struts 2 在这一年爆出了致命漏洞）辉煌时刻：在 21 世纪的头十年，如果你不会 Struts，你都不好意思说自己是 Java Web 开发者。面试官问的第一个问题就是："讲一下 Struts 的工作流程"。 <struts-config> <form-beans> <form-bean name="loginForm" type="com.example.LoginForm"/> </form-beans> <action-mappings> <action path="/login" type="com.example.LoginAction" name="loginForm" scope="request" validate="true" input="/login.jsp"> <forward name="success" path="/welcome.jsp"/> <forward name="failure" path="/login.jsp"/> </action> </action-mappings> </struts-config>淘汰原因：配置文件多得能绕地球一圈Struts 2 的安全漏洞层出不穷，2017年的 S2-045 漏洞直接让无数企业的服务器"裸奔"架构设计过于复杂，ActionForm、Action、ActionForward... 概念多到让人头秃接班人：Spring MVC（以优雅的注…

关于OOM
type: item
date: 2022-08-07T13:34:00+00:00
description: java相比c++最大的进步就是在于增加了gc不用开发者手动分配和回收内存，在虚拟机自动内存管理机制的帮助下，不再需要为每一个new操作去写配对的delete/free代码，不容易出现内存泄漏和内存溢出问题，在这个过程中控制权转移给了虚拟机VM自动完成，但是这仍然无法避免内存泄露和溢出的问题，而出了OOM如何排查解决这类问题呢？本文带您一起来探寻jvm如何自动分配回收内存、如何避开内存泄露问题和解决OOM的思路。java虚拟机jvm内存模型垃圾收集器PGC、 C4收集器gc有哪些类型一个对象在何时会被回收jvm的相关工具什么时候会出现OOM出现OOM如何排查问题参考资料《深入理解java虚拟机(第三版)》

关于jdk的版本和如何跟上oracle的节奏
type: item
date: 2022-07-10T05:15:00+00:00
description: 不查不知道，一查吓一跳，jdk最新的版本已经来到了jdk17，而在2017年9月，jdk发布的版本是java9，这几年oracle玩命的每年3月、9月一年发布2个版本，导致很多开发者和公司都压力很大，今年9月份又要发布jdk18了，现在用java8的公司还比比皆是。JDK 9发布后，Oracle随即宣布Java将会以持续交付的形式和更加敏捷的研发节奏向前推进，以后JDK将会在每年的3月和9月各发布一个大版本[11]，目的就是为避免众多功能特性被集中捆绑到一个 JDK版本上而引发交付风险。这次改革确实从根源上解决了跳票问题，但也为Java的用户和发行商带 来了颇大的压力，不仅程序员感慨“Java新版本还没开始用就已经过时了”，Oracle自己对着一堆JDK版 本分支也在挠头，不知道该如何维护更新，该如何提供技术支持。Oracle的解决方案是顺理成章地终 结掉“每个JDK版本最少维护三年”的优良传统，从此以后，每六个JDK大版本中才会被划出一个长期 支持(Long Term Support，LTS)版，只有LTS版的JDK能够获得为期三年的支持和更新，普通版的 JDK就只有短短六个月的生命周期。JDK 8和JDK 11会是LTS版，再下一个就到2021年发布的JDK 17 了。现有的LTS版本和支持的维护时间版本 LTS时间jdk8 2030年12月JDK11 2026年9月JDK17 2024年9月

apache-commons包总结
type: item
date: 2022-05-12T15:09:00+00:00
description: 前言：为什么需要Apache Commons？如果你写Java时经常遇到这些场景：判断字符串是否为空，写了一堆 str != null && !str.isEmpty()复制对象属性，写了几十行getter/setter读取文件内容，try-catch-finally嵌套三层集合操作，各种for循环写到手软那么恭喜你，Apache Commons就是来拯救你的！一、Apache Commons 全家福Apache Commons是Apache软件基金会的一个项目，提供了大量可复用的Java组件。它就像一个工具箱，里面装满了各种实用工具。flowchart TB subgraph ApacheCommons[Apache Commons 全家福] subgraph 核心工具[核心工具类] Lang[Commons Lang字符串/对象/反射] Collections[Commons Collections集合增强] IO[Commons IOIO操作] end subgraph 数据处理[数据处理] BeanUtils[Commons BeanUtilsBean操作] Codec[Commons Codec编解码] CSV[Commons CSVCSV处理] Compress[Commons Compress压缩解压] end subgraph 网络相关[网络相关] HttpClient[HttpClientHTTP客户端] Net[Commons Net网络协议] Email[Commons Email邮件发送] end subgraph 其他工具[其他工具] Pool[Commons Pool对象池] DBCP[Commons DBCP数据库连接池] CLI[Commons CLI命令行解析] Config[Commons Configuration配置管理] end end1.1 版本选择指南组件最新稳定版JDK要求Maven坐标commons-lang33.14.0JDK 8+org.apache.commons:commons-lang3commons-collections44.4JDK 8+org.apache.commons:commons-collections4commons-io2.15.1JDK 8+commons-io:commons-iocommons-codec1.16.0JDK 8+commons-codec:commons-codeccommons-beanutils1.9.4JDK 8+commons-beanutils:commons-beanutilshttpclient55.3JDK 8+org.apache.httpcomponents.client5:httpclient5注意：commons-lang 和 commons-lang3 是两个不同的包！commons-lang3 是新版本，包名以 org.apache.commons.lang3 开头。二、Commons Lang3：字符串和对象操作神器这是使用频率最高的Commons组件，没有之一。2.1 Maven依赖<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.14.0</version> </dependency>2.2 StringUtils：告别空指针flowchart LR subgraph StringUtils常用方法 A[判空系列] --> A1[isEmpty] A --> A2[isBlank] A --> A3[isNotEmpty] A --> A4[isNotBlank] B[处理系列] --> B1[trim] B --> B2[strip] B --> B3[truncate] B --> B4[abbreviate] C[判断系列] --> C1[equals] C --> C2[contains] C --> C3[startsWith] C --> C4[endsWith] D[转换系列] --> D1[join] D --> D2[split] D --> D3[replace] D --> D4[reverse] end判空方法对比（划重点！）import org.apache.commons.lang3.StringUtils; // isEmpty vs isBlank 的区别 StringUtils.isEmpty(null); // true StringUtils.isEmpty(""); // true StringUtils…

面向guava编程（一）
type: item
date: 2022-05-02T14:19:00+00:00
description: guava是google开发的一个基础java类库，包括主要的模块为utilities/Functional/Collections/concurrency/cache/eventbus/work files，guava提供了很多好用的api，使用guava编程可以有效提高开发效率和代码质量。Joiner(list/map转化为string) private static final String COMMA = ","; private static final String EMPTY = ""; private static final String EQUALS = "="; public static String oldJoiner(List<String> dataList) { // 老的方法实现 if (dataList == null) { return null; } StringBuilder accum = new StringBuilder(); for (String item : dataList) { accum.append(item == null ? EMPTY : item).append(COMMA); } String joinResult = accum.toString(); return joinResult.substring(0, joinResult.length() - 1); } public static String oldJoiner(Map<String,String> dataMap){ if (dataMap == null){ return null; } // 老的方式，转换代码和判断比较多 StringBuilder accum = new StringBuilder(); for (Map.Entry<String,String> item : dataMap.entrySet()) { accum.append(item.getKey() == null ? EMPTY : item.getKey()).append(EQUALS).append(item.getValue()).append(COMMA); } String joinResult = accum.toString(); return joinResult.substring(0, joinResult.length() - 1); } public static String joiner(List<String> dataList) { if (dataList == null) { return null; } // guava的实现，该方法使用类似于python中的",".join(list) return Joiner.on(COMMA).useForNull(EMPTY).join(dataList); } public static String appendTo(List<String> dataList){ return Joiner.on(COMMA).useForNull(EMPTY).appendTo(new StringBuilder(), dataList).toString(); } public static String joiner(Map<String,String> dataMap){ if (dataMap == null){ return null; } // guava joiner将map值转化为string return Joiner.on(COMMA).useForNull(EMPTY).withKeyValueSeparator(EQUALS).join(dataMap); } public static void main(String[] args) { // 要求:将list转化为使用逗号分隔的字符串 List<String> dataList = Arrays.asList("1", "2", "3", null); System.out.println(appendTo(dataList)); // 测试代码 TestUtil.assertIsTrue(TestUtil.equals(joiner(dataList), oldJoiner(dataList)), "值不等"); dataList = null; TestUtil.assertIsTrue(TestUtil.equals(joiner(dataList), oldJoiner(dataList)), "值不等"); Map<String,String…