Java开发经验——阿里巴巴编码规范经验总结2

摘要

这篇文章是关于Java开发中阿里巴巴编码规范的经验总结。它强调了避免使用Apache BeanUtils进行属性复制,因为它效率低下且类型转换不安全。推荐使用Spring BeanUtils、Hutool BeanUtil、MapStruct或手动赋值等替代方案。文章还指出不应在视图模板中加入复杂逻辑运算,应明确MVC架构各层的职责。此外,还涉及数据结构初始化应指定大小、正则表达式的预编译、避免通过catch处理某些RuntimeException异常、finally块中资源关闭的正确方式以及防止NPE的多种方法。

1. 【强制】避免用 ApacheBeanutils 进行属性的 copy。

不推荐使用 Apache Commons BeanUtils 工具来进行对象属性复制(如 BeanUtils.copyProperties),因为它效率低、性能差、类型转换不安全,在生产环境中容易成为性能瓶颈。

Apache BeanUtils 是通过反射 + 内省(Introspector)+ 字符串转换来做属性 copy,性能非常低,不适合在高并发或大量对象转换场景中使用。

1.1. 属性赋值推荐方案

方案

优势

场景

Spring BeanUtils

性能略优于 Apache,但仍是反射

适合小量级对象拷贝

Hutool BeanUtil

性能高,支持深拷贝、自定义字段映射

推荐在工具类中统一封装

MapStruct

编译期生成拷贝代码(无反射,极快)

推荐在 DDD 中的 DO <-> DTO 映射

手动赋值

最安全、最清晰

小对象或关键转换逻辑

ModelMapper / Dozer(不推荐)

仍是反射,配置复杂,性能低

不推荐使用

2. 【强制】不要在视图模板中加入任何复杂的逻辑运算。

在 MVC 架构的具体实现中,比如 Spring Boot 项目中,我们常见的结构包括:

  • Controller(控制器)
  • Service(服务/业务逻辑层)
  • DAO(数据访问层,也叫 Mapper、Repository)

下面是这三层的职责和理解方式,结合“不要在视图中写复杂逻辑”的那条建议,进一步深化层次的划分:

2.1. Controller:控制层

职责:

  • 接收 HTTP 请求参数;
  • 调用 Service 进行处理;
  • 封装和返回响应数据(Response);
  • 做参数校验、权限判断、日志记录等外围操作。

不要做的事:

  • 不要写业务逻辑;
  • 不要操作数据库;
  • 不要做复杂的流程判断或数据处理。

示例:

@PostMapping("/user/upgrade")
public Response<Void> upgradeUser(@RequestBody UserUpgradeRequest request) {userService.upgradeUserToVip(request.getUserId());return Response.success();
}

2.2. Service:业务逻辑层

职责:

  • 实现具体业务逻辑,如“升级用户为 VIP”、“扣减库存”、“发送通知”等;
  • 调用多个 DAO、封装业务判断流程;
  • 做事务控制(@Transactional);
  • 组装处理结果返回 Controller。

不要做的事:

  • 不要和 Web 框架(如 Servlet、HttpRequest)耦合;
  • 不要拼 SQL,不直接操作数据库。

示例:

public void upgradeUserToVip(Long userId) {UserDO user = userDao.findById(userId);if (user == null || user.isVip()) {throw new BizException("用户不存在或已是VIP");}user.setVip(true);userDao.update(user);notifyService.sendVipNotification(user);
}

2.3. DAO(Mapper/Repository):数据访问层

职责:

  • 直接与数据库交互;
  • 封装 SQL 查询(或通过 MyBatis/JPA 映射);
  • 只做增删改查操作;
  • 返回实体对象,不做业务判断。

不要做的事:

  • 不要处理业务逻辑;
  • 不要做流程判断;
  • 不要拼接复杂结果(如组装响应对象)。

示例:

@Mapper
public interface UserDao {UserDO findById(Long userId);int update(UserDO user);
}

2.4. 总结类比:工厂分工

层级

类比

职责

Controller

前台接待

接收客户请求,转交到内部处理

Service

经理

安排工人干活,处理流程,判断异常

DAO

工人

操作数据库,搬原材料,不决策

2.5. MVC 和模板逻辑的对应关系

  • 视图(View) = 页面模板、前端:只能展示数据,不参与 Controller、Service、DAO 的职责
  • 所以复杂判断、业务数据准备都应该在 Service/Controller 中完成,模板中直接展示结果即可

3. 【强制】任何数据结构的构造或初始化,都应指定大小,避免数据结构无限增长吃光内存。

