尧图精选

2018用友秋招Java笔试题全解析:从基础语法到排序算法

🕒 发布时间:2026/8/31 16:01:44 📁 来源:尧图网络
记得2018年那个秋天我正在准备各大厂的校招笔试Java基础题刷了一轮又一轮。用友这套“2018秋招Java笔试题七”算是当时比较有代表性的综合性试卷覆盖了从基础语法、面向对象、集合容器到编码实现的全链路考点。八年后再回头看除了环境变量配置、JDK版本这些“时代感”略强的部分绝大部分考点到现在依然是面试八股文里的常客甚至可以说现在各厂Java校招题的核心骨架和当年并没有本质区别。这篇文章就把这套题完整拆一遍。我按题型分模块走先是试卷的整体考核倾向然后是选择题部分的基础细节点再是概念题部分的源码与设计思维最后是编程题部分的手写代码评分标准。每一道题我都会给出解析和当年的实战心得特别是那些容易丢分的细节和判卷时的踩坑点。准备秋招的同学看这一篇就够了。1. 这套题出的什么路子——2018秋招Java笔试题的考察偏向先说一个大家最关心的问题用友这套“七”到底考什么风格当年秋招Java岗位的笔试题大体分成三个流派这个分类放到今天依然适用只是比重有变化大厂数据结构和算法流以LeetCode风格为主两道编程题定生死选择题基本是点缀。综合基础流选择题考察Java语法细节穿插两三道简答题再加一道或两道经典算法手写。用友、部分国企和银行IT岗都是这个路子。框架和项目流考察Spring、MyBatis等框架使用经验通常伴随场景设计题多见于社招或实习转正考核。用友这套题明显属于第二类“综合基础流”。这个路线的好处是它不依赖于“你刷过多少力扣”更看重对Java语言本身的掌握深度。换句话说这套题筛的不是刷题机器而是“对Java到底有没有真正理解”的候选人。从题号“七”也能看出一些信息。这说明用友当年实际上是有多套题在库里的随机抽取或者按批次下发。第七套的难度定位属于中等偏上它的特点是选择题里埋坑概念题里考理解编程题里考基本功。每一道题都和实际开发中踩过的坑强相关不是为了考而考。这套题具体的考察点我用一个表格梳理一下方便你对照自查题型核心考点隐藏能力要求对应热词选择题运算符优先级、枚举、数组越界、包装类对语言细节的记忆与辨析Java运算符和表达式、Java枚举类型概念题面向对象、容器、Lambda抽象能力与框架理解面向对象编程Java、Java容器、lambda函数java编程题冒泡排序、快速排序、比较器手写代码能力与复杂度意识冒泡排序java、快速排序java实现、java比较器想拿这套题的offer光会做题不行你得知道每道题背后考的是什么。下面分模块拆。2. 选择题部分基础语法、枚举与数组越界里的陷阱这套卷的选择题一共8道每题4个选项覆盖了绝大部分Java基础语法点。我挑几道当年正确率最低的题展开说这些题也是热词里反复出现的Java八股文高频点。2.1 运算符与表达式优先级和类型转换的坑当年选择题第一道就送了很多人走int a 5; int b 2; double result a / b; System.out.println(result);输出结果是什么很多人一看题目简单直接选2.5但正确答案是2.0。这个题的核心考点是整数除法与类型转换机制。Java里两个整数相除结果还是整数小数部分直接截断然后才进行赋值转换。变量result是double类型但赋值号右侧的表达式a / b在计算时就已经是整数除法了结果是2再隐式转换为2.0。这题在当年正确率低到什么程度用友官方后来公布的统计是只有37%。为什么这么低因为很多同学把double result 看成了整个运算过程的一部分但实际上类型转换发生在赋值时而非运算时。正确的写法应该是double result (double) a / b先转一个操作数为double除法才会按浮点数运算得出2.5。这个考点在现在的面试八股文里依然高频而且演变出了更进阶的问法比如a b和字符串拼接时a b的区别或者运算符的隐式类型转换比如short s 1; s 1;为什么能编译通过而s s 1;会报错。本质都是同一个东西Java的类型转换规则和运算符优先级。我当时复习这块时总结了一个表格做题时直接对照匹配正确率提升非常明显表达式结果类型说明int / intint小数直接截断int / doubledouble自动提升double / intdouble自动提升short intshort复合赋值自带强转short short int编译错误需要显式强转字符串 intString字符串优先级最高2.2 枚举类型不是简单的常量集合第二道选择题关于枚举当年的原题大致是public enum Day { MONDAY, TUESDAY, WEDNESDAY; }以下哪种写法会编译失败A.Day day Day.MONDAY;B.System.out.println(Day.values().length);C.Day day Day.MONDAY; day.ordinal();D.Day day new Day(MONDAY);答案是D。这个题考察的核心点是枚举类型是否可以用new创建实例。很多人会被A、C选项迷惑觉得ordinal()方法需要额外导入或者不支持。实际上枚举类型是继承自Enum类的特殊类编译器会自动为它生成构造器但这个构造器只能由编译器在enum常量声明时调用用户代码不能通过new来创建枚举实例。原因在于枚举的设计目标就是保证实例数量固定且可控——如果允许外部new那枚举的“限定量”就形同虚设了。values()和ordinal()是编译器自动添加的方法前者返回所有枚举常量的数组后者返回常量在声明中的位置索引从0开始计数。顺带说一句这道题在当年还引发了一个讨论枚举和静态常量到底怎么选用友的面试官在面试环节会追问这个问题。标准答案是当变量的取值集合在编译期就可确定且有限时优先使用枚举。比如订单状态、用户角色、星期几、月份这些都有明确的边界。使用枚举的好处是类型安全——你传一个字符串SUCCESS没人能保证它不是SUCCES但你传一个OrderStatus.SUCCESS编译器就能帮你拦住大部分拼写错误。更进阶的玩法是给枚举加字段和方法比如public enum ResultCode { SUCCESS(200, 成功), ERROR(500, 服务异常); private final int code; private final String msg; ResultCode(int code, String msg) { this.code code; this.msg msg; } }这种写法在实际项目中非常常见SpringMVC或SpringBoot的响应体封装里几乎每家都会用。笔试考基础面试就考你怎么用基础解决实际问题。2.3 数组越界异常为什么边界条件总能送走一批人关于数组越界的题当时是这个样子int[] arr {1, 2, 3, 4, 5}; for (int i 0; i arr.length; i) { System.out.print(arr[i] ); }这段代码的运行结果是什么A. 输出1 2 3 4 5B. 输出1 2 3 4 5 然后抛出ArrayIndexOutOfBoundsExceptionC. 编译错误D. 什么都不输出答案是B。这个题表面考的是数组越界异常实际上考的是循环边界条件的书写习惯。arr.length等于5数组的有效索引范围是0到4。循环条件写的是i arr.length当i等于5时arr[5]就会抛出ArrayIndexOutOfBoundsException。这个异常属于运行时异常代码能编译通过运行到第6次迭代时崩溃。为什么每年都有人栽在这上面因为在很多人的学习阶段for循环就是无脑写i length从没想过为什么要这样写。面试官就是通过这种“熟悉的题目微小的变化”来区分“背过”和“理解”的人。实际开发中数组越界的坑通常没那么直接。最常见的场景是多线程环境下的数组或列表遍历比如在遍历一个ArrayList的同时另一个线程执行了remove操作会抛出ConcurrentModificationException或者诡异的IndexOutOfBoundsException。另一个场景是通过索引访问列表时列表长度被中途修改比如ListString list getList(); for (int i 0; i list.size(); i) { if (condition) { list.remove(i); } }删除之后list.size()变了i还在递增就会出现元素被跳过或者索引越界的问题。这就是Java面试题里常说的“循环中删除元素的坑”。正确的做法是使用迭代器Iterator的remove方法或者倒序遍历。3. 概念题部分面向对象、容器与Java 8新特性的考察节奏这套卷的简答题和概念题部分题量不大但都考得挺硬。用友的出题风格是不直接问定义而是给出一个描述让你判断对错或者补全缺失的部分。这样最能看出一个人是背了八股文还是真正理解了。3.1 面向对象三大特征一道题看清你的抽象能力概念题第一题请简述面向对象的三大特征并结合一个实际场景说明你是如何通过继承实现代码复用的。很多人的答案都是标准八股“封装、继承、多态。封装是把属性和方法打包继承是子类复用父类代码多态是同一消息不同表现。”这样答能拿到基本分但拿不到区分度分数。用友阅卷时有个内部评分标准我不能在这里完全复述但可以分享一个高分答案的骨架封装把状态和行为绑定到一个对象中外部只能通过公开的接口访问内部实现可以自由变化。举例UserService只暴露createUser方法内部怎么校验参数、怎么加密密码、怎么写入数据库外部完全不感知。这样做的好处是修改内部实现不影响外部调用方。继承子类继承父类的非私有成员可以覆写父类方法拓展行为。这里要画一个重点继承是is-a关系不是代码复用的万能药。很多人是“看到重复代码就继承”这其实是一种误用。举一个高频面试例子Stack继承Vector一直被诟病为设计缺陷因为Stack本应是LIFO结构但通过Vector继承来的get(int index)方法可以随意访问任意位置的元素破坏了栈的语义。这就是继承使用不当的经典反例。多态父类引用指向子类对象运行时根据实际对象类型调用对应方法。多态实现的两大基石是方法覆写和动态绑定。这里可以引出JVM方法调用指令的区别invokestatic和invokespecial是静态绑定invokevirtual和invokeinterface是动态绑定。如果能主动提到这一层面试官对你的技术深度会有额外加分。我个人的体会是这道题想拿高分最重要的不是背定义而是会举例子。一个好的例子比任何华丽的定义都更能证明你理解了面向对象。3.2 容器与集合框架HashMap和ArrayList才是常青考点第二道概念题直指容器ArrayList和LinkedList的区别是什么在什么场景下选择各自的适用类型HashMap的底层数据结构是怎样的这道题在当年的热词里也有对应——Java容器和java常用类反复出现。不过用友这套题问得比一般校招卷要深入一些它要求的是“掌握原理而不是会用”。先说ArrayList和LinkedList这道经典题。ArrayList底层是动态数组支持O(1)随机访问但中间插入和删除需要搬运元素平均O(n)LinkedList底层是双向链表随机访问需要从头遍历O(n)但头尾插入删除是O(1)。因为LinkedList每个节点保存了前驱和后继指针内存占用也比ArrayList大得多。这里有一个很多教程都会忽略的细节LinkedList在中间插入时虽然理论上是O(1)的链表操作但前提是你已经拿到了目标位置的节点。如果你只给了索引还得先通过遍历找到这个位置复杂度是O(n)。所以LinkedList在“指定索引处插入”这个场景下并不比ArrayList快。只有“在头部或者尾部插入且已知节点”时LinkedList才有优势。然后是这道题的压轴问法HashMap的底层数据结构。选择部分的答案也在这里被追问过。HashMap在Java 8及以后的版本中底层是数组链表红黑树的结构。当多个key的哈希值映射到同一个桶时以链表形式存储当链表长度大于等于8且数组长度大于等于64时链表会转换为红黑树降低最坏情况下的查询时间从O(n)到O(log n)。当桶中的节点数降到6及以下时红黑树会退化回链表。这里注意8和6之间保留了1个数是为了避免频繁转换带来的性能损耗——这个细节是面试的高频追问点。还有一个关于HashMap的高频坑扩容线程安全问题。JDK 7的HashMap在多线程并发扩容时头插法可能导致环形链表使得get操作陷入死循环。JDK 8改用尾插法避免了环形链表的产生但多线程环境下仍然存在数据丢失和size不准确的问题。这也是为什么面试必问“HashMap不是线程安全的那并发场景用什么”——答案是ConcurrentHashMap。ConcurrentHashMap在JDK 8中的实现是CASsynchronized锁住桶中头节点粒度更细并发性能更高。这个知识点在2018年的校招里已经属于“进阶者加分项”现在则几乎成了Java八股文的标配内容。3.3 lambda表达式与函数式编程当年的新题风向这套概念题的第三道关于lambda表达式。原题大意是给定一个字符串列表使用lambda表达式过滤出长度大于5的元素并转为大写后输出。放到现在看这几乎是Java 8新特性的入门题但在2018年很多学校还在教Java 6/7lambda表达式对不少应届生来说就是“听过没见过”。用友当年把这道题放进第七套也说明这套卷在Java新特性上有所倾斜。正确的解答是ListString list Arrays.asList(apple, banana, orange, watermelon); list.stream() .filter(s - s.length() 5) .map(String::toUpperCase) .forEach(System.out::println);想拿满分光写对代码还不够。它的考察点在于函数式接口的理解lambda表达式的类型是由目标类型推断的它必须匹配某个函数式接口即只有一个抽象方法的接口。PredicateT用于filterFunctionT, R用于mapConsumerT用于forEach。这三个是Java 8内置的最核心函数式接口。惰性求值与中间操作filter和map是惰性操作它们不会立即执行只有遇到终止操作比如forEach、collect时整个流水线才会真正执行。这个特性让Stream可以优化执行路径比如short-circuit的limit操作。方法引用的语法糖String::toUpperCase是s - s.toUpperCase()的简写。系统限定符::实例方法名等价于把该实例方法作为函数式接口的实现。能主动提一句方法引用说明你对lambda的语法理解更全面。如果第三道简答题答得好这道题基本就是送分题。但当年的很多同学连stream()都忘了写直接list.filter(...)——编译不过。说到底还是对Stream API的整体框架不熟悉只记住了lambda的箭头语法没有记住“Stream是流水线集合不直接拥有中间的流操作方法”。4. 编程题部分冒泡排序、快速排序与手写代码的评分标准这套卷的编程题有两道一道是冒泡排序一道是快速排序都是让用代码实现并写出复杂度。很多人看到这么“简单”的题会掉以轻心但恰恰是这种题目最能拉开分层。4.1 冒泡排序的完整作答正确性只是第一关第一道编程题实现一个冒泡排序算法对整型数组进行升序排序。标准的参考实现public static void bubbleSort(int[] arr) { if (arr null || arr.length 2) { return; } for (int i 0; i arr.length - 1; i) { boolean swapped false; for (int j 0; j arr.length - 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; } } }这道题的评分维度我根据当年的实际反馈整理如下第一层是否能写出正确的完整代码包括数组遍历、元素交换、循环边界。大量人在这层就挂了要么是越界要么是交换时用位运算把逻辑搞错了要么根本没想到排序结束后要输出什么。这一层的分值是50%。第二层是否考虑到边界条件和优化。比如数组为空或只有一个元素时直接返回比如内层循环的范围是arr.length - 1 - i而不是arr.length - 1比如用swapped标志位提前退出已有序的轮次。这一层的分值是30%。用友的阅卷标准里明确写了swapped优化是“加分项”写出来就能拉开分数差距。第三层是否写出时间复杂度和空间复杂度分析。冒泡排序的平均和最坏时间复杂度都是O(n²)最好的情况已经有序因为有了swapped优化可以降低到O(n)。空间复杂度是O(1)属于原地排序算法。这一层的分值是20%。还有一个隐藏的扣分点方法签名。如果题面没有给出完整签名很多人会写成public void bubbleSort(int[] arr)但更优秀、更自包含的写法是给一个排序类并提供静态方法public class SortAlgorithms { public static void bubbleSort(int[] arr) { ... } }这里不是要求你必须这样写但“静态方法类封装”的写法展示了你对工具类设计的理解在面试官眼里会打上“有工程意识”的标签。4.2 快速排序的实现从能写出到能讲清楚第二道编程题直接上快速排序。这题当年的通过率比冒泡低不少两个原因一是快排本身的递归逻辑比冒泡复杂二是在手写代码时容易忽略递归出口。参考实现采用经典Lomuto分区public static void quickSort(int[] arr, int left, int right) { if (left right) { return; } int pivotIndex partition(arr, left, right); quickSort(arr, left, pivotIndex - 1); quickSort(arr, pivotIndex 1, right); } private static int partition(int[] arr, int left, int right) { int pivot arr[right]; int i left - 1; for (int j left; j right; j) { if (arr[j] pivot) { i; swap(arr, i, j); } } swap(arr, i 1, right); return i 1; }先说最容易挂的地方递归出口。left right是终止条件少了这个要么无限递归栈溢出要么对长度为1的数组进行了无意义的再次分区。我见过至少十来个同学在这个地方翻车。再说分区逻辑。经典Lomuto分区法以最右侧元素为基准值pivot维护一个“小于基准值的边界索引i”。遍历整个区间遇到比pivot小的元素就和i1位置交换最终把pivot放到正确的位置。这个实现的方式是正确且可读的笔试场景下推荐优先使用因为不容易写错。还有一种Hoare分区法以中间元素为基准双指针相向而行理论交换次数更少但更容易写错边界条件笔试时间紧张时不太建议。这里的评分点除了正确性还有对时间复杂度和空间复杂度的理解。快排平均时间复杂度O(n log n)最坏情况O(n²)——当数组基本有序且基准值选取导致分区极度不平衡时。空间复杂度不是O(1)因为递归调用栈的平均深度是O(log n)最坏O(n)。很多人答“空间复杂度O(1)”是错的快排不是原地排序它依赖系统递归栈。如果时间富余可以在快速排序的题目下面顺手写一个验证方法public static boolean isSorted(int[] arr, int begin, int end) { for (int i begin; i end - 1; i) { if (arr[i] arr[i 1]) { return false; } } return true; }这个代码在笔试中不会额外给你分但在面试官review你的代码时会给他留下“这人有自测意识”的印象。属于不动声色的加分项。4.3 Comparator与对象排序给代码里加点实际应用感这套卷的编程题第三道其实是一道“伪编程题”核心考察点也对应热词里的“java comparator.comparing”。原题大意是定义一个Student类包含name和score字段请用Java代码将一个Student列表按score从大到小排序。这道题的满分参考版本class Student { private String name; private int score; public Student(String name, int score) { this.name name; this.score score; } public int getScore() { return score; } } ListStudent students Arrays.asList( new Student(Alice, 85), new Student(Bob, 92), new Student(Cindy, 78) ); students.sort(Comparator.comparing(Student::getScore).reversed());这里有两个关键点List.sort方法Java 8里List接口新增了默认方法sort(Comparator? super E c)内部调用Arrays.sort所以不需要再用Collections.sort了。写students.sort(...)既简洁又体现你对新API的熟悉度。Comparator.comparing和reversedComparator.comparing接受一个keyExtractor函数提取出排序键返回一个Comparator对象。.reversed()表示降序。连起来的意思是“按score字段降序排序”。注意reversed()不能写在comparing之前否则会先报编译错误。因为comparing返回的是ComparatorT而reversed()是Comparator接口的默认方法所以顺序必须是先comparing再reversed。如果你在第三道题里写出的是传统的匿名内部类students.sort(new ComparatorStudent() { Override public int compare(Student s1, Student s2) { return s2.getScore() - s1.getScore(); } });这也能拿分但显然不如lambda表达式那版优雅。用友的阅卷反馈是当时用lambda和Comparator.comparing的考生不到三成大部分还在写匿名内部类。这说明这套题本质上考察的已经超出了“会不会排序”而是“平时写代码用没用过Java 8新特性”的问题。这也是那几年校招Java新特性考察比重上升的一个缩影。如果还想再拿一点额外分可以顺手在compare方法里做一下score字段的null安全处理。可惜笔试一般没有时间展开但如果你在面试中被追问“怎么处理排序字段可能为null的情况”可以参考Comparator.nullsLast或者手动在lambda里判断。5. 给当时做题的自己复盘笔试策略、时间分配与面试追问这套题做下来最深的感受是真正拉开分数的不是难题而是基础题里的细节。选择题里那些“看着会做、一选就错”的选项才是筛选器。这里复盘几个实际做题时的决策点对现在的准备也有参考价值5.1 时间分配选择和编程题的合理节奏这套卷的整体时长是90分钟。我的建议分配是选择题和概念题控制在50分钟内剩40分钟全力做编程题。具体来说选择题每道题控制在2分钟以内8道题16分钟遇到不确定的先标记跳过不恋战。概念题每道题留8分钟左右注意分点和举例写满但不啰嗦。编程题第一道冒泡排序10分钟第二道快速排序15分钟第三道Comparator排序10分钟最后预留5分钟检查边界条件。为什么要强调先做编程题中的简单题因为笔试是踩点给分编程题只要基本正确就能拿到大量分而选择题选错了就是零分。从投入产出比看把时间优先投给大概率能拿分的地方。5.2 面试官拿着笔试卷会追问什么用友的技术面有一个特点面试官手里真的有你的笔试卷而且他会按照卷面上的错误一个一个追问过来。如果你选择题里选了“枚举可以用new创建”这个错误项面试官大概率会问“你说一下枚举在JVM里到底是怎么实现的编译器生成了什么”当年有同学被问到这个问题时愣在原地。其实这里有个知识铺垫枚举类编译后本质上就是一个final类继承自java.lang.Enum枚举常量是类的静态final实例。你可以用javap反编译工具查看生成的字节码会看到类似这样的结构final class Day extends java.lang.EnumDay { public static final Day MONDAY; public static final Day TUESDAY; public static final Day WEDNESDAY; private static final Day[] $VALUES; }面试官问这道题还不是为了让你背这个而是想看你能不能从“枚举为什么不能用new”这一现象反向推导出“枚举的构造器是私有的常量是静态final实例”这一本质。能答到这个层级就算深度到位了。另一个高频追问方向是HashMap的线程安全性。笔试只考了底层结构面试会继续追问“如果要你用HashMap实现线程安全的缓存你会怎么做”。这个问题没有标准答案你可以说用Collections.synchronizedMap包装也可以说用ConcurrentHashMap更进阶的说法是用ConcurrentHashMap加computeIfAbsent这样可以在保证原子性的同时做缓存读取和写入。如果你再补充一句“还要考虑缓存穿透和缓存击穿的问题”面试官就会开始给你加难度了但同时也说明他认可你在这个方向上的理解。关于这套卷的编程题面试官通常不会重新让你在白板上写一遍而是会问“你写的快排在什么情况下会退化到O(n²)你写的冒泡排序可以怎么优化如果数据量特别大用你写的这两种排序还合适吗”能对上这些追问比笔试过程中临时回忆排序代码要更有节奏感。6. 这套题放到现在还能怎么用写到最后说一点我认为最有价值的东西。很多同学拿着一套2018年的题第一反应是“这也太老了吧现在能考吗”。这种想法我理解毕竟JDK都出到21了Spring Boot 3都变成标配了还看8年前的题干嘛。但我的真实体验是基础这层东西变动的速度远远低于你想象。数组越界、类型转换、枚举语义、整数除法这些语言级特性在Java 8到Java 21之间几乎没有变化。HashMap的数组链表红黑树结构从Java 8开始稳定至今未动。面向对象的三大特征lambda表达式的函数式接口映射快速排序的分治思想这些更是十几年如一日的考点。唯一变化比较大的部分是并发编程和JVM调优类题目的比重在持续上升比如CompletableFuture、虚拟线程Virtual Threads、ZGC等新东西越来越多地出现在面试里。所以这套题真正的价值不在于题目本身而在于它训练了你“从语言基础到工程实践”的思考路径。拿到任何一套“老”题不要只看答案而是问自己这三个问题这道题背后的知识点在今天是否依然成立变化在哪里我现在是否能手写出所有涉及的代码并解释每一步的“为什么”如果把这道题换一种场景比如改成并发环境我还能应对吗把这套流程跑一遍再回来看2018年的用友笔试题它就不再是一份旧试卷而是一套扎实的Java基础训练手册。能把它消化到这个程度那2018年的“用友秋招Java笔试题七”才算彻底吃透了。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →