ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Java初级笔试题深度解析:循环、继承、SQL与单例的避坑指南

Java初级笔试题深度解析:循环、继承、SQL与单例的避坑指南

简介:这份金航数码Java笔试题面向初级Java开发者与求职者,精选了覆盖基础语法核心考点的典型选择题,重点考察循环控制、字符串连接、变量命名规则、抽象类与继承关系、数组与对象转换以及异常处理机制。资源为doc格式文档,压缩包内共1个文件,体积约98KB,便于直接阅读或打印练习,目前已有395人学习浏览。文档不仅给出正确答案,还针对易错点展开解析:例如第1题通过do-while循环与后置自减边界条件得出结果为-1,第2题说明整数与字符串拼接的隐式转换,第3题强调标识符不能以#、~等特殊字符开头,第7题区分数组与Object类型转换的编译时限制,第10、11题梳理try-catch-finally及异常匹配的完整输出流程。除了解析,文档以单项选择题形式呈现,可直接作为模拟试卷限时自测,帮助熟悉企业笔试题型。这些题目覆盖面试高频基础点,既能帮助初学者自检语法掌握程度,也能让求职者在笔试前快速查漏补缺,巩固面向对象与异常处理等关键概念,为技术面试做好充分准备。

1. 一套Java初级笔试题:先搞清它考什么,再决定值不值得刷

朋友发来一套某航数码的Java初级笔试题,20道单选加4道上机。我第一反应是"初级题能难到哪去",结果逐题对完答案才发现,这套题几乎把Java新人最容易翻车的语法点全挑出来了:do-while后置自增自减、字符串拼接顺序、变量命名合法性、抽象方法大括号、继承初始化顺序、异常与finally、synchronized互斥锁,外加单例模式和Oracle部门员工表的SQL。适合两类人:一类是投Java初级岗前想自测基础的人,另一类是负责带新人、需要一份入职摸底卷的老手。刷这套题的价值不在题目本身,而在那些"一看就会、一写就错"的细节上。

2. 选择题逐题拆:循环边界、字符串拼接与继承初始化里的送命题

2.1 循环与作用域:不是算出结果,而是先看能不能编译

第1题是整套题里最容易算错的一道:

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

很多人的推导是:10除以2得5,5大于1成立,i自减成4;4除以2得2,2大于1成立,i自减成1;1除以2得0,循环结束,输出0。这个推导错在把i-- > 1理解成"先自减再比较"。实际上后置自减的语义是:先拿 i 的当前值参与比较,比较完再让 i 减1。正确推演是:

  • 第1轮:i=10,进入循环体后 i=5;判断 5>1 成立,然后 i 变成4。
  • 第2轮:i=4,进入循环体后 i=2;判断 2>1 成立,然后 i 变成1。
  • 第3轮:i=1,进入循环体后 i=0;判断 0>1 不成立,然后 i 变成-1。

所以输出-1。这类题目唯一的稳妥做法是拆开:把while (i-- > 1)改成while (oldValue(i) > 1),或者在心里先写一行boolean flag = i > 1; i = i - 1;,再决定循环是否继续。千万别在脑里口算,后置自减配合除法的循环,十个人九个会算错。

第6题则换了个考法,问for (int i=0; i<=10; i++) { if (i>6) break; }之后System.out.println(i)输出什么。正确结论是 Compilation fails,因为 i 声明在 for 的初始化语句里,作用域只在循环块内,循环结束 i 就不可见了。这道题真正的考点不是break,而是变量作用域。很多候选人只盯着"i到7就break",忽略了编译都过不了。把 i 改成在 for 之前声明,结果才是7。

第7题是关于数组和Object转型的:

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

这段代码完全合法:int数组是Object的子类型,引用赋值给Object没问题,向下转型也是允许的,foreach遍历int数组也没问题,输出1 2 3。这套题的第7题选项里有两个C,明显是排版笔误,但题目本身不复杂。遇到数组转型题,记住"向上转型自动、向下转型要强转、元素访问不受影响"就够了。

2.2 字符串、命名与抽象方法:语法题的三个经典误导

第2题问String s = "Hello" + 9 + 1;的结果,答案是Hello91,不是Hello10。原理是Java字符串拼接从左到右执行,一旦左边出现String,后面的加号就变成字符串连接符,9先被转成"9",再拼上"1"。如果把题目改成9 + 1 + "Hello",结果就是10Hello,因为前两个加号先做整数加法。这类题没技巧,记住"字符串拼接从左到右,遇到String就变拼接"。

第3题考标识符规则:

String #name = "Jane Doe"; int $age = 24; Double _height = 123.5; double ~temp = 37.5;