3.1. 这条建议的核心思想是

在使用如集合(List、Map、Set 等)这类可扩展数据结构时,应尽可能“预估其大小”并“显式设置初始容量”,从而避免它们在运行中频繁扩容、内存抖动,甚至 OOM(内存溢出)的问题。提前预估数据量,合理初始化集合容量,是性能优化与内存安全的重要实践。

ArrayList
HashMap
HashSet
ConcurrentHashMap
StringBuilder
自定义缓存、队列等

这些类的背后都依赖一个数组或哈希桶来存储数据,如果你没有指定容量,它们会用默认大小初始化,然后在插入过程中自动扩容(重新开数组、拷贝数据等)。

3.2. 为什么要指定大小?

3.2.1. 不指定容量的风险:

  • 频繁扩容: 每次容量不够都要重新分配数组,拷贝旧数据 ➜ 性能开销大;
  • 内存浪费: 扩容步长不是线性的,可能会分配远超实际需要的空间;
  • 内存溢出(OOM): 在循环里构造数据结构没有设置上限 ➜ 无限增长,吃光堆内存。

3.2.2. 指定容量的好处:

  • 减少扩容次数: 提高性能;
  • 控制内存: 限制最大容量,防止意外 OOM;
  • 体现程序边界意识: 编码更健壮。

3.3. 数据结构设置初始值示例对比

3.3.1. 不指定大小(有性能隐患):

List<String> list = new ArrayList<>();
for (int i = 0; i < 100000; i++) {list.add("item" + i);
}

默认容量是 10,之后 1.5 倍扩容 ➜ 至少扩容 10+ 次,代价很高

3.3.2. 指定大小(性能友好):

List<String> list = new ArrayList<>(100000);
for (int i = 0; i < 100000; i++) {list.add("item" + i);
}

只创建一次内部数组,避免扩容

3.4. 延伸到场景

数据结构

默认容量

推荐用法

ArrayList

10

new ArrayList<>(预计数量)

HashMap

16

new HashMap<>(预计数量 / 负载因子 + 1)

StringBuilder

16

new StringBuilder(预计字符串长度)

ConcurrentHashMap

16

new ConcurrentHashMap<>(预计大小)

4. 【强制】在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度。

说明:不要在方法体内定义:Pattern pattern = Pattern.compile("规则");

正则表达式在使用时,如果每次都重新编译,会严重影响性能。应该使用预编译(Pattern.compile(...))方式,将正则表达式提前编译好并重复使用。

在 Java 中使用正则时,一般有两种方式:

4.1. 每次都编译(效率低)

boolean isMatch = "abc123".matches("\\w+");

内部其实相当于:

Pattern.compile("\\w+").matcher("abc123").matches();

这会每次调用都重新编译正则表达式,开销很大,尤其在循环或高并发下。

4.2. 预编译后复用(推荐)

private static final Pattern PATTERN = Pattern.compile("\\w+");boolean isMatch = PATTERN.matcher("abc123").matches();

正则表达式只在类加载时编译一次,后续调用直接复用,提高性能。

4.3. 使用场景

场景

是否推荐预编译

单次用、不频繁

可以临时用 .matches()

多次校验、循环中用

必须预编译

高并发服务接口中

必须预编译

工具类/公共方法

强烈建议预编译并静态缓存

4.4. 总结

  • 编译正则是耗时操作
  • 多次使用时,一定要用 Pattern.compile(...) 并缓存起来;
  • 正则预编译 = 性能优化 + 好习惯。

5. 【强制】Java 类库中定义的可以通过预检查方式规避的 RuntimeException 异常不应该通过 catch 的方式来处理,比如:NullPointerException,IndexOutOfBoundsException 等等。

try-catch 是用来处理不可预知的异常情况,不是用来“代替 if 判断”的。对于 Java 类库中常见的 RuntimeException(运行时异常),如果我们可以在代码运行前通过逻辑“预检查”避免它的发生,就不应该依赖 try-catch 来处理它。

5.1. 举几个典型例子

5.1.1. 不推荐的做法(用 catch 捕获 NPE):

try {System.out.println(user.getName());
} catch (NullPointerException e) {// 捕获空指针异常System.out.println("user 为空");
}

5.1.2. 推荐的做法(用 if 判断提前规避):

if (user != null) {System.out.println(user.getName());
} else {System.out.println("user 为空");
}

