
又到一年校招季后台不少同学在问京东Java开发岗的笔试题怎么准备。我翻出2019年那套京东校招Java开发工程师笔试题重新做了一遍感慨挺多的。这套题在当年Java校招圈里流传很广不是因为它偏难怪恰恰相反它胜在覆盖面全、出题思路正——选择题、编程题、简答题都有很多题看着基础实际一上手就露怯。我带过好几个准备秋招的同学刷这套题发现一个共同规律基础扎实的人基本能拿七成以上分数靠背八股文赶进度的往往在编程题上翻车。这篇文章我打算把这套题完整复盘一遍包括每类题型的考察逻辑、背后的Java核心知识点、手写代码的注意事项再结合我实际刷题和带人过程中的踩坑经验把那些容易被忽略的细节摊开讲。不管你是正在准备校招的应届生还是工作两三年想补基础的Java开发这篇内容都能帮你理清思路。1. 京东2019校招Java笔试的全貌与考察思路1.1 笔试题型结构与时间分配先说大家最关心的题型结构。京东这套校招笔试题整体分为三大块第一部分是单选题大概20到25道覆盖Java语法基础、集合框架、JVM、并发编程、异常处理这些常规考点每道题分值不高但架不住量大第二部分是编程题通常是两道一道偏算法排序、字符串处理、动态规划这类一道偏设计也可能是数据库SQL或简单的系统设计第三部分是不定项选择或简答题用来考察候选人的综合理解和表达能力。整套笔试时间一般在90分钟到120分钟之间。这个时间安排本身就很有讲究——单选和不定项看似简单但很多题目会埋着“小陷阱”比如多选漏选、HashMap在并发场景下的死循环、String和StringBuilder在拼接时的性能差异这些地方特别容易让人纠结。我建议的分配策略是选择题部分控制在35到40分钟内完成遇到卡壳的题先标记跳过去不要在一道题上耗超过两分钟编程题留足45到50分钟因为这类题不只是写出来还要保证边界条件处理正确、代码能通过测试用例。我当时带人刷题时反复强调过一个点笔试不是高考不需要追求满分但要保证“会做的题绝对不丢分”。选择题的容错率其实很低一不留神就是2分、3分地扣编程题如果只写个大概思路、没有跑通测试用例基本也是零分。所以时间分配的核心原则是把精力优先投给确定性最高的题目。1.2 考点覆盖从基础语法到工程能力京东这套题的考点分布很有意思我把它拆开分析了一下Java基础语法占比最高。包括面向对象三大特性、重载与重写的区别、String类特性、包装类的比较、异常层级结构、枚举类型的使用等。这些属于“送分题”但前提是真正理解不是背概念。集合框架必考。HashMap的底层实现、ArrayList和LinkedList的区别、HashSet的去重原理、迭代器遍历时能否删除元素等。这类题考察的是“用过的原理”有真实项目经验的人答起来会轻松很多。JVM与内存模型这是拉开差距的地方。JVM内存区域划分、垃圾回收算法、类加载过程、OOM的典型场景等会以选择题或简答题形式出现。多线程与并发考察synchronized和Lock的区别、volatile关键字的语义、线程池参数含义等。京东这类互联网公司非常看重并发基础。算法与数据结构笔试中的编程题环节排序算法是保底题字符串处理和时间复杂度分析也常出现。数据库与框架占比不大但一定会有。SQL语法、索引原理、Spring IoC/AOP这些基本概念会穿插在选择题中。从这套考点分布能看出大厂的出题逻辑他们不指望校招生一进来就能上手项目但希望你有扎实的计算机基础、能独立解决算法问题、对Java常用机制有真实理解。“基础不牢地动山摇”这句话在校招笔试里体现得淋漓尽致。2. 高频基础题精讲语法、集合与面向对象2.1 面向对象三要素封装、继承、多态怎么考京东这套题里面向对象部分至少有3到4道题。最常见的考法是给你一段代码问你输出什么或者问某个设计违反了哪个原则。核心考点就是封装、继承、多态三个词背后真正的含义。封装不是“把变量设为private”这么简单它考察的是你能否理解“信息隐藏”的设计价值。笔试常见的变形题是子类继承父类后能不能重写父类的private方法答案是不能因为private方法对子类不可见子类中定义的同名方法是一个新方法不是重写。这个点经常和“重写与重载的区别”一起考。继承这个考点最容易踩坑的是初始化顺序。父类静态代码块、子类静态代码块、父类普通代码块、父类构造器、子类普通代码块、子类构造器的执行顺序笔试必考。我总结了一个口诀静态先行父先子后代码块在构造器前。当时带人刷题时有个同学在这里连续错了两道题都是因为把“普通代码块”和“构造器”的顺序搞混了。多态是重头戏。考点集中在父类引用指向子类对象时调用的是子类重写后的方法但访问属性时用的是父类的属性。记住一个原则方法看子类属性看父类。可以看下面这个经典例子class Parent { String name parent; void print() { System.out.println(name); } } class Child extends Parent { String name child; Override void print() { System.out.println(name); } } public class Test { public static void main(String[] args) { Parent p new Child(); System.out.println(p.name); // 输出 parent p.print(); // 输出 child } }这个例子我在面试辅导里讲了不下二十遍。第一行输出parent是因为属性访问不参与多态编译期就确定了第二行输出child是因为方法调用参与多态运行时动态绑定。搞懂这一个例子多态相关的选择题基本都能拿下。2.2 集合框架的底层逻辑HashMap、ArrayList必考集合框架在京东这套题中的占比相当高几乎每隔两道题就能遇到一个集合相关的。我重点说两个必考对象HashMap和ArrayList。HashMap是Java面试当之无愧的“题王”。2019年这套题里有一道问HashMap底层数据结构的经典题JDK 1.8之后底层是数组加链表加红黑树当链表长度超过8且数组长度超过64时链表会转为红黑树当红黑树节点数小于6时退化为链表。很多同学背了“8”和“64”这两个数字但不理解为什么。其实这是泊松分布算出来的一个平衡点——在理想哈希函数下链表长度达到8的概率极低大约是千万分之六所以用8作为树化阈值既保证了查询效率又避免了频繁的树化和退化开销。HashMap还有一个必考考点扩容机制。默认初始容量16负载因子0.75当元素个数超过16乘以0.75等于12时触发扩容扩容为原来的两倍。这里经常有同学问“为什么负载因子是0.75”我的理解是这是空间和时间的一个折中。负载因子太高比如1.0链表会很长查询效率下降负载因子太低比如0.5空间浪费严重频繁扩容也会影响性能。0.75是JDK开发者基于大量测试得出的经验值。ArrayList则考扩容和遍历两个点。默认容量是10扩容时调用grow方法新容量是旧容量的1.5倍也就是oldCapacity加上oldCapacity右移一位。循环遍历时删除元素如果用普通for循环配合remove很容易出现元素漏删或越界问题。正确做法是用Iterator的remove方法或者用Java 8的removeIf。还有一道常考的选择题是HashMap和Hashtable的区别。Hashtable是线程安全的但它的线程安全是通过给整个方法加synchronized实现的并发效率很低现在已经基本被弃用。ConcurrentHashMap才是并发场景的推荐选择JDK 1.8之后它摒弃了分段锁改用CAS加synchronized只锁桶首节点粒度更细性能更好。2.3 枚举类型与常用类的细节题枚举类型在京东笔试里也出现过考的是枚举在单例模式中的应用和枚举的基本用法。Effective Java里极力推荐用枚举实现单例因为枚举的实例创建是线程安全的并且可以天然防止反射攻击和序列化破坏。这个知识点很多初学者不知道一旦出现在选择题里答对的概率很低。枚举本质上是一个继承了Enum类的final类所以它可以定义成员变量、构造方法、抽象方法甚至可以 implements 接口。笔试里常见的一个坑是枚举的构造方法默认是私有的外部不能手动创建枚举实例这是枚举能实现单例的根基。常用类那边String是主角。String不可变是因为内部用final修饰的char数组JDK 9之后是byte数组存储并且String类本身是final的不允许被继承。这样设计的好处是字符串常量池可以安全地复用实例hashCode可以直接缓存线程安全也天然得到保证。但String不可变也带来了拼接性能问题所以频繁修改字符串的场景要用StringBuilder非线程安全或StringBuffer线程安全方法加了synchronized。还有一个高频考点是equals和hashCode的关系。两个对象equals相等hashCode必须相等两个对象hashCode相等equals不一定相等。这意味着重写equals时必须重写hashCode否则在HashSet或HashMap中会出问题。我记得有一道选择题给的选项是“两个对象hashCode相同则equals一定相同”这个说法是错误的属于典型的反例考法。3. 排序算法与手写代码笔试里的八股文3.1 冒泡排序的Java实现与优化京东2019年笔试的编程题里排序算法是保底选择冒泡排序和快速排序都出现过。冒泡排序虽然在实际项目中用得不多但作为笔试入门题它能有效筛选候选人的基础代码能力。基础版冒泡排序的思路很简单每一轮从头开始比较相邻元素如果顺序错误就交换经过n-1轮后最大元素会像气泡一样“冒”到最后面。时间复杂度是O(n²)空间复杂度是O(1)是稳定排序。public static void bubbleSort(int[] arr) { if (arr null || arr.length 2) { return; } int n arr.length; for (int i 0; i n - 1; i) { boolean swapped false; for (int j 0; j n - 1 - i; j) { if (arr[j] arr[j 1]) { int temp arr[j]; arr[j] arr[j 1]; arr[j 1] temp; swapped true; } } // 如果某一轮没有发生交换说明数组已经有序提前退出 if (!swapped) { break; } } }写冒泡排序时最容易犯的错误有两个一个是内层循环的边界条件写错写成j n - 1而不是n - 1 - i会导致多余的比较虽然不影响正确性但会浪费时间另一个是忘记设置交换标志无法利用“数组已经有序”这个特性提前退出。我在面试别人的时候遇到很多候选人能写出基础版但能主动加上swapped标志做优化的不到三成这个细节其实很加分。3.2 快速排序的Java实现与边界问题快速排序比冒泡排序更常出现在大厂的笔试编程题中京东也不例外。快排的核心思想是分治从数组中选一个基准值pivot将小于基准值的元素放到左边大于基准值的放到右边然后对左右两个子区间递归排序。平均时间复杂度O(n log n)最坏情况O(n²)空间复杂度O(log n)是不稳定排序。public static void quickSort(int[] arr, int left, int right) { if (left right) { return; } int pivot partition(arr, left, right); quickSort(arr, left, pivot - 1); quickSort(arr, pivot 1, right); } private static int partition(int[] arr, int left, int right) { int pivot arr[left]; int i left; int j right; while (i j) { // 先从右往左找比基准值小的元素 while (i j arr[j] pivot) { j--; } // 再从左往右找比基准值大的元素 while (i j arr[i] pivot) { i; } if (i j) { int temp arr[i]; arr[i] arr[j]; arr[j] temp; } } // 将基准值放到正确位置 arr[left] arr[i]; arr[i] pivot; return i; }这段代码看起来简单但边界问题非常多。第一个坑是递归终止条件left right就返回而不是left right因为可能出现left比right大的情况。第二个坑是内层while循环必须加上i j的判断否则会导致下标越界。第三个坑是两个while循环的方向不能搞反必须先移动j再移动i这样才能保证最后基准值交换的位置是正确的。第四个坑是等于基准值的元素怎么处理我的写法是arr[j] pivot时继续移动这样等于基准值的元素会被分到右侧实现是可行的但不够优雅也可以根据具体需求调整。注意快排在笔试中经常会追问“如何优化最坏情况”。最直接的方案是随机选择基准值或者采用三数取中法取left、mid、right三个位置的中位数作为基准值可以有效规避近乎有序数组导致的O(n²)退化。3.3 Lambda表达式与Comparator的写法京东这套题虽然出自2019年但Java 8的Lambda表达式和Stream API已经在笔试题中占了一席之地。我记得有一道选择题是问Comparator的正确用法选项里给了好几种写法考察点其实就是Comparator.comparing的链式调用。热词里有一个很具体的需求用Comparator.comparing将某元素值放第一个。这种需求在实际开发中经常遇到比如排序时想让某个状态值优先排在前面。实现方式其实很巧妙list.sort(Comparator .comparingInt(item - item.getStatus() 1 ? 0 : 1) .thenComparing(item - item.getCreateTime()));这段代码的逻辑是先按照状态字段排序状态等于1的元素排在最前其他元素排在其后如果状态相同再按照创建时间排序。关键在于comparingInt接收一个ToIntFunction我们在这个函数里做了一个三元表达式映射把业务规则转换为排序键。这个套路在笔试简答题或设计题里很吃香因为Java 8的函数式写法不仅简洁而且语义清晰。Lambda表达式在排序中的应用本质上是把“比较逻辑”作为参数传递这就是行为参数化。很多同学刷题时只关注算法本身忽略了Java 8新特性在算法题中的使用其实在笔试时间紧张的情况下用Lambda配合流式操作能极大缩短代码量减少出错概率。比如快速排序的partition也可以用Stream的filter实现虽然效率不高但可读性极强这在笔试的“伪代码”环节很管用。4. JVM与异常从OutOfMemoryError到数组越界4.1 OOM的典型场景与排查思路热词里出现了“java: outofmemoryerror: insufficient memory”这个报错在实际开发和笔试中都很有代表性。OOMOutOfMemoryError是JVM运行时抛出的错误最常见的两种场景是堆内存不足和创建线程时本地内存不足。堆内存不足的报错信息通常是“Java heap space”。当堆中无法再分配新对象并且垃圾回收也无法释放足够空间时就会抛出这个异常。常见原因有内存泄漏对象被错误地持有引用无法回收、一次性加载过多数据比如把几百万条记录一次性加载到List里、设置了过小的最大堆内存-Xmx参数配置不当。创建线程时OOM的报错信息通常是“unable to create new native thread”。这个场景在笔试选择题里经常被拿来混淆视听。线程的内存在JVM规范中不属于堆而是属于线程栈由操作系统分配。当系统总线程数达到上限时即使堆内存还有很多也会抛出这个错误。解决方案是减少线程数量或减小线程栈大小-Xss参数。如果是笔试简答题标准答题思路应该按照“定位—分析—解决”三个步骤展开先用jps找到进程ID然后用jmap导出堆转储文件用MAT或VisualVM分析对象占用情况找出可疑的大对象或泄漏点最后通过修改代码或调整参数解决。# 查看JVM进程 jps -l # 查看堆内存使用情况 jmap -heap pid # 导出堆转储文件 jmap -dump:formatb,fileheap.bin pid # 查看线程状况 jstack pid4.2 数组越界异常与防御式编程ArrayIndexOutOfBoundsException是Java开发中最常见的运行时异常之一也是京东笔试选择题里的“常客”。它的触发条件非常简单访问了数组或List中不存在的下标。笔试里最常见的考法有两种。第一种是给你一段for循环代码判断循环结束后某个变量的值或是否会抛出异常。比如int[] arr new int[]{1, 2, 3}; for (int i 0; i arr.length; i) { System.out.println(arr[i]); }这段代码在i等于3时会抛出ArrayIndexOutOfBoundsException因为数组下标范围是0到length-1。很多粗心的同学会把i arr.length写成等于这就是典型的边界错误。第二种考法结合了多线程或集合的fail-fast机制。比如在循环遍历List时另一个线程删除了一个元素导致modCount变化会在迭代器next()时抛出ConcurrentModificationException而不是ArrayIndexOutOfBoundsException。这两个异常经常被放在同一道多选题的选项里考察你是否能区分。防御式编程的思路很简单所有通过下标访问的操作都要先判断边界条件。我写代码时的习惯是不确定下标是否合法时先判断索引范围再取值或者用增强for循环、Stream API等更安全的方式替代下标访问。这不仅是笔试的得分点更是实际生产中降低线上故障概率的关键习惯。4.3 环境配置JDK版本与编译警告热词里还有一个很有代表性的报错“java: 警告: 源发行版 17 需要目标发行版 17”。这个警告在笔试本身不考但在准备笔试时大量同学遇到过——本地运行环境JDK版本和项目配置的编译目标版本不一致。比如你的电脑装的是JDK 17但项目pom.xml中maven.compiler.source和maven.compiler.target配置的是1.8就会出现这个警告。IDE的编译器版本和项目字节码版本不统一时轻则报警告重则直接编译失败或运行时报UnsupportedClassVersionError。解决方案分两步。第一步确认当前JDK版本java -version第二步在Maven的pom.xml中显式声明编译版本或者在IDE里设置Project Structure和Java Compiler的版本保持一致。我遇到过一种情况多个同学组队做项目每个人的JDK版本不一样代码合并后总有人编译报错。后来统一在pom.xml里用properties锁定Java版本问题才彻底解决。这是工程化协作的基础素养面试时如果能体现出这种意识是很加分的。5. 工程化考点Spring Boot、接口安全与开发工具链5.1 Spring Boot API Key安全对接设计京东这类大厂笔试的选择题部分偶尔会出现Spring相关的基础概念。但真正有意思的是简答题或设计题里会考你如何设计一个安全的API接口。热词里有一条“java springboot apikey 安全对接”这个问题我在实际项目中真的踩过坑。简单的API Key方案是服务端为每个调用方生成一个唯一的Key调用方在请求时带上Key服务端校验Key是否存在、是否有效。但这个方案有两个明显的安全漏洞一是Key在传输过程中可能被截获二是即使加了HTTPS中间人依然可能重放请求。更健壮的方案是“API Key 签名 时间戳”三位一体。签名机制的核心是将请求参数按字典序排序拼接成字符串加上密钥用HMAC-SHA256算法生成签名。服务端用同样的算法重新计算签名并比对如果一致说明请求参数没有被篡改调用方身份合法。时间戳的作用是防重放服务端只接受当前时间前后5分钟内的请求超过时间窗口的直接拒绝。如果笔试让你写一个接口签名设计的伪代码标准答案一般是这样// 服务端生成签名伪代码 String sortedParams sortAndConcat(params); // 参数排序拼接 String sign hmacSha256(sortedParams, secretKey);从招聘角度看这类开放题并不指望你写出完美方案而是考察你有没有工程安全意识、有没有真实对接经验、能不能从“能用”升级到“好用、安全”。如果你能主动提到“签名中只包含非空参数”、“时间戳需要校验偏移量”、“密钥不能硬编码在代码里”等细节面试官会认为你确实有实战经验。5.2 Lombok与编译器的兼容性问题Lombok这个工具在校招笔试中不会直接考但它在实际Java开发中几乎绕不开。热词里那条“you arent using a compiler supported by lombok, so lombok will not work”是最让我头疼的报错之一因为它往往出现在最不恰当的时机——项目正在打包部署时。这个报错的原因非常简单Lombok依赖注解处理器在编译阶段修改AST抽象语法树从而自动生成getter、setter、构造器等代码。如果Lombok的版本不支持当前使用的JDK版本注解处理器就无法工作。比如Lombok 1.18.20之前的版本对JDK 16以上的支持就不好会出现这个报错。解决方案有两种。最直接的方案是升级Lombok版本到最新稳定版比如1.18.30版本对应关系可以在Lombok官网的changelog里查到。另一种方案是降低JDK版本但这不是长久之计——新项目应该主动拥抱新版本JDK而不是为了兼容老工具而停留在旧版本。我在带新人时遇到过这样一个case同事用JDK 17写代码Lombok版本还停留在1.18.22编出来的class文件里完全没有getter和setter运行时报NoSuchMethodError。排查了半天最后发现就是Lombok版本太老。升级到1.18.30后问题消失。这个经验侧面说明Java生态的“依赖地狱”问题无处不在尤其是工具库版本兼容性测试一定要纳入技术选型评估。5.3 从IDE到框架工程实践中的那些坑除了Lombok热词里还提到了不少工程化场景的问题比如VSCode运行Java报错乱码、drozer找不到Java、MapStruct映射处理器报错、人人框架和BladeX框架对比等。虽然这些不一定直接出现在京东2019年笔试中但它们共同指向一个能力真实工程环境下的问题排查能力。VSCode运行Java报错乱码大概率是控制台编码和系统编码不一致。Windows环境下默认编码是GBK而VSCode的终端默认可能是UTF-8两者不一致就会在控制台输出乱码。解决办法是在launch.json或settings.json中显式设置控制台编码或者在运行时指定编码参数。MapStruct报“internal error in the mapping processor”这类错误通常是Lombok和MapStruct注解处理的顺序冲突。MapStruct在编译时读取getter/setter生成映射代码但Lombok的getter/setter也是在编译期生成的如果MapStruct先于Lombok执行就找不到对应方法。解决办法是在Maven的annotationProcessorPaths中手动声明处理器的执行顺序。这些坑单独看都不难但它们共同说明了一个底层规律Java工程化的核心不只是“会写Java代码”还包括对构建工具、IDE、注解处理器、框架选型之间协作的理解。笔试也许不会直接考这些实操细节但会在简答题的“描述一次你印象最深的BUG排查经历”中体现出来。能讲清楚“报错现象—排查思路—根因定位—解决方案”这个过程比背一百道选择题都有用。6. 备考建议与实战心得6.1 校招笔试的时间分配与答题策略回到京东2019校招Java开发工程师笔试题本身我想聊聊备考方法论。第一阶段的建议是“按考点分块复习”不要上来就刷整套卷子。先把Java基础、集合、JVM、多线程、算法这几个模块分别过一遍知识点每过完一个模块就找对应的专项题目练习确保每个考点都建立了完整的知识框架。我见过太多同学一上来就刷整套题错题涉及的知识点东一个西一个复盘效率极低。第二阶段才是整套卷子的限时模拟。重点训练两件事一是时间分配选择题卡壳不要超过两分钟编程题至少留出45分钟二是答题节奏先做会做的、再做能推的、最后猜没把握的。笔试不是学术考试你的目标是在有限时间内拿到最高分数而不是把每道题都做出来。编程题的答题策略还有一个细节即使思路不完整也要把能写的代码写出来。很多人遇到不会的算法题直接放弃整道题零分。但实际上只要把类结构、方法签名写对把基本逻辑写出来哪怕最后结果是错的阅卷人也能看到你的思路给个几分过程分。校招笔试的竞争激烈到你无法放弃任何一分所以“能写多少写多少”是底线策略。6.2 值得反复练习的知识点清单结合京东这套题和近几年的Java校招趋势我整理了一份校招笔试高频考点清单按优先级排序第一优先级HashMap底层结构、扩容、树化、ArrayList扩容、遍历删除、String/StringBuilder/StringBuffer不可变、拼接性能、equals/hashCode关系、面向对象多态方法分派规则。第二优先级JVM内存区域、垃圾回收算法可达性分析、Minor GC/Full GC、类加载双亲委派、synchronized和volatile内存屏障、原子性、线程池参数核心线程数、阻塞队列、拒绝策略。第三优先级排序算法手写冒泡、快排、归并、链表反转、二分查找、字符串匹配、动态规划基础题爬楼梯、最长公共子序列。第四优先级Spring IoC/AOP概念、MySQL索引原理B树、聚簇索引、SQL编写联表查询、分组排序、HTTP协议基础状态码、请求方法。我建议你用表格的形式把“考点—常见变形—易错点—刷题量”四列整理成自己的复习表每掌握一个知识点就打一个勾。这种方式比盲目刷题高效得多它能让你清楚地看到自己的知识盲区在哪里。6.3 从笔试到技术面试的准备衔接最后聊一个容易被忽视的问题笔试和面试其实是连贯的。京东2019年这套笔试题里的很多选择题考点在面试环节会以“追问”的形式出现。比如笔试考了HashMap的底层结构面试就会追问“为什么链表长度超过8才树化”“红黑树和链表相比优势在哪里”笔试考了快速排序面试就会追问“如何优化最坏情况”“快排和归并排序有什么区别”。我的建议是每做完一道笔试题不要只看答案对不对要把这道题涉及的考点全部展开复习一遍假设自己是被面试官追问的那个人尝试把每个“为什么”都解释清楚。这种“以终为始”的复习方式能让你用一套题同时准备笔试和面试效率翻倍。以我自己的经验为例我带的一个同学笔试前把HashMap相关的所有知识从底层结构到并发问题到JDK各版本的变化都梳理成了一张思维导图。笔试时相关的选择题全对面试时被问到ConcurrentHashMap为什么比Hashtable并发度高他直接把JDK 1.7分段锁和JDK 1.8 CAS加synchronized的区别讲了一遍当场拿到了好评。这就是“把一道题吃透”的复利效应。京东这套2019年校招笔试题放到今天来看依然有很高的参考价值。这不是因为它有多难而是它的出题思路代表了大厂对Java校招生的核心期待扎实的基础、清晰的逻辑、严谨的边界意识。把这套题吃透你准备的就不只是一场笔试而是一整套Java工程能力的底层框架。后续我还会陆续整理其他大厂的校招笔试题复盘包括阿里、腾讯、美团这些Java岗的经典考法如果你正在备战校招可以持续关注。