Java标识符只能以字母、下划线、美元符开头,#和~都不能开头,所以有2处编译错误。容易误判的是$age和_height,它们虽然看着别扭,但完全合法。这道题提醒一件事:平时写代码别用这种风格,但考试时要认得它们是合法的。

第4题问abstract的叙述,正确答案是"声明抽象方法不可写出大括号"。抽象方法没有方法体,声明结束就是分号,写了{}反而变成普通方法。另外要注意abstract不能修饰字段,修饰字段的是final或volatile那一类。每当看到"A说abstract可修饰字段",直接排除。

2.3 继承与初始化顺序:父子构造器和static的执行次序

第8题输出BaseBase:

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,然后再new Base()又打印一次。注意是连续的BaseBase,中间没有换行。

第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); } }

答案是Super1,Super3,Super2,Sub2,。执行顺序很容易搞混,拆开看就清晰了:

  • 类加载时先初始化静态成员,s1 = new Super(1)打印 Super1。
  • main里执行new Sub(2),创建对象前先初始化实例成员,s2 = new Super(3)打印 Super3。
  • 然后进入Sub构造器,首行super(i)即Super(2),打印 Super2。
  • 最后执行Sub构造器主体,打印 Sub2。

这类题我在带人的时候反复强调一个口诀:类加载先静态,new对象先成员,再走父类构造,最后子类构造体。静态成员只初始化一次,实例成员每次new都会执行,父类构造器永远在子类构造体之前跑完。

第13题是一个隐藏的编译错误:

abstract class MineBase { abstract void amethod(); static int i = 1; } public class Sub extends MineBase { // 没有实现 amethod() }

Sub继承了一个抽象类,却没有实现amethod(),所以Sub本身必须是抽象类。题目里Sub不是abstract,直接编译失败。至于数组int[5]默认输出00000,那是在能编译的前提下才讨论的事。第9题的值传递也很经典:modify(a)里的a是形参,方法内a++改的是副本,静态变量a保持0。

2.4 异常、线程与设计原则:catch的顺序和synchronized的用途

第10题的异常流程值得完整推一遍:

try { method1(); // 内部发生 10/0 System.out.print("A"); } catch (Exception ex) { System.out.print("B"); } finally { System.out.print("C"); } System.out.print("D");

method1里除零抛出ArithmeticException,try剩余语句不执行,进入catch打印B;finally无论是否异常都执行,打印C;finally执行完后,try-catch语句块之后的代码继续执行,打印D。最终输出BCD。这道题常见错误是漏掉D,以为程序在catch或finally后就结束了,实际只要没有System.exit,就会继续往下走。

第11题考的是异常捕获的匹配顺序:

throw new Exc1();

Exc1是Exc0的子类,catch (Exc0) 能捕获子类异常,输出 Ex0 caught。注意如果先写catch (Exception e),Exc1也会被匹配,Java按catch块从上到下找第一个能匹配的异常类型。实际开发里,子类异常catch要写在父类异常前面,否则编译器直接报"已经捕获到"错误。

第17、18、20题是线程和关键字的基础:run()定义线程执行体,start()启动线程;synchronized用来加互斥锁;static修饰的方法可以通过类名直接调用。第19题问构造函数描述错误的是"A:构造函数的返回类型只能是void型"。构造函数压根不能声明返回类型,写void也是语法错误,这是初学Java最容易被误导的一句话。第14、15、16题分别对应开闭原则、设计模式两大主题、继承是白盒复用——其中第16题说"继承对父类的修改不会影响到子类"是错的,白盒复用的特点恰恰是父类一改,子类跟着变。

3. 上机题实现:字符串排序、递归、SQL和单例的标准答案

3.1 字符串"100,10,1,0,15,20"排序:先转int再排

第21题要求把"100,10,1,0,15,20"中的数字升序排列,输出0,1,10,15,20,100。这道题最直接的坑是:字符串自带排序是字典序,直接Arrays.sort会排成0,1,10,100,15,20,完全不对。必须先把字符串拆成数字再排序:

import java.util.Arrays; public class Test1 { public static String sort(String 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)); } }

逻辑说明:split(",")按逗号切分,得到字符串数组;trim()是保险操作,防止输入数据里带空格;Integer.parseInt把每个字符串转成int,这样排序才是数值序;最后用StringBuilder拼接,避免循环里用+=产生大量临时对象。参数说明:方法签名里的args就是入参字符串,和main的String[] args不冲突,但建议换名input更清晰。如果面试官追问排序复杂度,直接答Arrays.sort对基本类型用的是双轴快速排序,平均O(n log n),初级岗位答到这里就够。