5.2. 为什么不推荐用 catch 处理这些异常?

  1. 这类异常不是业务异常,而是代码逻辑错误:出现 NullPointer、数组越界等,说明你的代码逻辑写得有问题,不是正常的“可恢复”情况。
  2. catch 成本高,影响性能:try-catch 的异常捕获机制在 JVM 中性能是开销较大的(尤其是频繁抛异常的情况)。
  3. 可读性变差,调试困难:滥用 catch 会把真正的问题掩盖,调试困难,也不利于代码维护。

5.3. 适用的异常类型(不建议 catch)

异常类

说明

NullPointerException

空指针异常,应通过非空判断避免

IndexOutOfBoundsException

下标越界,应判断下标是否合法

ClassCastException

类型转换错误,应先 instanceof判断

IllegalArgumentException

参数非法,应通过参数校验处理

5.4. 异常捕获正确的原则

  • 能通过逻辑避免的异常,不要 try-catch
  • RuntimeException 更多是一种编码警告,不是业务流程的一部分
  • 只在顶层兜底或做日志监控时统一捕获这些异常

6. 【强制】finally 块必须对资源对象、 流对象进行关闭,有异常也要做 try-catch。

无论是否发生异常,finally 块中一定要确保资源被正确关闭,且关闭操作本身也要加 try-catch,避免二次异常导致资源未释放。

6.1. 正确的使用方式示例:

自 Java 7 起,Java 提供了 try-with-resources 语法,它能够自动关闭实现了 AutoCloseableCloseable 接口的资源(如 InputStream)。使用该语法,可以消除手动管理资源关闭的复杂性,并自动处理 close() 方法可能抛出的异常。

try (InputStream in = new FileInputStream("data.txt")) {// 读文件逻辑
} catch (IOException e) {e.printStackTrace(); // 异常处理
}

6.2. 错误的示例(不捕获关闭异常):

finally {in.close(); // 如果这里抛出 IOException,整个异常流程会被覆盖
}

6.3. 适用范围:

这条规范适用于所有需要关闭或释放的资源类,例如:

  • IO 流(InputStream、OutputStream、Reader、Writer 等)
  • 数据库连接(Connection、Statement、ResultSet)
  • 网络资源(Socket、HttpURLConnection)
  • 文件句柄
  • 线程池(ExecutorService 的 shutdown
  • 锁(Lock.unlock()

7. 【推荐】防止 NPE,是程序员的基本修养,注意 NPE 产生的场景

1)返回类型为基本数据类型,return 包装数据类型的对象时,自动拆箱有可能产生 NPE,反例:public int method() { return Integer 对象; },如果为 null,自动解箱抛 NPE。
2)数据库的查询结果可能为 null。
3)集合里的元素即使 isNotEmpty,取出的数据元素也可能为 null。
4)远程调用返回对象时,一律要求进行空指针判断,防止 NPE。
5)对于 Session 中获取的数据,建议进行 NPE 检查,避免空指针。
6)级联调用 obj.getA().getB().getC();一连串调用,易产生 NPE。正例: 使用 JDK8 的 Optional 类来防止 NPE 问题。

7.1. 常见的 NPE 产生场景

7.1.1. 访问未初始化的对象

当你尝试访问一个未初始化的对象(即其值为 null)时,通常会抛出 NPE。

String str = null;
int length = str.length();  // NPE: str 是 null,无法调用 length()

7.1.2. 调用 null 对象的实例方法

如果对象为 null,直接调用其方法会导致空指针异常。

MyClass obj = null;
obj.someMethod();  // NPE: obj 是 null,无法调用 someMethod()

7.1.3. 尝试访问 null 数组元素

null 数组尝试访问元素时也会抛出 NPE。

String[] arr = null;
String element = arr[0];  // NPE: arr 是 null,无法访问元素

7.1.4. 传递 null 给不接受 null 的方法

有些方法要求传入非 null 的参数,如果传入 null,可能会触发 NPE。

public void printLength(String str) {
System.out.println(str.length());  // 如果 str 为 null,将引发 NPE
}

7.1.5. 链式调用中的空指针

在链式调用中,如果某一环节返回了 null,而后续还对其进行方法调用,就会导致 NPE。

Person person = getPerson();
int age = person.getAddress().getCity().getZipCode();  // 如果 person 或 address 为 null,则会 NPE

7.2. 如何防止NPE问题?

7.2.1. 避免使用 null

尽量避免使用 null,特别是在可能触发 NPE 的地方。可以使用 Optional 来表示可能为空的值。

Optional<String> optionalStr = Optional.ofNullable(str);
optionalStr.ifPresent(s -> System.out.println(s.length()));  // 安全访问

7.2.2. 空值检查

在调用对象的方法之前,先检查对象是否为 null

