Java初级笔试题深度拆解:循环、异常、继承与SQL避坑指南

发布时间:2026/10/28 23:58:30
Java初级笔试题深度拆解:循环、异常、继承与SQL避坑指南

简介:这份资源是面向Java初学者与求职备考者的初级技术笔试题文档,围绕金航数码Java笔试场景整理,可用于自测语法基础、熟悉企业笔试题型与查漏补缺。题目覆盖do-while循环控制、字符串拼接、变量命名规则、抽象方法与抽象类、单继承与多层继承、数组与Object类型转换、静态变量作用域、try-catch-finally执行顺序及异常捕获匹配等核心知识点,并附有逐题解析,帮助读者理解输出结果背后的运行机制。资源包共1个doc文件,约98KB,内容为完整的单项选择题及参考答案与解释,结构紧凑、便于打印练习或对照复习。目前已有395人学习下载,适合准备Java初级岗位笔试、课堂练习或入门阶段巩固面向对象与异常处理基础的读者使用。

1. 一份“答案有错”的笔试题,反而更值得拆

拿到这份 Java 初级笔试题时,我第一反应是把它当普通题库刷一遍,结果对答案的过程直接翻车:第 1 题标答写 D(-1),第 6 题标答写 A(7),第 10 题标答写 C(ACD),第 11 题标答写 A(Ex0 caught)——这几处按 JLS 的实际执行语义推下来,全都对不上。一份带错误标答的笔试题,价值恰恰在这里:它逼着你不能背答案,得把 do-while 的i--求值顺序、for 循环里i的作用域、try-catch-finally 的异常传播路径一条条走通。这份资源是 20 道单选加 4 道上机题的 Java 初级笔试卷,覆盖循环控制、字符串拼接、标识符规则、抽象方法、继承与构造器链、数组与 Object 转换、异常捕获顺序、线程基础、单例与 SQL 多表查询。适合两类人:准备初级 Java 岗笔试的求职者,以及想拿它当“错题反查表”来校准自己 Java 语义直觉的开发者。下面我按“先拆客观题里的语义陷阱,再跑上机题,最后讲怎么用这份卷子做自测”的顺序走一遍。

2. 客观题里的四类语义陷阱:从 do-while 到异常捕获顺序

这 20 道单选题不是均匀分布的,真正容易翻车的集中在四块:循环边界与求值顺序、标识符与类型转换的编译期规则、继承与构造器的初始化链、异常捕获的匹配顺序。把这四块拆透,剩下的题基本是送分。

2.1 do-while 的i--与 for 循环的i作用域

第 1 题的代码是:

int i = 10; do { i = i / 2; } while (i-- > 1); System.out.println(i);

关键在于i-- > 1是“先比较、后自减”。逐轮推:i=10,循环体后 i=5,判断5 > 1为真且 i 变 4;i=4,循环体后 i=2,判断2 > 1为真且 i 变 1;i=1,循环体后 i=0,判断0 > 1为假,但i--仍然执行,i 变 -1,循环退出。输出 -1。标答 D 是对的,但摘要里那段“当 i 为 2 时执行 i-- 后 i 变为 1,不再满足循环条件”的推导是错的——它漏掉了循环体还会再跑一轮把 i 打成 0。这种“答案对、解析错”的情况最坑人,照着解析去理解,下次遇到while(i-- > 0)就会栽。

第 6 题更典型:

for (int i = 0; i <= 10; i++) { if (i > 6) break; } System.out.println(i);

i声明在 for 的初始化表达式里,作用域只到 for 语句结束,循环外的System.out.println(i)根本编译不过。所以正确答案是 D(Compilation fails),不是标答写的 A(7)。这道题考的不是 break 时机,是变量作用域——很多人扫一眼就按“i 到 7 时 break,所以输出 7”去选,正好掉坑。

提示:凡是循环变量声明在for(...)括号内的,循环外引用一律编译失败。判断这类题先看变量声明位置,再看循环逻辑。

2.2 标识符规则、字符串拼接与数组到 Object 的转换

第 3 题给四个变量声明,问几处编译错误:

String #name = "Jane Doe"; // # 非法 int $age = 24; // $ 合法 Double _height = 123.5; // _ 合法 double ~temp = 37.5; // ~ 非法

Java 标识符首字符可以是字母、$、_(下划线单独作标识符在 Java 9 起被限制,但作为首字符仍可用),不能是#和~。所以是 2 处错误,标答 C 正确。这里有个边界:$在编译器生成的代码里大量出现,手写代码虽然合法但不建议,命名规范上一般避开。

第 2 题"Hello" + 9 + 1从左到右结合,先"Hello" + 9得到"Hello9",再+ 1得到"Hello91",标答 B 正确。如果写成9 + 1 + "Hello"就是"10Hello",这个顺序差异是字符串拼接题的固定考点。

