AI写Java实测:能写样板代码,但并发幂等还得人工兜底 别又牛逼了——这话我最近听得耳朵快起茧了。无论是技术群还是朋友圈每隔几天就有人抛出一句AI 写 Java 代码真的行吗评论区立刻分成两派一派把 AI 吹成拎包入住的架构师另一派翻着白眼说它连个 Hello World 都写不利索。两派我都当过。从去年开始我陆陆续续让 AI 帮我写了不少 Java 代码从单表 CRUD 到带库存扣减、幂等控制的核心接口都试过。说句实话AI 写 Java 确实行但行的边界比大多数人想象的要窄得多。它不是魔法也不是废物它更像一个知识量极大、但没有项目经验的实习生——能在一个小时内写出你一天的工作量也能一本正经地给你编一个根本不存在的方法还能在你指出错误之后诚恳道歉、然后继续犯错。这篇文章不吹不黑记录我自己实测 AI 写 Java 的全过程哪些场景真的好用哪些场景翻车翻得惨以及我现在最顺手的一套人机协作打法。如果你还在观望怎么上手或者已经用了一阵子但总觉得差点意思这篇应该能帮上忙。1. 先泼盆冷水AI 写 Java 的真实水平在哪一档很多人问AI 能不能写 Java本质上是想问我能不能把活丢给它然后去喝咖啡。答案很遗憾不能。但如果你换个问法——它能不能帮我快速搭骨架、写样板、出第一版实现答案是非常肯定的。1.1 三类任务三类结果我把过去几个月里让 AI 写 Java 的实测结果按任务类型分了个类直接看这张表任务类型生成质量人工返工量样板代码DTO、枚举、配置文件、常用工具类很好格式规范命名合理基本不用改单表 CRUD 的 Controller / Service / Mapper良好多数能一次编译通过少量调整复杂业务规则、跨模块状态流转、并发控制不稳定经常看起来对大概率要大改先说说样板代码。AI 从海量开源代码里吸收了太多这种模式Java 世界的 getter/setter、Builder、统一返回结构、分页对象、日期格式化它闭着眼睛都能写。我试过让它生成一个几十个字段的 DTO类名、注解、getter/setter 几乎零错误。这类工作对人来说有手就行、毫无乐趣但对 AI 来说简直是主场。单表 CRUD 的表现也不错因为它本身就是教科书级的重复模式。但不错不等于直接用。分页参数怎么传、排序字段怎么处理、状态字段的默认值是什么AI 会按全世界最常见的写法来猜而你的项目很可能恰好不是那种写法。比如它的分页逻辑默认从第 0 页开始你项目里可能约定从第 1 页开始。这些细节必须要靠提示词去约束约束不到位就等着改。到了复杂业务AI 就开始表演型写作了。状态机流转、补偿逻辑、幂等控制、并发扣减、多条件组合校验——这些场景下它生成的代码结构完整、注解齐全、看起来极其专业但仔细一跑里面藏着几个致命逻辑漏洞。它生成的代码不是不会编译而是编译能过、单测能过、一到并发就出事。1.2 别拿它和架构师比把它当读过三年 JDK 源码的实习生给 AI 定位这件事关系到你后面所有的工作流设计。我以前总喜欢拿它和高级工程师比一比就失望后来换了个类比一切都顺了它就像一个读过海量源码、基础扎实但完全没经历过生产环境的实习生。实习生有哪些特点动作快给什么活都接知识面广但判断力弱不会主动质疑需求也不知道你们项目里的隐性约定。它不会问你这里的库存是物理库存还是可售库存它只会按字面意思理解然后替你做一个很可能不对的决定。AI 完美继承了这些特点。它有惊人的信息广度和表述自信这恰恰是最危险的地方。越是在你不熟悉的领域你越容易信任它它就越容易坑你。所以我的第一条经验是永远别把 AI 的输出当结论只当它是候选结果。这和带实习生是同一个道理——你不 review 他的代码就上线那是你的问题不是他的问题。把这个定位想清楚后面所有章节的方法论就顺了任务拆小、给足上下文、逐行审查、关键逻辑自己拍板。2. 我用 AI 从零写一个订单模块的完整实测纸上谈兵没意思我说一个真实做过的实验。某内部管理系统的订单模块需求本身不复杂但我故意叠了三个常见约束库存扣减、接口幂等、事务一致性。这是生产环境里几乎必踩的点也是 AI 最容易翻车的地方。2.1 开工需求要喂到什么程度第一次我偷懒只给了一句话提示词帮我写个创建订单的接口。结果毫不意外它给了我一个纯 CRUD——没有库存校验没有事务没有幂等甚至没有业务异常。这不是 AI 蠢是我自己没把需求说清楚就像你跟实习生说把这个订单接口做了他能给你做出花来才怪。于是我把提示词重写了一遍把背景、约束、输出要求一次性交代清楚。实际使用的提示词大致是这个结构背景某库存管理系统的订单服务Java 17 Maven后端框架是 Spring Boot 3 持久层用 MyBatis数据库是 MySQL。 任务实现 createOrder(CreateOrderRequest req) 方法。 要求 1. 创建前校验商品库存是否足够 2. 同一个 externalRequestId 重复调用必须幂等只允许创建一个订单 3. 扣减库存和写入订单必须处于同一事务 4. 库存不足时抛出带业务码的异常 5. 返回生成的订单号 输出直接给出完整可编译的 Java 代码包含 import 并附 3 条关键设计说明。这版提示词的效果立竿见影。原因很简单AI 没有记忆也不了解你的项目每次对话都像一场面试。背景信息、功能要求、边界条件、输出格式这四样东西给得越全它的表现越好。很多人说AI 写代码不行其实有相当一部分是需求没讲清。2.2 高光时刻第一版生成的代码长什么样第二版生成的结果确实让我眼前一亮。它给出了一个结构完整的 Service 实现构造器注入、注解、分层调用都像模像样。我把核心代码精简后贴出来Service public class OrderServiceImpl implements OrderService { private final OrderMapper orderMapper; private final ProductMapper productMapper; public OrderServiceImpl(OrderMapper orderMapper, ProductMapper productMapper) { this.orderMapper orderMapper; this.productMapper productMapper; } Override Transactional public String createOrder(CreateOrderRequest req) { // 1. 校验库存 Product product productMapper.selectById(req.getProductId()); if (product null || product.getStock() req.getQuantity()) { throw new BusinessException(1001, 库存不足); } // 2. 幂等检查 Order exist orderMapper.selectByExternalRequestId(req.getExternalRequestId()); if (exist ! null) { return exist.getOrderNo(); } // 3. 扣减库存 productMapper.decreaseStock(req.getProductId(), req.getQuantity()); // 4. 创建订单 Order order buildOrder(req); orderMapper.insert(order); return order.getOrderNo(); } }第一眼扫过去结构清楚、命名规范、注解齐全好像直接就能用。请记住这个第一眼的陷阱——AI 生成的代码最擅长的就是让你第一眼看过去觉得很专业。我是一个个点去抠的时候才发现了下面这一堆问题。2.3 翻车现场五处必须手改的地方我把必须修改的点列出来每一个都是生产环境里的真问题不是吹毛求疵。第一幂等检查不是原子的。先查后插两个并发请求同时进来到步骤 2都会发现不存在然后都去扣库存、都去插入——幂等直接失效。解决方式必须靠数据库唯一索引兜底或者在插入时捕获唯一键冲突。只靠 select 判断是典型的看起来对。第二库存扣减没有条件限制。它先查一遍库存够了再扣但这两步之间库存可能已经被别的请求改掉了。正确的做法是把检查和扣减合并成一条条件更新 SQL靠数据库原子性保证不超卖。第三事务注解位置看起来没问题但它生成的decreaseStock方法是直接在 Mapper 上加了个普通 update。如果我想在里面加扣减前库存不足则返回影响行数为 0的逻辑就必须在 SQL 层面完成而不是 Java 层面。第四BusinessException 是 AI 自己猜的。它不知道我项目里异常体系到底是 checked 还是 unchecked也不知道统一异常处理会拦截什么类型。这种猜一个类的行为相当危险编译可能过但运行时行为完全不可控。第五幂等返回策略值得商榷。重复请求返回原订单号这是幂等查询的混合语义另一种做法是直接抛重复提交异常。AI 替你做了这个业务决策但它根本不知道你们产品和用户约定是什么。我最后提交的版本核心部分改成了这样Override Transactional public String createOrder(CreateOrderRequest req) { // 1. 幂等兜底依赖数据库唯一索引而不是先查后插 if (orderMapper.existsByExternalRequestId(req.getExternalRequestId())) { return orderMapper.selectByExternalRequestId(req.getExternalRequestId()).getOrderNo(); } // 2. 条件更新扣库存一次 SQL 原子完成检查扣减 int affected productMapper.decreaseStockIfAvailable(req.getProductId(), req.getQuantity()); if (affected 0) { throw new BusinessException(1001, 库存不足); } // 3. 写订单 Order order buildOrder(req); orderMapper.insert(order); return order.getOrderNo(); }对应的 Mapper SQL 也必须改成这样update iddecreaseStockIfAvailable UPDATE product SET stock stock - #{quantity} WHERE id #{productId} AND stock #{quantity} /update同时在external_request_id字段上建唯一索引作为幂等的最后一道防线。这三处改动每一处都是 AI 自己想不到的。它不是不会写代码而是对并发下的正确性没有直觉。Java 工程师的价值从来不在会写语法而在知道哪些地方会出错。3. 为什么 AI 会写 Java又为什么经常翻车搞清楚 AI 为什么会翻车比单纯骂它有用得多。这里涉及两个层面一是大模型本身的工作机制二是 Java 这个语言的工程特征。3.1 它怎么写代码语言建模不是逻辑推理大语言模型本质上是下一个词预测器。你给它一段文本它根据已经见过的海量文本模式预测最可能出现的下一个词。代码对它来说只是有特定格式的文本而 Java 恰好是人类历史上开源代码数量最大的语言之一——构造器、getter/setter、Service 层、Mapper 层这些模式在训练数据里出现了几亿次。所以它写 Java 的那种顺手感是真实的。它见过太多用 Spring WEB 写接口、用 ORM 框架做持久化的例子高概率模式下它几乎不会错。你可以理解为一个背了海量例题的考生遇到相似的题解题过程行云流水遇到没见过的变形题它也能凭看着像硬写一个过程但写出来的可能每一步都对不上。你自己跑一遍订单模块就能感受到样板代码和常规 CRUD 是它的舒适区一到并发、状态流转、事务边界这些需要因果推理的地方它就露馅了。因为它没有真正的世界模型它不知道数据库锁意味着什么只是知道并发场景下通常会加锁这个文本模式。3.2 Java 工程化的三个特征恰好戳在 AI 软肋上Java 不是一个写完就能跑的语言它的工程化特性决定了 AI 的错误会被快速暴露但也会更频繁地踩中。第一个特征是强类型和编译期约束。泛型、受检异常、方法签名每一样都极其严格。AI 在生成时是逐 token 预测的它看不到整个文件也看不到你的依赖树更看不到编译器报错。于是它经常生成语法对但类型不通的代码。编译器成了它最大的敌人也成了你最好的防线。第二个特征是依赖和版本的漂移。AI 的知识有截止时间即便它在不断更新也永远追不上依赖库的演进速度。它会使用某个框架版本里已经被移除的方法或者把两个不兼容版本的方法写在同一段代码里。我遇到过它让我用一个工具类的toXxx()方法我查了当前依赖的 jar 包发现那个方法早在三个版本前就废弃了。第三个特征是工程上下文的割裂。Java 项目尤其讲究分层Controller 不写业务逻辑Service 管事务Mapper 管 SQLDTO 和 VO 严格区分。AI 默认只能看到你当前这一小段上下文它不知道你项目里统一返回对象叫 Result 还是 ApiResponse也不知道你的异常基类是哪个。它只能用全球最常见写法来猜而你的团队约定很可能就是为了反常见而定的。3.3 三大翻车模式我总结 AI 写 Java 最常踩的三个坑大家可以拿这个清单去对照自己遇到的情况。第一种是幻觉 API。它生成一个类名、方法名、依赖坐标语法上完全合理但你一编译发现根本没有这个类。它会一本正经地告诉你使用 X 类可以解决实际上 X 类根本不在你的 classpath 里。这是大模型的通病——它分不清真实存在的知识和看起来合理的推测。第二种是需求想当然。你说校验库存它默认是查一遍库存字段然后比较大小但你的业务可能是冻结库存加可用库存两套语义或者需要锁定库存防止超卖。它不会主动问只会替你做一个最简单、最常见的假设。第三种是忽略非功能因素。AI 对并发、安全、性能几乎没有感觉。不加锁、不做幂等、不防 SQL 注入、不脱敏敏感字段、不考虑大结果集的内存占用——这些它默认不做。它生成的是功能正确的代码而你需要的往往是生产正确的代码这两者之间的距离就是你要补的功课。4. 把 AI 变成一个合格结对搭档的实操方法知道它能干什么、不能干什么之后剩下的问题就是怎么把它用好。我现在的定位很明确AI 是结对编程里的那个配合者我是决策者。下面这几个方法是我实测下来最有效的直接照着用就可以。4.1 提示词三要素背景、约束、输出格式很多人提示词就写一句话帮我写个接口。这种用法等于把实习生叫过来说你把这个模块做了就想走人。正确的做法是把提示词当一份小型需求文档固定包含三部分内容。第一部分是背景。告诉它项目是什么、技术栈是什么、数据库是什么、相关的类有哪些。AI 没有记忆每次对话都是全新的你给的信息越全它猜得越准。第二部分是约束。明确告诉它只能用哪些依赖、必须处理哪些场景、不需要做哪些事情。很多翻车都是因为没说不许做于是它自由发挥。第三部分是输出格式。要求它给出完整代码、关键设计说明、或者只给 diff。格式明确了你后面的 review 工作量会小很多。一个可以直接套用的模板大概是这样的角色你是一名熟悉 Java 17 和某主流后端框架的资深工程师。 背景这是某库存管理系统的订单模块持久层用 MyBatis数据库 MySQL。 任务实现某某方法要求如下 1. ... 2. ... 约束 - 只能使用现有依赖中的类不要引入新框架 - 必须处理并发与异常边界 - 不要写单元测试或需要同时给出单元测试 输出完整可编译代码 三条关键设计说明。另外还有一个非常重要的习惯小步走。千万不要一次让它生成整个系统或者一个巨大的 Service 类。一次只让它写一个类、一个方法、一句 SQL。每一步都编译验证、人工过目再进入下一轮。步子越碎翻车的范围越小排查的难度越低。把大任务拆小本质上就是在控制它的错误半径。4.2 四张人工审查清单AI 生成的代码要合并进主干我建议至少过四道审查别嫌麻烦这四道检查能拦下 90% 的生产事故。第一道是编译与依赖审查。跑一次编译用 IDE 和构建工具自带的静态检查扫一遍。重点看它用的 API 是不是当前依赖版本里真实存在的有没有用了已被废弃的方法。凡是它用了但你没见过的方法一律点进去看一眼源码。第二道是安全审查。AI 生成的代码经常不做输入校验也分不清楚什么是用户可控的、什么是系统内部的。SQL 拼接要彻查越权风险要想清楚日志里别打印敏感字段。我见过 AI 生成了一段把用户手机号直接塞进日志的代码那眼神真的绝了。第三道是并发与事务审查。所有涉及写操作的路径都要问三个问题会不会重复执行会不会同时执行失败了一部分怎么办幂等靠不靠唯一索引事务边界合不合理事务方法是不是被同类内部调用导致失效。这些都是 AI 的盲区中的盲区。第四道是异常与边界审查。空值、零值、超大值、重复值、超长字符串、精度问题逐一过一遍。AI 对边界条件的感知非常弱它写的if (obj ! null)看着合理但可能漏了quantity必须大于 0 这种最基本的业务约束。4.3 人机分工的三个原则用了一段时间之后我的分工原则浓缩成三句话分享给你。骨架交给 AI语义自己定。让 AI 生成类结构、方法签名、分层组织、样板代码这些是它最擅长的。但状态流转要自己理业务规则要自己写边界条件要自己补。你负责的是这个系统到底要怎么运转AI 负责的是把这些运转机制翻译成代码。重复劳动交给 AI排查自己来。字段映射、对象转换、分页封装、测试数据构造这些没有创造性但费时间的活大胆丢给它。但运行出问题之后排查这个环节千万别外包。AI 会给你非常自信的错误解释而且它的解释经常和实际情况对不上。排查必须自己动手把堆栈日志读进去把数据流捋清楚。参考代码交给 AI决策自己拍板。你可以让它列出方案对比比如乐观锁和悲观锁各自的优缺点它写得又快又全。但方案选型不能让它替你决定它不知道你的业务量级、部署环境、团队水平。最终拍板的人必须是你。4.4 团队落地时的三个建议如果你不是一个人在折腾而是想带着团队一起用 AI 写 Java我还有三条落地建议。第一条把 AI 生成的代码在评审里单独标记。不是要歧视它而是要强制团队意识到这段代码需要重点关注。AI 生成的内容往往流畅到让人放松警惕标记出来等于给所有人一个提醒。第二条在 CI 里加强自动化检查。编译、静态扫描、关键路径的集成测试这些卡点越多AI 翻车的代价越小。第三条建立团队的提示词库。把你们项目里的技术栈、异常体系、统一返回结构、命名约定这些背景写成一个标准模板所有人都从同一个模板出发效果会比各自瞎试稳定得多。5. 常见问题与排查技巧实录最后这部分是我踩坑之后的复盘记录都是真实遇到过、且非常有代表性的问题。我把它写成速查式的风格方便你遇到类似问题时直接对照。5.1 编译不过AI 还死不认账有次我让它生成一段用 Stream 做分组的代码它用了Collectors.toMap()但没处理重复 key 的情况编译直接挂在 key 冲突的运行时问题上。我贴了报错回去它道歉、修改然后又引入了新的类型不匹配。循环了三轮还没解决最后还是我手动一改完事。经验是把编译错误全文贴回给 AI 让它修改这个操作可以有效但最多循环三轮。三轮之后如果还修不好说明它对这个具体问题的理解是有缺陷的继续纠缠只会浪费时间。这时候自己动手反而比跟它来回拉扯快。5.2 单元测试通过业务还是错的这是最让人血压升高的一种情况。AI 生成的代码配上 AI 生成的单元测试居然全绿但拿真实业务数据一跑结果完全不对。原因很微妙测试和代码是同源的。AI 基于自己对需求的理解写实现又基于同一份理解写测试两头都对上了但两头都偏离了真实需求。所以我的原则是AI 生成的单测可以跑但只能当参考。真正的验证用例必须你自己列尤其要把那些模棱两可的需求用例子固化成测试再拿这些测试去验 AI 的实现。比如库存刚好等于购买数量这种边界AI 往往不会单独测但你需要。5.3 它编造了一个看起来合理的类有一次我让 AI 对接某个内部网关 SDK它直接写出了一段调用GatewayClient.create()的代码还说这个方法是 SDK 里现成的。一编译发现根本不存在这个类。我翻了依赖 jar 包确认这个组件连类名都对不上完全是它编出来的。这类幻觉的可怕之处在于如果是一个你完全陌生的依赖你很可能被它蒙混过去。所以我在前面强调凡是 AI 用到但你没见过的方法和类一律点进去验证它真实存在。可以养成的肌肉记忆是IDE 里对它按跳转键跳不过去就不要信。5.4 验证生成代码的四个手段为了保证质量我现在会按顺序做四件事。第一编译加静态检查让构建工具把所有低级问题先暴露出来。第二让 AI 生成单元测试但用我自己的业务用例去跑重点覆盖边界条件。第三让它解释关键代码的为什么这么写如果解释含含糊糊、答非所问那大概率是猜的。第四对关键路径做一次真实联调调用真实接口观察数据库里的数据变化和日志输出。这四件事做完我才会把代码合并进主干。有人觉得麻烦但算一笔账一次生产事故的排查成本往往比这四道工序的十倍还多。从这个角度看这套验证流程是在省钱。5.5 一张速查表最后把常见问题和应对方式整理成一张速查表方便你贴在手边症状可能原因快速解法编译报错多依赖版本不匹配 / 知识过期回填依赖版本号把完整报错贴给它重试某个类或方法找不到幻觉 API跳转验证真实存在换成实际 API逻辑看似完整但边界缺失需求没说清把边界场景写成显式约束放进提示词并发下出现重复数据模型默认不处理并发加唯一索引或乐观锁改 SQL 条件更新单测全过但业务结果错测试与实现同源用自己的业务用例重新设计测试这五类问题几乎覆盖了我见过的大部分 AI 写 Java 事故现场。你对照着排查基本能快速定位问题的根源。我个人现在的工作流已经离不开 AI 了但和很多人想象的不太一样——我让它干的活越多我自己需要动脑的地方反而越多了。因为省下来的时间并没有用来休息而是全部砸进了需求分析、边界梳理、方案选型和代码审查里。平均下来一个接口从半小时缩到十分钟但剩下的那十分钟每一秒都在想业务到底要什么、哪里会出问题、怎么兜底。这大概就是 AI 写 Java 最实在的价值它把写代码变成了快消品而把想清楚代码背后的业务这件事翻倍地还给了我们。别又是吹得天花乱坠也别一棒子打死把它当成一个有知识没经验的实习生来用。把话说清楚把活拆小把审查做扎实它真的能给你顶上不小的忙。