if (str != null) {System.out.println(str.length());  // 只有 str 非 null 时才调用方法
} else {System.out.println("str is null");
}

7.2.3. 使用默认值

如果方法或字段值可能为 null,考虑使用默认值或替代值。

String str = Optional.ofNullable(inputString).orElse("default value");

7.2.4. 适用断言和工具库

通过工具库如 Apache Commons Lang 提供的 StringUtilsObjectUtils 等可以避免手动编写空值检查代码,减少 NPE 风险。

StringUtils.isNotEmpty(str);  // 不会抛出空指针异常

7.2.5. 使用 @NonNull@Nullable 注解

通过注解可以清楚标明方法参数或返回值是否可以为空,这有助于避免因不清楚空指针约束导致的 NPE。

public void processString(@NonNull String str) {// str 必须不为 null
}

7.2.6. 避免深层嵌套的链式调用

通过设计合理的 API 接口或引入中间变量,避免深层次的链式调用,降低因某一环节为 null 导致的 NPE 风险。

Address address = person != null ? person.getAddress() : null;
if (address != null) {// 安全地访问 address
}

博文参考

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/80382.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java大师成长计划之第18天:Java Memory Model与Volatile关键字

&#x1f4e2; 友情提示&#xff1a; 本文由银河易创AI&#xff08;https://ai.eaigx.com&#xff09;平台gpt-4o-mini模型辅助创作完成&#xff0c;旨在提供灵感参考与技术分享&#xff0c;文中关键数据、代码与结论建议通过官方渠道验证。 在Java多线程编程中&#xff0c;线程…

js前端分片传输大文件+mongoose后端解析

最近一直在完善mongoose做webserver的项目&#xff0c;其中程序升级要通过前端传输升级包到服务器。 因为第一次写前端代码&#xff0c;分片传输的逻辑&#xff0c;网上一堆&#xff0c;大同小异&#xff0c;而且版本啊&#xff0c;API不一致的问题&#xff0c;导致头疼的很。后…

MiniMind:3块钱成本 + 2小时!训练自己的0.02B的大模型。minimind源码解读、MOE架构

大家好&#xff0c;我是此林。 目录 1. 前言 2. minimind模型源码解读 1. MiniMind Config部分 1.1. 基础参数 1.2. MOE配置 2. MiniMind Model 部分 2.1. MiniMindForCausalLM: 用于语言建模任务 2.2. 主干模型 MiniMindModel 2.3. MiniMindBlock: 模型的基本构建块…

引言:Client Hello 为何是 HTTPS 安全的核心?

当用户在浏览器中输入 https:// 时&#xff0c;看似简单的操作背后&#xff0c;隐藏着一场加密通信的“暗战”。Client Hello 作为 TLS 握手的首个消息&#xff0c;不仅决定了后续通信的加密强度&#xff0c;还可能成为攻击者的突破口。据统计&#xff0c;超过 35% 的网站因 TL…

Dockerfile 完全指南:从入门到最佳实践

Dockerfile 完全指南&#xff1a;从入门到最佳实践 1. Dockerfile 简介与作用 Dockerfile 是一个文本文件&#xff0c;包含了一系列用于构建 Docker 镜像的指令。它允许开发者通过简单的指令定义镜像的构建过程&#xff0c;实现自动化、可重复的镜像构建。 主要作用&#xf…

Python httpx库终极指南

一、发展历程与技术定位 1.1 历史演进 起源&#xff1a;httpx 由 Encode 团队开发&#xff0c;于 2019 年首次发布&#xff0c;目标是提供一个现代化的 HTTP 客户端&#xff0c;支持同步和异步操作&#xff0c;并兼容 HTTP/1.1 和 HTTP/2。背景&#xff1a; requests 库虽然功…

app加固

1、什么是加固? 我们之前讲的逆向,大多数都是用加密算法去加密一些明文字符串,然后把得到的结果用 Base64、Hex等进行编码后提交。加固其实也一样&#xff0c;只不过他通常加密的是 dex文件而已。但是 dex 文件加密以后&#xff0c;安卓系统是没法直接运行的。所以加固的核心&…

Win全兼容!五五 Excel Word 转 PDF 工具解决多场景转换难题

各位办公小能手们&#xff01;今天给你们介绍一款超牛的工具——五五Excel Word批量转PDF工具V5.5版。这玩意儿专注搞批量格式转换&#xff0c;能把Excel&#xff08;.xls/.xlsx&#xff09;和Word&#xff08;.doc/.docx&#xff09;文档唰唰地变成PDF格式。 先说说它的核心功…

