国投智能 Java 面经:笔试 + 技术面复盘,最后通过了,厦门拿到 1.4w

这篇记录一下我参加国投智能 Java 岗位笔试和面试的真实复盘。
最后结果是通过了,地点在厦门,薪资给到了 1.4w。这篇不写成鸡血文,主要把笔试题、面试题、答题思路和我觉得真正有用的准备方式整理出来。

🦅个人主页
🐼GitHub主页


先说结论:这次面试给我的最大感受

国投智能这次给我的感觉不是那种一上来就疯狂追分布式高并发的面试,而是比较看重 Java 基础、数据库、Spring、微服务、消息队列和项目表达。

笔试偏基础,但基础里有不少容易答错的细节题;技术面会往项目和实际使用场景上延伸,不是只问你“背没背过”。

这次最后能过,我觉得主要是三点:

  • Java 基础没有只背结论,而是能用代码解释
  • 数据库、AOP、Kafka、线程池这些问题能说出实际项目里的处理路径
  • AI 应用和 LangChain4j 这块能说出完整模块,不只是“调过大模型接口”

一张表先看懂:这次整体流程和题目分布

阶段 主要内容 难度感受 我觉得考察重点
笔试 Java 基础、语法细节、代码题、单例、接口抽象类 中等偏基础 基础是否扎实,细节是否容易混
技术面 数据库优化、Spring AOP、Spring Cloud、Kafka、线程池、AI 应用 中等偏项目 能不能把原理和项目结合起来
结果 最后通过,厦门 1.4w 结果还可以 对普通 Java 开发来说有参考价值

笔试题复盘:Java 基础真的不能轻视

笔试里让我印象比较深的是,它不是只考一个特别难的算法,而是用一组基础题看你 Java 细节是不是稳。

1. Java 合法标识符

常见问法:

  • Java 变量名能不能数字开头?
  • $_ 能不能用?
  • 关键字能不能作为变量名?
  • Java 标识符区分大小写吗?

我的答法可以压缩成一句:

Java 标识符可以由字母、数字、下划线 _、美元符号 $ 组成,但不能以数字开头,不能是关键字,区分大小写。实际开发中虽然 $、中文这类也可能合法,但不推荐,还是要按 Java 命名规范写。

2. i++++i

这个题看起来很基础,但笔试里特别容易因为紧张写错。

int i = 1;
int a = i++;
int b = ++i;

System.out.println(a); // 1
System.out.println(b); // 3
System.out.println(i); // 3

我的理解:

  • i++:先用旧值,再自增
  • ++i:先自增,再用新值
  • 单独一行执行时结果一样
  • 放在表达式里才体现区别

3. try catch finally 执行顺序

这个题核心是:

  • 一定先执行 try
  • 有异常并且匹配时执行 catch
  • 只要程序没有被强制终止,通常都会执行 finally
  • finally 里不要写 return,否则可能覆盖前面的返回值或异常

典型代码:

public int test() {
    try {
        return 1;
    } catch (Exception e) {
        return 2;
    } finally {
        System.out.println("finally");
    }
}

这个会先准备返回 1,但真正返回前会先执行 finally

4. equals==

这个题我建议一定用“基本类型 + 引用类型”分开答。

对比点 == equals
基本类型 比较值 不能直接调用
引用类型 比较地址 默认也是比较地址,但很多类会重写
String 比较引用地址 比较字符串内容
Integer 有缓存陷阱 比较包装后的值

比如:

String a = new String("java");
String b = new String("java");

System.out.println(a == b);      // false
System.out.println(a.equals(b)); // true

5. 为什么要重写 equals

这个题不能只说“为了比较内容”,最好再补一句集合场景。

如果一个对象要放进 HashSet、作为 HashMap 的 key,或者业务上需要按某些字段判断对象是否相等,就应该重写 equals,并且通常要一起重写 hashCode

public class User {
    private Long id;
    private String username;

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof User other)) {
            return false;
        }
        return Objects.equals(this.id, other.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
}

编程题:不使用 split,把每个单词倒序,单词顺序不变

