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

news2025/5/12 19:00:38

摘要

这篇文章是关于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.coloradmin.cn/o/2374134.html

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

相关文章

机器人手臂“听不懂“指令?Ethercat转PROFINET网关妙解通信僵局

机器人手臂"听不懂"指令&#xff1f;Ethercat转PROFINET网关妙解产线通信僵局 协作机器人&#xff08;如KUKA iiWA&#xff09;使用EtherCAT控制&#xff0c;与Profinet主站&#xff08;如西门子840D CNC&#xff09;同步动作。 客户反馈&#xff1a;基于Profinet…

深度学习 CNN

CNN 简介 什么是 CNN&#xff1f; 卷积神经网络&#xff08;Convolutional Neural Network&#xff09;是专为处理网格数据&#xff08;如图像&#xff09;设计的神经网络。核心组件&#xff1a; 卷积层 &#xff1a;提取局部特征&#xff08;如边缘、纹理&#xff09;通过卷…

MySQL索引原理以及SQL优化(二)

目录 1. 索引与约束 1.1 索引是什么 1.2 索引的目的 1.3 索引分类 1.3.1 数据结构 1.3.2 物理存储 1.3.3 列属性 1.3.4 列的个数 1.4 主键的选择 1.5 索引使用场景 1.6 索引的底层实现 1.6.1 索引存储 1.6.2 页 1.6.3 B 树 1.6.4 B 树层高问题 1.6.5 自增 id 1.7 innod…

MATLAB中矩阵和数组的区别

文章目录 前言环境配置1. 数据结构本质2. 运算规则&#xff08;1&#xff09;基本运算&#xff08;2&#xff09;特殊运算 3. 函数与操作4. 高维支持5. 创建方式 前言 在 MATLAB 中&#xff0c;矩阵&#xff08;Matrix&#xff09; 和 数组&#xff08;Array&#xff09; 的概…

Desfire Ev1\Ev2\Ev3卡DES\3K3DES\AES加解密读写C#示例源码

本示例使用的发卡器&#xff1a;https://item.taobao.com/item.htm?spma21dvs.23580594.0.0.1d292c1bYhsS9c&ftt&id917152255720 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using S…

MySQL核心内容【完结】

MySQL核心内容 文章目录 MySQL核心内容1.MySQL核心内容目录2.MySQL知识面扩展3.MySQL安装4.MySQL配置目录介绍Mysql配置远程ip连接 5.MySQL基础1.MySQL数据类型1.数值类型2.字符串类型3.日期和时间类型4.enum和set 2.MySQL运算符1.算数运算符2.逻辑运算符3.比较运算符 3.MySQL完…

C++类和对象进阶 —— 与数据结构的结合

&#x1f381;个人主页&#xff1a;工藤新一 &#x1f50d;系列专栏&#xff1a;C面向对象&#xff08;类和对象篇&#xff09; &#x1f31f;心中的天空之城&#xff0c;终会照亮我前方的路 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 文章目录 […

Django之账号登录及权限管理

账号登录及权限管理 目录 1.登录功能 2.退出登录 3.权限管理 4.代码展示合集 这篇文章, 会讲到如何实现账号登录。账号就是我们上一篇文章写的账号管理功能, 就使用那里面已经创建好的账号。这一次登录, 我们分为三种角色, 分别是员工, 领导, 管理员。不同的角色, 登录进去…

EXCEL中嵌入其他表格等文件

在EXCEL中嵌入其他表格 先放链接&#xff1a;https://jingyan.baidu.com/article/295430f11708c34d7e00509a.html 步骤如下&#xff1a; 1、打开一个需要嵌入新表格的excel表。 2、切换至“插入”菜单中&#xff0c;单击选择“对象”。 3、如下图所示&#xff0c;会弹出“对象…

21. LangChain金融领域:合同审查与风险预警自动化

引言&#xff1a;当AI成为24小时不眠的法律顾问 2025年某商业银行的智能合同系统&#xff0c;将百万级合同审查时间从平均3周缩短至9分钟&#xff0c;风险条款识别准确率达98.7%。本文将基于LangChain的金融法律框架&#xff0c;详解如何构建合规、精准、可追溯的智能风控体系…

