金航数码Java笔试题深度拆解:do-while后缀自减、异常捕获顺序与SQL HAVING避坑指南

📅 发布时间:2026/10/15 22:24:42
金航数码Java笔试题深度拆解:do-while后缀自减、异常捕获顺序与SQL HAVING避坑指南
简介这份金航数码Java笔试题资源面向Java初学者与求职备考者用于检验和巩固Java基础语法与面向对象核心概念。题目覆盖do-while循环控制、字符串拼接、变量命名规则、抽象方法与抽象类、继承与构造器调用顺序、数组与Object类型转换、静态变量作用域以及try-catch-finally异常处理等高频考点每道题均配有详细解析帮助读者理解输出结果背后的运行机制。资源包内含1个doc文档压缩后约98KB以单项选择题形式呈现题量适中、解析完整适合作为笔试模拟练习或知识点自查清单。目前已有394人学习下载读者可借此梳理Java初级技术面试的常见题型查漏补缺为后续深入学习面向对象编程与异常处理打下基础。1. 从一份 Java 初级笔试题拆起它到底能帮你补上哪些基础漏洞很多人拿到「金航数码 java 笔试题」这类资源第一反应是扫一眼答案就扔进收藏夹。但真正在一线带过人的都知道这套题的杀伤力不在难度而在它把 Java 基础里最容易「以为自己会了」的地方全踩了一遍。do-while 的后缀自减、字符串拼接的隐式转换、变量命名里$和_的合法边界、抽象方法到底能不能带大括号、静态变量与局部变量同名时的遮蔽——这些点单独看都不难可一旦混在选择题里翻车率极高。这份资源适合三类人刚学完 Java 语法想自测的在校生、准备初级岗面试但基础不牢的求职者、以及带新人时想找一套现成摸底题的工程师。它覆盖了循环控制、字符串操作、继承与多态、异常处理、线程基础、设计原则外加四道上机题和一组 Oracle SQL 查询。下面我不按题号顺序讲而是按「哪些知识点最容易在实战里埋雷」重新拆一遍把每道题背后的机制、参数边界和常见误用讲透让你不只是对答案而是能自己推导出答案。2. 选择题里的五个高频翻车点从 do-while 到异常捕获顺序2.1 do-while 的后缀自减为什么输出是 -1 而不是 1第 1 题给的是int i 10; do { i i/2; } while( i-- 1 );最后打印i。很多人算到i变成 2 的时候就停了觉得循环条件2 1成立继续执行然后i变成 11 1不成立退出输出 1。但这里的关键是i--是后缀自减先拿i的当前值去比较比较完再把i减 1。所以当i 2时i-- 1的判断过程是取 2 和 1 比较成立然后i自减为 1循环体继续执行i 1/2 0。下一轮判断i-- 1取 0 和 1 比较不成立然后i自减为 -1循环退出。最终输出 -1。这个点在实际代码里对应的是「循环变量在条件判断中被意外修改」的经典坑。我一般会建议如果循环条件里要用到自增自减优先用前缀形式或者把修改挪到循环体末尾否则读代码的人包括三个月后的你自己很容易算错。// 把自减从条件里拆出来逻辑一目了然 int i 10; do { i i / 2; if (i 1) break; i--; } while (true); System.out.println(i); // 输出 0和原题语义不同仅作可读性对比上面这段只是演示「拆开写」的思路实际语义和原题不一样别直接抄去对答案。重点是理解后缀自减在条件表达式里的执行时序。2.2 字符串拼接与变量命名Hello91和那两个非法字符第 2 题String s Hello 9 1;输出Hello91。原因是从左到右结合Hello 9先把 9 转成字符串得到Hello9再 1得到Hello91。如果想先算9 1必须写成Hello (9 1)。这个坑在拼 SQL、拼日志、拼 URL 参数时天天出现尤其是拼接金额和数量的时候少一对括号结果就完全不对。第 3 题考变量命名合法性。Java 标识符可以用字母、$、_开头后面跟字母、数字、$、_。所以#name里的#非法~temp里的~非法$age和_height都是合法的。答案是 2 处编译错误。注意_单独作为标识符在 Java 9 以后是保留的但_height这种带前缀的没问题。2.3 抽象方法与继承大括号的有无和单继承边界第 4 题问抽象方法声明。抽象方法不能有方法体也就是不能写大括号所以 D 正确。A 错在abstract不能修饰字段B 和 C 都在说大括号可有可无都是错的。第 5 题考继承Java 类单继承一个子类只能有一个直接父类但一个父类可以有多个子类C 正确。第 16 题关于继承表述错误的是 D因为继承恰恰是「白盒复用」父类的修改会直接影响子类这正是继承耦合度高的问题所在。实际项目里我见过太多人为了复用几个方法就随便继承结果父类一改十几个子类全炸。常见做法是优先用组合只有确实是「is-a」关系时才用继承。2.4 异常捕获顺序与 finally 的执行时机第 10 题method1()里10/0抛ArithmeticException被catch (Exception ex)捕获打印 B然后finally打印 C最后System.out.print(D)打印 D所以是 BCD。第 11 题里Exc1 extends Exc0抛出Exc1后先匹配catch (Exc0 e0)因为子类异常可以被父类 catch 捕获所以打印Ex0 caught答案是 A。这里有个实战原则catch 块要从具体到宽泛排列否则子类异常的 catch 永远轮不到。编译器对「父类异常在前、子类异常在后」会直接报错但很多人写catch (Exception e)一把梭把具体异常处理逻辑全吞了排查问题时连异常类型都看不到。2.5 静态变量遮蔽与构造器执行顺序第 9 题modify(int a)里的a是局部变量a改的是副本静态变量a还是 0。第 12 题考构造器执行顺序静态变量s1先初始化打印Super1然后main里new Sub(2)先执行父类构造Super(2)打印Super2再执行子类构造打印Sub2。但s2是实例变量在super(i)之后才初始化所以顺序是Super1, Super2, Sub2再Super3这里要仔细s2 new Super(3)是实例变量初始化发生在父类构造之后、子类构造体之前。所以完整顺序是Super1静态、Super2父类构造、Super3实例变量、Sub2子类构造体对应选项 C。这个顺序在 Spring 项目里排查 Bean 初始化问题时特别有用静态代码块、构造器、PostConstruct的执行时序搞混了就会出现「依赖还没注入就拿来用」的空指针。3. 上机题落地字符串排序、递归、SQL 和单例的完整实现3.1 字符串数字升序split 类型转换 排序第 21 题要求把100,10,1,0,15,20升序排列输出0,1,10,15,20,100。注意这是按数值排序不是按字符串字典序否则100会排在15前面。实现思路按逗号切分转成 int 数组排序再拼回去。import java.util.Arrays; public class Test1 { public static String sort(String args) { // 空值和空串保护避免 split 后拿到空数组 if (args null || args.trim().isEmpty()) { return ; } String[] parts args.split(,); int[] nums new int[parts.length]; for (int i 0; i parts.length; i) { // trim 防止 10 这种带空格的输入转换失败 nums[i] Integer.parseInt(parts[i].trim()); } Arrays.sort(nums); // 用 StringBuilder 拼接避免循环里用 产生大量临时对象 StringBuilder sb new StringBuilder(); for (int i 0; i nums.length; i) { if (i 0) sb.append(,); sb.append(nums[i]); } return sb.toString(); } public static void main(String[] args) { String s 100,10,1,0,15,20; System.out.print(sort(s)); // 0,1,10,15,20,100 } }参数说明split(,)的参数是正则逗号本身不是特殊字符直接写没问题如果分隔符是|或.就需要转义。Integer.parseInt遇到非数字会抛NumberFormatException生产环境里我一般会包一层 try-catch 或者用Integer::valueOf配合默认值。Arrays.sort对 int 数组是双轴快排时间复杂度 O(n log n)数据量小的时候完全够用。3.2 递归求第 30 位斐波那契数能跑但别在面试里只写这一版第 22 题给了递归实现index 2 ? 1 : ComputNum(index-1) ComputNum(index-2)。这个写法正确但求第 30 位时重复计算量巨大实际跑起来会明显卡顿。第 30 位大概是 832040递归调用次数是指数级的。public class TestNum { // 记忆化递归用一个数组缓存已经算过的结果 static long[] cache new long[100]; static long computNum(int index) { if (index 2) return 1; if (cache[index] ! 0) return cache[index]; cache[index] computNum(index - 1) computNum(index - 2); return cache[index]; } public static void main(String[] args) { System.out.println(computNum(30)); // 832040 } }注意这里把返回类型从int改成了long因为斐波那契数增长很快第 50 位就超过 int 范围了。如果面试官只要求第 30 位int 够用但养成用 long 的习惯没坏处。记忆化之后复杂度降到 O(n)第 30 位瞬间出结果。迭代版更省空间但递归版更能体现你对「重叠子问题」的理解。3.3 Oracle SQL 三连子查询、分组过滤和连接聚合第 23 题给了 DEPT 和 EMP 两张表三道查询。第一道「薪金比张平多的员工姓名」用子查询拿张平的薪金再外层过滤SELECT Ename FROM EMP WHERE Sal (SELECT Sal FROM EMP WHERE Ename 张平);第二道「最低薪金大于 2000 的各种职位」注意是「最低薪金大于 2000」不是「薪金大于 2000」。原题答案写的是WHERE sal 2000这是错的那样会把某个职位里低薪的人也筛进来。正确写法要用GROUP BY加HAVINGSELECT Job FROM EMP GROUP BY Job HAVING MIN(Sal) 2000;第三道「每个部门的部门名称、员工数量和平均薪水」原题答案里avg(d.Deptno)是明显笔误应该是对薪金求平均而且连接条件写在WHERE里配合GROUP BY虽然能跑但更规范的写法是JOIN ... ONSELECT d.dname AS 部门名称, COUNT(e.Empno) AS 员工数量, AVG(e.Sal) AS 平均薪水 FROM DEPT d LEFT JOIN EMP e ON d.deptno e.Deptno GROUP BY d.dname;用LEFT JOIN是为了让没有员工的部门也能显示出来员工数量为 0。如果用INNER JOIN市场部如果没有员工就会直接消失。这个细节在实际报表里很关键我踩过好几次「数据对不上最后发现是内连接把空部门过滤掉了」的坑。3.4 单例模式原题代码有三个致命问题第 24 题给的单例实现有几个明显缺陷getInstance少了括号s没有加volatile而且没有处理多线程并发。原题代码class Singleton { private static Singleton s; private Singleton() {}; public static Singleton getInstance { // 缺少 () if (s null) s new Singleton(); return s; } }getInstance后面必须加()否则编译不过。更严重的是线程安全问题两个线程同时判断s null都为真就会创建两个实例。常见做法是加synchronized或者用静态内部类/枚举。下面给一个兼顾懒加载和线程安全的双重检查锁版本public class Singleton { // volatile 禁止指令重排序防止拿到未初始化完的对象 private static volatile Singleton instance; private Singleton() { // 防止反射破坏单例 if (instance ! null) { throw new IllegalStateException(单例已被创建); } } public static Singleton getInstance() { if (instance null) { synchronized (Singleton.class) { if (instance null) { instance new Singleton(); } } } return instance; } }参数说明volatile在这里的作用是保证instance new Singleton()这个操作的可见性和有序性因为对象创建分为分配内存、初始化、赋值引用三步没有volatile时其他线程可能看到引用非空但对象还没初始化完。构造器里的反射防护是进阶做法日常业务代码里不强制但面试时提一句会加分。4. 避坑与排查这套题里最容易误判的五个地方4.1 现象第 6 题输出 6 还是 7 分不清原因for (int i0; i10; i)里if (i6) break;当i7时条件成立执行break但此时i已经是 7 了。很多人以为 break 会把i回退。解决break 只跳出循环不改变循环变量的当前值所以循环外打印i是 7。但注意原题把System.out.println(i)写在循环外面而i是在 for 括号里声明的作用域只在循环内所以这段代码其实编译失败。这是题目本身的陷阱选项里 D 是 Compilation fails。4.2 现象第 7 题数组强转到底哪一行报错原因Object obj new int[]{1,2,3};合法因为数组是对象。int[] someArray (int[])obj;也合法因为运行时类型确实是int[]。但for (int i : someArray)这里原题选项里有两个 C属于排版错误。实际这段代码能正常编译运行输出1 2 3。解决遇到选项重复或明显排版问题时以代码实际行为为准别被选项带偏。4.3 现象第 13 题抽象类里的静态变量被循环修改原因abstract class MineBase里定义了static int i 1子类Sub的main里for(i0; iar.length; i)直接用了这个静态i。ar是长度 5 的 int 数组默认全 0循环打印00000。但Sub继承了MineBase却没有实现抽象方法amethod()所以Sub必须声明为 abstract否则编译错误。原题选项 C 是编译错误正确。解决继承抽象类时要么实现所有抽象方法要么自己也声明为 abstract。4.4 现象第 17 题线程执行体方法选 run 还是 start原因start()是启动线程的方法由 JVM 调用run()来执行线程体。题目问「定义线程的执行体」答案是run()。很多人把「启动线程」和「定义执行体」搞混。解决记住start()负责开线程run()负责干活直接调用run()就是普通方法调用不会开新线程。4.5 现象SQL 里WHERE和HAVING混用导致结果不对原因WHERE在分组前过滤行HAVING在分组后过滤组。第 23 题第二道要求「最低薪金大于 2000 的职位」必须用HAVING MIN(Sal) 2000写成WHERE Sal 2000会先把低薪行过滤掉再分组语义完全变了。解决聚合函数条件一律放HAVING普通列条件放WHERE两者可以同时出现。5. 把这份题当自测工具我的使用习惯和一道扩展练习我带新人的时候不会让他们直接看答案而是先限时 40 分钟做一遍选择题上机题单独给 30 分钟。做完之后不急着对答案而是让每个人讲一遍自己选某个选项的理由尤其是做错的题。这套题的价值不在于题目本身有多新而在于它能把「我以为我懂了」和「我真的懂了」之间的差距暴露出来。一个具体的验证方法是把第 21 题的排序改成「按数字的字符串长度排序长度相同按数值升序」比如100,10,1,0,15,20应该输出0,1,10,15,20,100还是别的顺序自己写一遍就知道对 Comparator 的理解到不到位。import java.util.Arrays; import java.util.Comparator; public class SortByLengthThenValue { public static void main(String[] args) { String s 100,10,1,0,15,20; String[] parts s.split(,); Arrays.sort(parts, new ComparatorString() { Override public int compare(String a, String b) { // 先比长度长度不同直接返回 if (a.length() ! b.length()) { return a.length() - b.length(); } // 长度相同按数值比 return Integer.compare(Integer.parseInt(a), Integer.parseInt(b)); } }); System.out.println(String.join(,, parts)); } }这段代码的输出是0,1,10,15,20,100因为长度分组后 1 位数是 0 和 12 位数是 10、15、203 位数是 100。如果你能不看答案直接写出来说明Comparator和Arrays.sort的重载用法已经过关了。还有一个我自己的习惯每次做完这类笔试题都会把错题对应的知识点写成一个最小可运行 demo扔进一个叫java-basics-drill的本地目录。三个月后如果又错了同一类题说明当时根本没理解只是记住了答案。这份「金航数码 java 笔试题」里do-while 后缀自减、异常捕获顺序、SQL 的HAVING这三处是我见过最多人反复栽跟头的地方。从那以后我每次遇到循环条件里带自增自减的代码都会强制自己在纸上把每一轮的值写出来再下结论。希望帮到你。本文还有配套的精品资源点击获取