这个题我觉得很适合作为笔试里的代码题,因为它不算难,但能看出你字符串处理和边界处理是否细。

需求大概是:

  • 输入:hello world java
  • 输出:olleh dlrow avaj
  • 单词顺序不变
  • 每个单词内部倒序
  • 不使用 split

我的写法:

public class ReverseWordsWithoutSplit {

    public static String reverseEachWord(String text) {
        if (text == null || text.isEmpty()) {
            return text;
        }

        StringBuilder result = new StringBuilder();
        StringBuilder word = new StringBuilder();

        for (int i = 0; i < text.length(); i++) {
            char ch = text.charAt(i);
            if (ch == ' ') {
                appendReverseWord(result, word);
                result.append(ch);
            } else {
                word.append(ch);
            }
        }

        appendReverseWord(result, word);
        return result.toString();
    }

    private static void appendReverseWord(StringBuilder result, StringBuilder word) {
        for (int i = word.length() - 1; i >= 0; i--) {
            result.append(word.charAt(i));
        }
        word.setLength(0);
    }

    public static void main(String[] args) {
        System.out.println(reverseEachWord("hello world java"));
    }
}

这个题面试时可以顺手说一下边界:

  • 空字符串
  • 多个空格
  • 前后空格
  • 单个单词
  • null

单例模式:双重检查锁为什么要加 volatile

这个也是 Java 基础里很经典的题。

public class Singleton {

    private static volatile Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

这里 volatile 主要解决两个问题:

  • 保证可见性
  • 禁止指令重排序

对象创建不是一个原子动作,可以粗略理解成:

  1. 分配内存
  2. 初始化对象
  3. 把引用赋值给变量

如果没有 volatile,可能出现引用已经不为 null,但对象还没初始化完成的情况。

抽象类和接口怎么答更稳

我觉得这个题不要只背“接口多实现,抽象类单继承”。更好的答法是从设计语义讲。

对比点 抽象类 接口
语义 是什么 能做什么
继承限制 Java 单继承 可以实现多个接口
成员变量 可以有实例变量 通常是常量
方法 可以有普通方法和抽象方法 可以有抽象方法、默认方法、静态方法
场景 抽公共父类模板 定义能力规范

面试表达:

抽象类更强调一类对象的共同基础,比如 AbstractOrderProcessor;接口更强调一种能力,比如 PayableRunnableSerializable。如果我要抽公共状态和模板流程,会用抽象类;如果只是定义能力契约,会用接口。

Integer 的 127 和 128 判断题

这个题非常经典。

Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true

Integer c = 128;
Integer d = 128;
System.out.println(c == d); // false

原因是 Integer 默认缓存 -128127

所以:

  • 127 命中缓存,两个引用指向同一个对象
  • 128 超出默认缓存范围,会创建不同对象
  • 比较包装类型值时,优先用 equals

技术面扩展题:不是只背八股,要能说项目路径

技术面里更偏扩展,会从数据库、Spring、微服务、Kafka、线程池、AI 应用这些方向看你是不是只会背结论。

我把重点整理成下面这张表。

主题 我会怎么答
红黑树 / B 树 / B+ 树 红黑树适合内存有序结构,B+ 树适合数据库页式索引和范围查询
数据库优化 先慢 SQL 和 EXPLAIN 定位,再从 SQL、索引、表结构、缓存和架构层优化
Spring AOP 本质是运行期动态代理,外部调用进入代理对象,再走拦截器链
Spring Cloud 解决注册发现、配置、网关、服务调用、熔断、消息集成等微服务治理问题
AI / LangChain4j 不只是调模型,还包括 Prompt、RAG、记忆、工具调用、结构化输出、评测和兜底
Kafka 高吞吐 顺序写、批量处理、Page Cache、零拷贝、分区并行、消费者拉取
线程池参数 先区分 CPU 密集还是 IO 密集,再结合下游容量、队列、压测和监控调整

数据库优化怎么答,才不像只会说“加索引”

数据库优化我建议按这个路径答:

  1. 先定位:慢 SQL 日志、监控、EXPLAIN
  2. 看现象:扫描行数、索引命中、回表、filesort、临时表、锁等待
  3. 再处理:SQL 改写、联合索引、覆盖索引、分页优化、缓存、冷热分离
  4. 最后验证:执行计划、耗时、扫描行数、接口 RT、数据库 CPU

比如订单列表查询:

SELECT id, order_no, amount, created_at
FROM orders
WHERE merchant_id = 10001
  AND status = 'PAID'
  AND created_at >= '2026-06-01'
  AND created_at < '2026-07-01'
ORDER BY created_at DESC
LIMIT 20;

比较合适的联合索引:

CREATE INDEX idx_orders_merchant_status_created
ON orders (merchant_id, status, created_at);

面试表达:

我不会一上来就说加索引,而是先看慢 SQL 和 EXPLAIN。如果发现这个订单列表查询是按商户、状态、时间范围过滤,并且按时间排序,那我会考虑建 (merchant_id, status, created_at) 的联合索引,同时避免 SELECT *,减少回表和排序成本。

Spring AOP 底层原理

我会这样答:

Spring AOP 的本质是运行期动态代理。Spring 容器创建 Bean 时,如果发现某个 Bean 的方法匹配切点规则,就会创建代理对象。外部调用 Bean 方法时,实际先进入代理对象,再通过拦截器链执行事务、日志、权限、监控等增强逻辑,最后调用目标方法。

可以按这个流程理解:

  1. Spring 扫描 Bean
  2. BeanPostProcessor 判断是否需要代理
  3. 创建 JDK 动态代理或 CGLIB 代理
  4. 外部调用进入代理对象
  5. 代理对象执行拦截器链
  6. 最后调用目标方法

容易被追问的是 @Transactional 为什么会失效。

常见原因:

  • 同一个类内部 this.xxx() 自调用
  • 方法不是 public
  • 对象不是 Spring 容器管理的 Bean
  • 异常被 catch 掉没有抛出
  • 数据库本身不支持事务

Spring Cloud 怎么答

Spring Cloud 不要背成一堆组件名,最好说它解决了哪些问题。

问题 常见组件
服务地址怎么发现 Nacos / Eureka / Consul
统一入口怎么做 Spring Cloud Gateway
服务之间怎么调用 OpenFeign
多实例怎么负载均衡 LoadBalancer
下游挂了怎么办 CircuitBreaker / Sentinel / Resilience4j
配置怎么统一管理 Config / Nacos Config
配置变更怎么通知 Bus / Nacos 动态配置
订单成功后怎么通知其他系统 Stream + Kafka

面试表达:

我理解 Spring Cloud 不是单个框架,而是一套微服务治理工具。比如网关解决统一入口,注册中心解决服务地址动态变化,OpenFeign 解决服务间调用,熔断降级解决下游故障保护,配置中心解决多服务配置统一管理,消息组件解决异步解耦。

Kafka 为什么吞吐量高

Kafka 这个题经常被问,别只说“因为它快”。

我会从这几个点答:

  • 顺序写磁盘,避免大量随机 IO
  • 批量发送和批量拉取,减少网络请求次数
  • 利用 Page Cache,让操作系统缓存热点数据
  • 零拷贝,减少用户态和内核态之间的数据复制
  • 分区并行,一个 topic 可以拆成多个 partition
  • 消费者拉取模型,消费节奏由消费者自己控制

项目表达可以这样说:

在项目里如果有日志采集、订单状态流转、异步通知、削峰填谷,我会优先考虑 Kafka。它不只是因为单机性能高,更重要的是可以通过 partition 做并行,通过 consumer group 做水平扩展。

线程池参数怎么配置

线程池不要直接背一个固定公式。

我会先判断任务类型:

  • CPU 密集:线程数接近 CPU 核心数
  • IO 密集:线程数可以适当大一些
  • 混合任务:要结合压测和监控调

企业里更稳的做法:

@Bean
public ThreadPoolTaskExecutor bizExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(16);
    executor.setMaxPoolSize(32);
    executor.setQueueCapacity(1000);
    executor.setThreadNamePrefix("biz-executor-");
    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    executor.initialize();
    return executor;
}