springCloud/Alibaba常用中间件之Nacos服务注册与发现

文章目录 SpringCloud Alibaba:依赖版本补充六、Nacos:服务注册与发现1、下载安装Nacos2、服务注册1. 导入依赖(这里以服务提供者为例)2. 修改配置文件和主启动类3. 创建业务类4. 测试 3.服务映射1. 导入依赖2. 修改配置文件和主启动类3. 创建业务类和RestTemplate配置类用来提…

uniapp中score-view中的文字无法换行问题。

项目场景&#xff1a; 今天遇到一个很恶心的问题&#xff0c;uniapp中的文字突然无法换行了。得..就介样 原因分析&#xff1a; 提示&#xff1a;经过一fan研究后发现 scroll-view为了能够横向滚动设置了white-space: nowrap; 强制不换行 解决起来最先想到的是&#xff0c;父…

【STM32 学习笔记】I2C通信协议

注&#xff1a;通信协议的设计背景 3:00~10:13 I2C 通讯协议(Inter&#xff0d;Integrated Circuit)是由Phiilps公司开发的&#xff0c;由于它引脚少&#xff0c;硬件实现简单&#xff0c;可扩展性强&#xff0c; 不需要USART、CAN等通讯协议的外部收发设备&#xff0c;现在被广…

【网络原理】数据链路层

目录 一. 以太网 二. 以太网数据帧 三. MAC地址 四. MTU 五. ARP协议 六. DNS 一. 以太网 以太网是一种基于有线或无线介质的计算机网络技术&#xff0c;定义了物理层和数据链路层的协议&#xff0c;用于在局域网中传输数据帧。 二. 以太网数据帧 1&#xff09;目标地址 …

控制台打印带格式内容

1. 场景 很多软件会在控制台打印带颜色和格式的文字&#xff0c;需要使用转义符实现这个功能。 2. 详细说明 2.1.转义符说明 样式开始&#xff1a;\033[参数1;参数2;参数3m 可以多个参数叠加&#xff0c;若同一类型的参数&#xff08;如字体颜色&#xff09;设置了多个&…

[6-2] 定时器定时中断定时器外部时钟 江协科技学习笔记(41个知识点)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 V 30 31 32 33 34 35 36 37 38 39 40 41

数据库的脱敏策略

数据库的脱敏策略&#xff1a;就是屏蔽敏感的数据 脱敏策略三要求&#xff1a; &#xff08;1&#xff09;表对象 &#xff08;2&#xff09;生效条件&#xff08;脱敏列、脱敏函数&#xff09; &#xff08;3&#xff09;二元组 常见的脱敏策略规则&#xff1a; 替换、重排、…

Python序列化的学习笔记

1. Npy&Numpy O4-mini-Cursor&#xff1a;如果.npy文件里包含了「Python对象」而非纯数值数组时&#xff0c;就必须在加载时加上allow_pickleTrue。

[思维模式-27]:《本质思考力》-7- 逆向思考的原理与应用

目录 一、什么是逆向思考 1.1、逆向思考的六大核心思维模式 1.2、逆向思考的四大实践方法 1. 假设倒置法 2. 缺陷重构法 3. 用户反推法 4. 规则解构法 1.3、逆向思考的经典案例库 1. 商业创新&#xff1a;从“卖产品”到“卖服务” 2. 用户体验&#xff1a;从“功能满…

在python中,为什么要引入事件循环这个概念?

在Python中&#xff0c;事件循环&#xff08;Event Loop&#xff09;是异步编程的核心机制&#xff0c;它的引入解决了传统同步编程模型在高并发场景下的效率瓶颈问题。以下从技术演进、性能优化和编程范式三个角度&#xff0c;探讨这一概念的必要性及其价值。 一、同步模型的局…

Taccel:一个高性能的GPU加速视触觉机器人模拟平台

触觉感知对于实现人类水平的机器人操作能力至关重要。而视觉触觉传感器&#xff08;VBTS&#xff09;作为一种有前景的解决方案&#xff0c;通过相机捕捉弹性凝胶垫的形变模式来感知接触的方式&#xff0c;为视触觉机器人提供了高空间分辨率和成本效益。然而&#xff0c;这些传…

oracle 会话管理

会话管理 1&#xff1a;查看当前所有用户的会话(SESSION)&#xff1a; SELECT * FROM V S E S S I O N W H E R E U S E R N A M E I S N O T N U L L O R D E R B Y L O G O N T I M E , S I D ; 其中 O r a c l e 内部进程的 U S E R N A M E 为空 2 &#xff1a;查看当前…