3.2 斐波那契第30位:递归能跑,但要有优化意识

第22题给了递归实现,求数列 1、1、2、3、5、8... 的第30位数:

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

逻辑说明:index <= 2是递归出口,前两位都是1;后面每位等于前两位之和。第30位算出来是832040,这个数字可以当作自检答案。这段代码能跑,但效率很差,重复计算量是指数级的。面试官大概率会追问"能不能优化"。

我一般会顺手写一个带缓存的版本:

static int computeFast(int index, int[] cache) { if (index <= 2) { return 1; } if (cache[index] != 0) { return cache[index]; } cache[index] = computeFast(index - 1, cache) + computeFast(index - 2, cache); return cache[index]; }

在main里先int[] cache = new int[31];再调用computeFast(30, cache)。空间换时间,每个下标只算一次,复杂度从O(2^n)降到O(n)。初级岗位能用递归写出正确结果已经及格,能主动给出缓存版本就是加分项。

3.3 Oracle两张表的SQL:三连查里的常见错误与正确写法

第23题给了部门表DEPT和员工表EMP,三个查询分别考察子查询、分组过滤、连接聚合。先看原题给出的答案,第2题写的是select job from EMP where sal>2000,第3题用的是avg(d.Deptno),这两个都有明显问题。

第一问:列出薪金比张平多的所有员工姓名。正确写法是用子查询取出张平的薪金,再做行比较:

SELECT Ename FROM EMP WHERE Sal > (SELECT Sal FROM EMP WHERE Ename = '张平');

逻辑说明:内层子查询先算出张平的工资2800,外层再筛掉自己、留下大于2800的人。需要注意Ename = '张平'用的是单引号,Oracle里字符串常量必须单引号,双引号会被当成列名。结果里张平本人不会出现,因为2800 > 2800 不成立。

第二问:列出最低薪金大于2000的各种职位。关键词是"各种职位"和"最低薪金",这是典型的聚合条件:

SELECT Job FROM EMP GROUP BY Job HAVING MIN(Sal) > 2000;

逻辑说明:先按job分组,然后用HAVING MIN(Sal) > 2000过滤组。原题答案select job from EMP where sal>2000的错误在于:where是在分组前对行过滤,会把所有工资大于2000的员工列出来,然后职位重复,而且业务员里王一平虽然个人工资1850不高,但另一个业务员孙小平2100是被原答案漏掉的?不,孙小平2100大于2000会被原答案包含,于是结果里就出现了业务员,违背了"最低薪金大于2000"的语义。分组后业务员组的最低薪金是1850,不满足条件。HAVING和WHERE的差别,是这道题最想考的。

第三问:列出每个部门的部门名称、员工数量和平均薪水。注意两张表的关联字段是deptno,连接条件要写在on里:

SELECT d.Dname AS 部门名称, COUNT(e.Empno) AS 员工数量, AVG(e.Sal) AS 平均薪水 FROM DEPT d JOIN EMP e ON e.Deptno = d.Deptno GROUP BY d.Dname;

逻辑说明:以部门表为左表,员工表为右表,按部门编号关联;COUNT(e.Empno)统计每个部门的员工数;AVG(e.Sal)统计平均薪水。原题答案里avg(d.Deptno)是明显的笔误,平均薪水肯定取EMP表的Sal字段,而不是部门编号。另外原答案把连接条件写在where里,在Oracle里也能跑出内连接效果,但规范写法是放到ON子句。如果要求市场部(无员工)也显示出来,就得改成左连接LEFT JOIN EMP e ON e.Deptno = d.Deptno,初级岗位能想到这里已经很稳。

3.4 单例模式:先写对再防并发

第24题要求实现单例。原题答案已经给了懒汉式的基本框架:

class Singleton { private static Singleton s; private Singleton() {} public static Singleton getInstance() { if (s == null) { s = new Singleton(); } return s; } }

逻辑说明:构造方法私有,外部无法直接new;静态变量s持有唯一实例;getInstance在第一次调用时创建。这段代码单线程下没问题,但多线程环境下两个线程同时进入if (s == null),可能各new一次。初级笔试能写出这个版本算合格,但如果面试官让你"保证线程安全",我一般会答synchronized方法版:

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

再进一步就是双重检查锁加volatile:

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

写的时候要说清楚:外层判空避免每次调用都抢锁,内层判空防止并发重复创建,volatile保证s的可见性和禁止指令重排。初级岗位不要求写出双检锁,但能写出synchronized版本并说明理由,就已经把这道题的边界吃透了。

4. 笔试避坑记录:五条最容易翻车的踩坑经验

