AI+Java中级开发面试题库
生成时间:2026-05-20 22:47 | 题目数:10 | AI:Java ≈ 5:5
【面试题】1. RAG系统中检索阶段使用向量相似度搜索召回的文档,为什么还需要Rerank重排序?直接用Top-K向量结果有什么问题?
【标准答案】 向量相似度搜索本质是语义近似匹配,用余弦相似度或点积衡量embedding距离,但它有两个硬伤:第一,它对细粒度语义区分能力弱,比如"Java并发编程"和"Java并行计算"的embedding非常接近,向量检索可能把不太相关的文档也召回来了;第二,向量检索是单塔模型,query和document各自独立编码,没有做深层交互,缺乏精确的相关性判断。
Rerank用的是交叉编码器(Cross-Encoder),把query和每个候选document拼在一起做深层attention交互,能捕捉更细粒度的语义匹配关系。实践上典型做法是向量检索先召回Top-20~50,再用Rerank模型精排取Top-5送进LLM。效果上,加Rerank后答案准确率通常能提升10-20个百分点,尤其是多跳推理类问题提升更明显。
【面试题】2. Java中ReentrantLock和synchronized有什么区别?什么场景下必须用ReentrantLock?
【标准答案】 核心区别有三个维度:
第一,功能维度——synchronized是JVM层面的关键字,自动加锁释放;ReentrantLock是API层面的类,需要手动lock/unlock(必须在finally里释放)。ReentrantLock额外支持公平锁、可中断锁、多条件变量(Condition)、超时获取锁(tryLock with timeout)。
第二,性能维度——JDK 6之后synchronized做了大量优化(偏向锁→轻量级锁→重量级锁升级),低竞争场景下性能和ReentrantLock基本持平。但高竞争场景下,ReentrantLock的公平锁模式可以避免线程饥饿,synchronized没有这个能力。
第三,必须用ReentrantLock的场景:需要tryLock非阻塞获取锁时;需要公平锁保证先到先得时;需要多个条件变量做精准线程唤醒时;需要中断等待锁的线程时。实际项目中,简单同步用synchronized就够了,涉及复杂线程协调才上ReentrantLock。
【面试题】3. 在AI Agent开发中,ReAct模式和Function Calling模式各有什么优缺点?实际项目中怎么选?
【标准答案】 ReAct模式是"思考-行动-观察"循环,LLM先推理下一步该做什么,调用工具后观察结果再继续推理。优点是推理链路透明、可审计,能处理需要多步推理的复杂任务;缺点是token消耗大、延迟高,每一步都要让模型"想一遍"。
Function Calling模式是LLM直接输出结构化的工具调用参数,由框架侧执行后把结果喂回来。优点是调用效率高、延迟低,适合工具明确、步骤固定的场景;缺点是推理深度有限,复杂任务容易选错工具或漏步骤。
我的选择策略:任务需要多步推理、动态决策路径的用ReAct(比如数据分析Agent、研究助手);工具调用模式固定、追求低延迟的用Function Calling(比如客服机器人查订单、FAQ问答)。实际中很多框架(LangChain、Spring AI)两者可以混合使用,外层ReAct做规划,内层Function Calling做具体执行。
【面试题】4. Spring Boot中@Autowired注入和构造器注入有什么区别?为什么推荐构造器注入?
【标准答案】 @Autowired字段注入是直接在属性上加注解,通过反射注入,代码简洁但问题多:第一,无法声明不可变性,字段不能是final的;第二,隐藏了依赖关系,类看起来不需要依赖,实际运行时必须要有;第三,容易导致过多依赖而不自知,因为加个字段太容易了;第四,和单元测试不友好,必须用反射或Spring容器才能注入mock。
构造器注入在构造器参数中声明依赖,Spring官方推荐。好处:依赖可以是final的,保证不可变和不为null;依赖关系显式,构造器参数多了一看就知道类职责过重该拆了;单元测试直接new对象传mock,不需要反射也不需要Spring容器。从Spring 4.3开始,如果只有一个构造器,@Autowired都可以省略。
一句话总结:字段注入是图省事的技术债,构造器注入才是正经做法。如果依赖特别多用lombok的@RequiredArgsConstructor可以减少样板代码。
【面试题】5. 什么是大模型的"幻觉"问题?在RAG场景下有哪些有效的缓解策略?
【标准答案】 幻觉是大模型生成看似合理但实际错误或虚构的内容,根本原因是模型基于统计概率生成文本,不是检索事实。表现形式有三种:事实性幻觉(编造不存在的信息)、推理幻觉(逻辑链错误)、忠实性幻觉(不遵守上下文给定的约束)。
RAG场景下缓解策略分三层:
检索层——提高召回质量是根源,优化chunk策略(按语义切分而非固定长度)、加Rerank精排、引入HyDE(让模型先生成假设性答案再拿它去检索)、混合检索(向量+关键词BM25互补)。
生成层——Prompt工程约束,明确指示"只根据以下参考资料回答,如果资料中没有相关信息请回答我不知道";降低temperature减少随机性;要求模型给出答案的同时引用来源片段。
验证层——对生成结果做事实性校验,可以用另一个LLM做自洽性检查(多次生成比对一致性),或者用NLI模型判断答案和检索文档是否矛盾。工业界最佳实践是三层组合使用,幻觉率可以压到5%以下。
【面试题】6. MySQL中什么情况下索引会失效?列举至少5种典型场景。
【标准答案】 索引失效是SQL调优的高频考点,典型场景:
- 对索引列使用函数或运算:WHERE YEAR(create_time) = 2026 会让create_time索引失效,应改为 WHERE create_time >= '2026-01-01' AND create_time < '2027-01-01'。
- 隐式类型转换:varchar列用int查,WHERE phone = 13800138000,phone是varchar但传了数字,MySQL会对列做隐式转换导致索引失效。
- 左模糊查询:WHERE name LIKE '%张',前导通配符让B+树无法走索引。只有右模糊 LIKE '张%' 才能走索引。
- 违反最左前缀原则:联合索引(a,b,c),查WHERE b=1跳过了a,索引失效。必须从最左列开始匹配。
- OR连接非索引列:WHERE a=1 OR d=2,a有索引d没有,整体走全表。可以用UNION ALL改写。
- NOT IN / NOT EXISTS / != / <>:在数据量大时优化器可能判断全表扫描成本更低而放弃索引。
面试时说5个就够了,但最后补一句"索引是否失效最终要看EXPLAIN的type列和Extra信息,不能死记规则",加分。
【面试题】7. 向量数据库(如Milvus/Pinecone)与传统关系型数据库在存储和检索机制上有什么本质区别?各适合什么场景?
【标准答案】 存储机制区别:传统数据库存结构化数据,行列表格,每列有明确类型和约束;向量数据库核心存的是高维浮点向量(通常768-3072维),附带少量元数据字段。传统数据库用B+树索引,向量数据库用HNSW、IVF_PQ等近似最近邻(ANN)索引。
检索机制区别:传统数据库精确查询,WHERE条件要么匹配要么不匹配,结果是确定性的;向量数据库做相似度检索,返回的是"最相近"的Top-K结果,结果有误差,是近似检索而非精确匹配。
适用场景:传统数据库适合事务处理、精确查询、复杂关联、数据一致性要求高的场景(订单、账户、库存);向量数据库适合语义搜索、推荐系统、RAG文档检索、以图搜图、去重聚类等场景。
实际项目中往往两者配合使用:元数据和结构化信息存MySQL,embedding向量存Milvus,检索时先向量召回再根据元数据过滤。很多向量数据库也开始支持标量过滤(如Milvus的Filter功能),但复杂事务和关联查询还是得靠关系型数据库。
【面试题】8. Java中ThreadLocal的原理是什么?为什么会发生内存泄漏?如何正确使用?
【标准答案】 原理:每个Thread对象内部维护一个ThreadLocalMap,key是ThreadLocal对象的弱引用,value是线程要存储的值。调用threadLocal.set()时,实际上是把值存到当前线程的ThreadLocalMap里,get()也是从当前线程的map里取,所以做到了线程隔离,不需要同步。
内存泄漏原因:ThreadLocalMap的key是弱引用,GC时如果ThreadLocal对象没有外部强引用就会被回收,此时key变成null但value还在,而且value是强引用。如果线程长期存活(比如线程池中的核心线程),这些key为null的value就永远无法被回收,造成内存泄漏。
正确使用方式:第一,最关键的,用完必须调用remove()清理,通常放在finally块或filter的afterCompletion里;第二,用ThreadLocal.withInitial()给初始值避免NPE;第三,线程池场景要特别注意,线程复用导致上一个任务的数据可能泄漏到下一个任务,必须在任务执行前后清理。
Java 22引入了Scoped Values(_preview),是ThreadLocal的替代方案,不可变且作用域受限,未来是更好的选择。
【面试题】9. Spring AI中如何实现一个带工具调用的ChatClient?请描述核心流程和关键代码结构。
【标准答案】 Spring AI是Spring官方的AI开发框架,ChatClient + Tool Calling的核心流程:
第一步,定义工具Bean。创建一个@Component类,方法上加@Tool(description="...")注解声明工具描述和参数:
@Component
public class WeatherTool {
@Tool(description = "查询指定城市的天气信息")
public String getWeather(String city) { ... }
}第二步,构建ChatClient。通过ChatClient.builder()构建,绑定ChatModel和工具:
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultSystem("你是一个助手,可以查询天气")
.defaultTools(weatherTool)
.build();第三步,发起调用。调用chatClient.prompt().user("北京今天天气怎么样").call(),框架自动完成LLM→工具选择→执行→回传→LLM总结的完整循环。
关键机制:Spring AI自动把@Tool注解的方法转化为Function Definition发给模型,模型决定调用哪个工具后框架自动反序列化参数、执行方法、把结果拼回消息流。开发者只需要写工具实现和描述,不需要手动处理工具调用的协议细节。
注意事项:工具描述要精准,直接影响模型选择是否正确;复杂工具建议用@ToolParam注解详细描述参数含义;生产环境要加工具执行超时和异常处理。
【面试题】10. 分布式系统中如何保证接口的幂等性?列举至少3种方案并说明适用场景。
【标准答案】 幂等性就是同一操作执行一次和多次效果相同。分布式场景下网络抖动、重试机制、消息重复消费都可能导致重复请求,必须保证幂等。
方案一:唯一Token机制。请求前先向服务端申请一个唯一token(存Redis),提交时带上token,服务端处理时删除token,删除成功才执行业务。利用Redis的DEL命令原子性保证token只能消费一次。适用于表单提交、支付下单等写操作。
方案二:数据库唯一索引。利用业务唯一标识建唯一索引,重复插入会抛DuplicateKeyException,捕获后按幂等处理。适用于创建订单、注册等insert场景,简单粗暴有效。
方案三:乐观锁/版本号。UPDATE时带版本号条件 WHERE id=? AND version=?,版本号不匹配则更新失败。适用于更新操作,如修改账户余额、更新配置。
方案四:状态机控制。业务流程有明确状态流转,只允许特定状态转换,重复请求遇到非法状态转换直接返回。适用于订单流程(待支付→已支付→已发货),重复支付请求在已支付状态下直接返回成功。
实际项目中通常组合使用:Token防重复提交做第一道防线,唯一索引/乐观锁做数据库层兜底,状态机做业务层语义保障。三个层次保证幂等性基本万无一失。