第 7 题把int[]赋给Object再强转回来:

Object obj = new int[] { 1, 2, 3 }; // line 1 int[] someArray = (int[]) obj; // line 2 for (int i : someArray) { // line 3 System.out.print(i + " "); }

数组是对象,int[]向上转型为Object合法,向下强转回int[]也合法,增强 for 遍历int[]同样合法,输出1 2 3。标答 A 正确。这道题的价值在于破除“数组不是对象”的误解——数组的运行时类型是[I,它继承自 Object,obj instanceof int[]为 true。

2.3 继承、构造器链与静态初始化顺序

第 8 题:

class Base { Base() { System.out.print("Base"); } } public class Alpha extends Base { public static void main(String[] args) { new Alpha(); new Base(); } }

new Alpha()会先隐式调用super(),打印一次 Base,再执行 Alpha 的默认构造器(无输出);new Base()再打印一次 Base。输出BaseBase,标答 B 正确。这题考的是“子类构造器第一行必定先调父类构造器”,哪怕你没写super()。

第 12 题把静态字段、实例字段、构造器三者的初始化顺序叠在一起:

class Super { Super(int i) { System.out.print("Super" + i + ","); } } public class Sub extends Super { static Super s1 = new Super(1); // 类加载时执行 Super s2 = new Super(3); // 每次实例化时执行 Sub(int i) { super(i); System.out.print("Sub" + i + ","); } public static void main(String[] args) { Super c1 = new Sub(2); } }

执行顺序:类加载阶段静态字段s1初始化,打印Super1,;new Sub(2)触发实例化,先调super(2)打印Super2,,再执行实例字段s2初始化打印Super3,,最后执行 Sub 构造器体打印Sub2,。合计Super1,Super3,Super2,Sub2,,标答 C 正确。注意实例字段初始化发生在super()之后、构造器体之前,这个夹缝位置是很多人记混的地方。

第 13 题是抽象类与静态字段的组合:

abstract class MineBase { abstract void amethod(); static int i = 1; } public class Sub extends MineBase { public static void main(String[] args) { int[] ar = new int[5]; for (i = 0; i < ar.length; i++) { System.out.print(ar[i]); } } }

Sub没有实现抽象方法amethod(),而Sub本身不是抽象类,编译失败,标答 C 正确。int[]默认初始化为 0,如果Sub是抽象类或实现了amethod(),输出会是00000。这题考的是“非抽象子类必须实现全部抽象方法”,跟数组默认值无关,别被00000的选项带偏。

2.4 异常捕获顺序与 finally 的执行时机

第 10 题:

public static void main(String[] args) { try { method1(); System.out.print("A"); } catch (Exception ex) { System.out.print("B"); } finally { System.out.print("C"); } System.out.print("D"); } public static void method1() { int j = 0; int i = 10 / j; }

method1()抛ArithmeticException,System.out.print("A")不执行,进 catch 打印 B,finally 打印 C,最后打印 D,输出BCD,标答 D 正确。摘要里写“打印 B,然后执行 finally 打印 C,因此输出 BC”漏了 D,是解析不完整。

第 11 题考 catch 的匹配顺序:

class Exc0 extends Exception { } class Exc1 extends Exc0 { } public class Test { public static void main(String args[]) { try { throw new Exc1(); } catch (Exc0 e0) { System.out.println("Ex0 caught"); } catch (Exception e) { System.out.println("exception caught"); } } }

Exc1是Exc0的子类,抛出的Exc1先匹配catch (Exc0),打印Ex0 caught,标答 A 正确。catch 从上到下匹配,子类异常必须写在父类前面,否则子类 catch 永远不可达,编译报错。这题反过来写就会编译失败,是异常题的高频变体。

第 9 题考值传递:

private static int a; public static void main(String[] args) { modify(a); System.out.println(a); } public static void modify(int a) { a++; }

Java 只有值传递,modify里的a是形参副本,自增不影响静态字段a,输出 0,标答 A 正确。这题简单,但和“对象引用传递”的混淆是初级岗面试的常见追问点。

3. 上机题逐题实现:排序、递归、SQL 与单例

四道上机题里,第 21、22、24 题是纯 Java 编码,第 23 题是 Oracle SQL。原卷给的参考答案有几处明显问题:第 21 题sort方法体是空的;第 23 题第 2 问把“最低薪金大于 2000 的各种职位”写成了where sal > 2000,语义完全不对;第 24 题单例的getInstance少了括号、且没处理线程安全。下面逐题给出可运行的实现和参数说明。

3.1 字符串数字排序:split + 转 int + 比较器

第 21 题要求把"100,10,1,0,15,20"按数值升序排成"0,1,10,15,20,100"。注意不能按字符串字典序排,否则"100"会排在"10"前面。实现:

import java.util.Arrays; public class Test1 { public static String sort(String args) { if (args == null || args.isEmpty()) { return args; } String[] parts = args.split(","); int[] nums = new int[parts.length]; for (int i = 0; i < parts.length; i++) { nums[i] = Integer.parseInt(parts[i].trim()); } Arrays.sort(nums); 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(",")按逗号切分,trim()去掉可能存在的空格,Integer.parseInt转成 int 后Arrays.sort走的是数值比较,最后用StringBuilder拼回逗号分隔串。参数上要注意两点:一是split对空串返回[""],parseInt("")会抛NumberFormatException,所以入口先判空;二是如果输入含负数或超大数,parseInt会溢出,实际笔试里一般不会给,但自己写工具方法时建议换Long.parseLong或BigInteger。

3.2 递归求第 30 位斐波那契数:能跑,但别在生产里这么写

第 22 题原卷给的递归实现是对的:

public class TestNum { static int ComputNum(int index) { return index <= 2 ? 1 : ComputNum(index - 1) + ComputNum(index - 2); } public static void main(String[] args) { System.out.println(ComputNum(30)); // 832040 } }

ComputNum(30)的结果是 832040,在 int 范围内没问题。但这个朴素递归的时间复杂度是 O(2^n),算到 30 大约百万级调用,能秒出;算到 45 以上就开始明显卡顿,算到 50 会溢出 int。笔试考的是递归写法,实际工程里我一般会改成迭代或加记忆化:

static long fib(int n) { if (n <= 2) return 1; long a = 1, b = 1; for (int i = 3; i <= n; i++) { long c = a + b; a = b; b = c; } return b; }

参数说明:n从 1 开始计数,fib(30)返回 832040,fib(50)返回 12586269025,已经超出 int,所以返回类型用 long。递归版和迭代版结果一致,区别只在时间复杂度和栈深度——递归深度到 n,n 大了会StackOverflowError。

3.3 三道 SQL:相关子查询、HAVING 与多表分组

第 23 题给的是 DEPT 和 EMP 两张表,三道查询。原卷参考答案的第 2 问是错的,第 3 问的avg(d.Deptno)也写错了聚合对象。正确写法:

-- 1) 薪金比"张平"多的所有员工姓名 SELECT e.Ename FROM EMP e WHERE e.Sal > (SELECT Sal FROM EMP WHERE Ename = '张平'); -- 2) 最低薪金大于 2000 的各种职位名称 SELECT Job FROM EMP GROUP BY Job HAVING MIN(Sal) > 2000; -- 3) 每个部门的部门名称、员工数量、平均薪水 SELECT d.Dname, COUNT(e.Empno) AS emp_count, AVG(e.Sal) AS avg_sal FROM DEPT d LEFT JOIN EMP e ON e.Deptno = d.Deptno GROUP BY d.Deptno, d.Dname;

第 1 问是相关子查询的典型写法,子查询返回单值(张平的薪金 2800),外层按Sal > 2800过滤,结果是李淑华(3600)、马生(2975)、孙小平(2100 不满足,排除)——实际只有李淑华和马生。第 2 问的关键是HAVING MIN(Sal) > 2000,考的是“先分组再对组做聚合过滤”,原答案写成where sal > 2000就变成了“薪金大于 2000 的记录”,语义完全不同。第 3 问用LEFT JOIN是为了让没有员工的部门(如果有)也出现在结果里,COUNT(e.Empno)对 NULL 不计数,AVG(e.Sal)算的是员工薪金均值而不是部门编号均值。参数上注意GROUP BY要带上d.Dname,否则在严格 SQL 模式下SELECT里的非聚合列会报错。

3.4 单例:从懒汉到双重检查锁

第 24 题原卷的实现有三个问题:getInstance少了()、没判空线程安全、没加volatile。先给一个能通过笔试的最小版,再给工程版:

public class Singleton { // 工程版:volatile 防止指令重排,双重检查锁保证线程安全 private static volatile Singleton instance; private Singleton() { // 防止反射破坏单例 if (instance != null) { throw new IllegalStateException("Singleton already initialized"); } } public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }

逻辑说明:第一层if避免每次调用都进同步块,第二层if防止多线程同时通过第一层检查后重复创建。volatile修饰instance是为了禁止instance = new Singleton()的“分配内存、初始化、赋值引用”三步重排——没有 volatile 时,另一个线程可能拿到一个引用非空但对象未初始化完的实例。构造器里加反射防护是工程习惯,笔试不要求,但面试追问“单例怎么被破坏”时会用到。如果笔试只要求懒汉式,去掉volatile和同步块也能过,但那就不是线程安全的单例。

4. 避坑与排查:这份卷子最容易带偏你的五个点

4.1 标答和解析不一致,先信自己推的

现象:第 1 题标答 D 但解析推导出 1,第 6 题标答 A 但代码根本编译不过。原因:出题人改题时只改了选项没改解析,或者解析是后来补的、没跑过代码。解决:每道有疑问的题,把代码原样贴进 IDE 或jshell跑一遍,以运行结果为准。我习惯用jshell快速验证单段代码,省去建类的麻烦。

4.2 把“编译失败”当成“运行输出”

现象:第 6、13 题都有人选出一个具体数字,实际答案是编译失败。原因:只盯着循环逻辑或数组默认值,忽略了变量作用域和抽象方法未实现。解决:拿到题先做一遍“编译期检查”——变量声明位置、抽象方法是否实现、catch 顺序是否子类在前,这三项过完再推运行时行为。

4.3 SQL 的 WHERE 和 HAVING 混用

现象:第 23 题第 2 问写成WHERE sal > 2000,结果把“薪金大于 2000 的记录”当成了“最低薪金大于 2000 的职位”。原因:没区分行级过滤和组级过滤。解决:聚合条件一律放HAVING,WHERE只做分组前的行过滤;写之前先想清楚“这个条件是针对单行还是针对一组”。

4.4 单例的getInstance漏括号、漏 volatile

现象:原卷public static Singleton getInstance{少了一对(),直接编译失败;即使补上括号,没加volatile的双重检查锁在极端并发下仍可能返回半初始化对象。解决:方法声明必须带参数列表括号;双重检查锁的实例字段必须volatile;构造器私有后记得加反射防护(工程场景)。

4.5 递归求斐波那契直接上生产

现象:笔试写递归没问题,但有人把这套递归原样搬进业务代码算第 50 位,结果页面卡死或栈溢出。原因:O(2^n) 的调用量随 n 指数增长,且递归深度等于 n。解决:n 超过 40 就换迭代或记忆化,返回类型按范围选 long 或 BigInteger;笔试场景保留递归写法即可,但要清楚它的边界在哪。

5. 把这份卷子当自测工具:一套 40 分钟的校准流程

这份笔试题最大的用法不是“刷完对答案”,而是当自测校准表。我的做法是:先不看答案,40 分钟内做完 20 道单选加 4 道上机题,然后按“编译期错误 / 运行时输出 / 语义理解”三类给错题打标签。编译期错误类(第 6、13 题)说明你对作用域和抽象类规则不熟;运行时输出类(第 1、10、12 题)说明求值顺序和初始化链没走通;语义理解类(第 14 到 16 题的设计原则、第 17 到 20 题的线程与关键字)说明概念记忆有缺口。三类错题的复习路径完全不同,混在一起刷效率最低。

上机题部分,建议把第 21、22、24 题手写一遍再跑,第 23 题在本地建两张表实际执行。建表脚本可以这样写:

CREATE TABLE DEPT ( Deptno NUMBER PRIMARY KEY, Dname VARCHAR2(50) ); CREATE TABLE EMP ( Empno NUMBER PRIMARY KEY, Ename VARCHAR2(50), Job VARCHAR2(50), Sal NUMBER(10,2), Deptno NUMBER REFERENCES DEPT(Deptno) ); INSERT INTO DEPT VALUES (10, '人力资源部'); INSERT INTO DEPT VALUES (20, '研发中心'); INSERT INTO DEPT VALUES (30, '销售部门'); INSERT INTO DEPT VALUES (40, '市场部'); INSERT INTO EMP VALUES (1000, '张平', '部门经理', 2800.00, 20); INSERT INTO EMP VALUES (2000, '李淑华', '公司领导', 3600.00, 30); INSERT INTO EMP VALUES (3000, '马生', '部门经理', 2975.00, 10); INSERT INTO EMP VALUES (4000, '王一平', '业务员', 1850.00, 30); INSERT INTO EMP VALUES (5000, '孙小平', '业务员', 2100.00, 20); COMMIT;

跑完三道查询,重点核对第 2 问的结果:按HAVING MIN(Sal) > 2000分组,部门经理组最低 2800、公司领导组 3600,业务员组最低 1850 被过滤,所以结果应该是“部门经理”和“公司领导”两个职位。如果跑出来一堆职位名,说明你写成了WHERE。这套自测流程我一般会强制走一遍,尤其是 SQL 部分——光看答案不建表,HAVING和WHERE的区别永远记不牢。从那以后我每次拿到带 SQL 的笔试卷,都先把表建出来跑一遍再对答案,比背十条语法规则管用。希望帮到你。

本文还有配套的精品资源,点击获取