面试时我会补一句:

线程池参数不是拍脑袋定的,要结合任务类型、下游承载能力、队列长度、拒绝策略、压测结果和线上监控一起调。如果下游数据库或接口只能承受 100 QPS,上游线程池开再大也只是把压力打爆。

AI 应用和 LangChain4j 怎么答

这一块如果只说“我调过大模型接口”,基本不够。

更好的表达是:

AI 应用不是只调一个模型接口,外面还要有 Prompt 模板、RAG 检索、向量库、聊天记忆、工具调用、结构化输出、内容安全、日志观测、效果评测和异常兜底。

LangChain4j 里常见组件可以这样说:

组件 作用
ChatModel 对接大模型
AI Services 用 Java 接口声明 AI 能力
ChatMemory 多轮对话记忆
EmbeddingModel 文本向量化
EmbeddingStore 存储向量
ContentRetriever 检索相关知识
Tools 让模型调用业务方法
Structured Output 把模型结果转成 Java 对象
Guardrails / Moderation 做输入输出安全治理

项目表达可以这样说:

如果让我设计一个企业知识库问答,我不会让业务直接调模型,而是会先做文档入库、切块、向量化、检索召回、重排、Prompt 组装、模型回答、引用来源、日志审计和效果评测。这样 AI 能力才不是一个 demo,而是一个能上线维护的系统。

我觉得这次能通过的关键

这次能通过,最关键的不是我把所有八股都背得很满,而是答题时尽量做到三层:

  1. 先给结论
  2. 再讲原理
  3. 最后落到项目怎么用

比如问数据库优化,不要只说加索引,要说定位、分析、优化、验证。

比如问 AOP,不要只说动态代理,要说代理对象、拦截器链、事务为什么会失效。

比如问 AI,不要只说调 API,要说 Prompt、RAG、记忆、工具调用、评测和兜底。

这套表达方式,对国投智能这种偏 Java 后端综合能力的面试,还是比较有用的。

厦门 1.4w 这个结果怎么看

最后通过并且在厦门拿到 1.4w,我个人觉得这个结果对普通 Java 开发来说还算可以。

如果是在上海、杭州、深圳,可能这个数字不算特别高;但放到厦门,结合岗位稳定性、通勤、生活成本和公司背景,整体就有参考价值了。

当然,薪资不是唯一标准。我更看重的是:

  • 岗位是不是能继续写 Java 后端
  • 项目是不是有真实业务复杂度
  • 技术栈是不是还有成长空间
  • 团队是不是愿意让你接触核心模块
  • 后面跳槽时这段经历能不能讲得出来

给后面准备国投智能 Java 岗的建议

如果你后面也准备类似岗位,我建议不要只刷算法,重点可以放在这些方向:

  • Java 基础:equalshashCodeInteger 缓存、try finally、单例、接口抽象类
  • 集合和数据结构:红黑树、B+ 树、HashMap、TreeMap
  • 数据库:慢 SQL、索引、EXPLAIN、深分页、联合索引、事务锁
  • Spring:AOP、事务失效、Bean 生命周期、自动装配
  • 微服务:注册中心、网关、Feign、熔断、配置中心、消息
  • Kafka:高吞吐原理、partition、consumer group、顺序写、零拷贝
  • 线程池:参数配置、拒绝策略、队列、监控、下游容量
  • AI 应用:LangChain4j、RAG、记忆、工具调用、结构化输出

结语

这次国投智能面试给我的感觉是:基础题不会很偏,但会看你是不是真的理解;扩展题不会只问名词,而是想听你怎么落到项目里。

最后能通过,并在厦门拿到 1.4w,对我来说算是一次比较正向的反馈。也说明普通 Java 开发准备面试时,不一定要一味追最新概念,把基础、项目表达和工程化思路讲清楚,机会还是有的。

如果你也在准备 Java 面试,我建议把每个问题都练成一句话结论、一段原理解释、一个项目例子,这比单纯背答案更稳。

更多推荐