4.1 现象:do-while最后输出-1,自己算出来却是0或1

第1题我见过好几个人信誓旦旦选0,理由是"1除以2等于0,循环结束,i就是0"。原因就是后置自减的比较顺序搞反了,把i-- > 1当成先减后比。解决方法是把后置自减拆开:比较时用的是i的旧值,比较完才自减。我自己的习惯是做题时在草稿纸上画一张三轮的小表,列i的当前值、进入循环后的值、比较结果、自减后的值,四列写完答案自然出来。这个习惯也从笔试沿用到了写代码:凡是遇到i--参与的判断,一律单独一行,不写在条件里。

4.2 现象:for循环里的变量,循环外一用就编译失败

第6题看上去在考break,实际上考作用域。有人算出7,有人算出11,却没发现System.out.println(i)根本不在for的作用域内。原因是对for的语法结构不敏感,for (int i=0; ...)里声明的i,生命周期从for开始到for结束。解决方法是拿到代码先扫一遍变量声明位置,再看循环逻辑。这类题在笔试里出现频率很高,不只是Java,C和JavaScript也爱考。我遇到这种代码会先问自己一个问题:"这段代码能编译吗?"而不是"这段代码输出什么?",顺序反了就会掉坑。

4.3 现象:子类构造时,static和父类构造器的输出顺序全乱

第12题的输出顺序是静态成员、实例成员、父类构造器、子类构造体,这个顺序新手普遍记不住。原因在于把"类加载"和"对象创建"混为一谈。我的记忆方法是:静态成员属于类,类第一次被加载时只初始化一次,所以最早;实例成员属于对象,每次new都执行,所以紧随其后;构造器是对象创建的最后一个步骤,父类又先于子类。以后遇到初始化顺序题,第一件事就是先找static字段,把它从对象创建流程里单独摘出来,剩下的再按"实例字段→父类构造→子类构造体"推。

4.4 现象:SQL用where筛出所有职位,结果面试官直接摇头

写"最低薪金大于2000的职位"时,不少人第一反应是where sal>2000,然后交卷。原因是没有抓住"各种职位"这个聚合语义:最低薪金是一个组级别(job级别)的条件,不是行级别条件。行过滤用where,组过滤必须用having。解决方法是看题目里有没有"各种""每个""平均""最低"这类词,有就基本锁定group by加having。这道题还有一个后续追问点:为什么业务员职位没出现在结果里?因为业务员组的最低薪金1850不满足大于2000。能把业务员排掉的原因讲清楚,这道SQL题才算真会。

4.5 现象:单例写完了,被追问"多线程下会不会new出两个"

第24题原答案是非线程安全的懒汉模式。很多人笔试过了,面试时被问"两个线程同时调用getInstance会发生什么"就卡住。原因是他只知道单例的写法,不知道为什么这么写。解决方法是把线程安全问题分三层答:第一层,两个线程同时判空,都可能进入if;第二层,解决办法是synchronized方法或同步块;第三层,同步块里还要再判空一次,否则后进入的线程依然会再new一次。从那以后我教人写单例,都强制要求在白板上画出两个线程的执行时序图,画得出来才叫会。

5. 把一份笔试题变成自查清单:做对之后还要能讲清

这套题做完,如果只是对答案、改错号,那价值就只发挥了一半。我习惯把它变成一份按考点分类的自查清单:语法类、面向对象类、异常类、并发类、SQL类。每道错题记录三个字段:考点一句话、我当时的错误答案、正确思路。比如第1题写"后置自减的比较时机",第6题写"for变量作用域",第12题写"静态成员先于对象创建",这样复盘时不用重看整张卷子。

自查清单做完,还有一步更狠的:挑三道题改写成变体。第6题改成int i; for (i=0; i<=10; i++) { if(i>6) break; }再输出i,结果从编译失败变成7;第12题在main里第一行加new Super(9);,整个输出顺序都会变;第21题把字符串换成带前导零的"001,02,10",考验parseInt的容错。改完再自己算一遍,算对了才算真正掌握了这个考点。

最后一步是口头讲题。找一面镜子或者对着录音工具,把第10题异常流程、第23题having和where的区别,用两分钟讲清楚。讲的时候卡壳的地方,就是理解还模糊的地方。这个习惯来自一次真实的笔试复盘:我做对了所有选择题,面试官让我讲第12题,我却从"static s1"开始就说不利索,当场露馅。从那以后我每次刷笔试题,都强制自己走一遍"复现→改题→讲题"的流程。笔试不只是选对答案,能讲清楚边界和原因的答案,才是真正属于你的答案。希望帮到你。

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

返回列表