Springboot使用事件流调用大模型接口

什么是事件流 事件流&#xff08;Event Stream&#xff09; 是一种处理和传递事件的方式&#xff0c;通常用于系统中的异步消息传递或实时数据流。在事件驱动架构&#xff08;Event-Driven Architecture&#xff09;中&#xff0c;事件流扮演着至关重要的角色。 事件流的概念…

计算机网络--2

TCP三次握手 TCP连接为什么需要三次握手 1. 由于网络情况复杂,可能会出现丢包现象,如果第二次握手的时候服务器就认为这个端口可用,然后一直开启,但是如果客户端未收到服务器发送的回复,那么就会重新发送请求,服务器就会重新开启一个端口连接,这样就会浪费一个端口。 三…

尤雨溪宣布:Vue 生态正式引入 AI

在前端开发领域,Vue 框架一直以其易用性和灵活性受到广大开发者的喜爱。 而如今,Vue 生态在人工智能(AI)领域的应用上又迈出了重要的一步。 尤雨溪近日宣布,Vue、Vite 和 Rolldown 的文档网站均已添加了llms.txt文件,这一举措旨在让大型语言模型(LLM)更方便地理解这些…

蓝桥杯第十六届c组c++题目及个人理解

本篇文章只是部分题目的理解&#xff0c;代码和思路仅供参考&#xff0c;切勿当成正确答案&#xff0c;欢迎各位小伙伴在评论区与博主交流&#xff01; 题目&#xff1a;2025 题目解析 核心提取 要求的数中至少有1个0、2个2、1个5 代码展示 #include<iostream> #incl…

硬件工程师笔记——电子器件汇总大全

目录 1、电阻 工作原理 欧姆定律 电阻的物理本质 一、限制电流 二、分压作用 三、消耗电能&#xff08;将电能转化为热能&#xff09; 2、压敏电阻 伏安特性 1. 过压保护 2. 电压调节 3. 浪涌吸收 4. 消噪与消火花 5. 高频应用 3、电容 工作原理 &#xff08;…

微软推动智能体协同运作:支持 A2A、MCP 协议

今日凌晨&#xff0c;微软宣布 Azure AI Foundry 和 Microsoft Copilot Studio 两大开发平台支持最新 Agent 开发协议 A2A&#xff0c;并与谷歌合作开发扩大该协议&#xff0c;这一举措对智能体赛道意义重大。 现状与变革意义 当前智能体领域类似战国时代&#xff0c;各家技术…

Linxu实验五——NFS服务器

一.NFS服务器介绍 NFS服务器&#xff08;Network File System&#xff09;是一种基于网络的分布式文件系统协议&#xff0c;允许不同操作系统的主机通过网络共享文件和目录3。其核心作用在于实现跨平台的资源透明访问&#xff0c;例如在Linux和Unix系统之间共享静态数据&#…

20242817李臻《Linux⾼级编程实践》第9周

20242817李臻《Linux⾼级编程实践》第9周 一、AI对学习内容的总结 第十章 Linux下的数据库编程 10.1 MySQL数据库简介 MySQL概述&#xff1a;MySQL是一个开源的关系型数据库管理系统&#xff0c;最初由瑞典MySQL AB公司开发&#xff0c;后经SUN公司收购&#xff0c;现属于O…

开源分享:TTS-Web-Vue系列:SSML格式化功能与高级语音合成

&#x1f3af; 本文是TTS-Web-Vue系列的第十二篇文章&#xff0c;重点介绍项目新增的SSML格式化功能以及SSML在语音合成中的应用。通过自动格式化和实时预览&#xff0c;我们显著提升了SSML编辑体验&#xff0c;让用户能够更精确地控制语音合成的细节&#xff0c;实现更自然、更…

FAST-LIO笔记

1.FAST-LIO FAST-LIO 是一个计算效率高、鲁棒性强的激光-惯性里程计系统。该系统通过紧耦合的迭代扩展卡尔曼滤波器&#xff08;IEKF&#xff09;将激光雷达特征点与IMU数据进行融合&#xff0c;使其在快速运动、噪声较大或环境复杂、存在退化的情况下仍能实现稳定的导航。 1…