简介:这份金航数码Java笔试题文档面向Java初级岗位求职者与在校学习者,聚焦基础语法、面向对象和异常处理等笔试高频考点,可用于自测、考前复盘与查漏补缺。压缩包共1个doc文件,约98KB,以单项选择题为主,覆盖循环控制、字符串拼接、变量命名、抽象方法、继承、数组与Object类型转换以及try-catch-finally执行顺序等内容,并涉及do-while循环终止值、表达式自动转字符串、抽象方法不可带方法体、子类与父类构造器调用先后、局部变量与静态变量作用域差异等细节。题目附有答案和解析,部分题目还说明代码输出、编译错误位置与异常传播过程,便于读者理解Java类型安全和继承机制。已有396人学习,适合需要熟悉企业初级Java笔试题型、检验基础掌握程度并梳理易错知识点的读者。
1. 先看清这张卷子:20 道选择加 4 道上机,分值全压在基础语法上
这套 Java 初级笔试题的结构很朴素:20 道单项选择,加 4 道上机题,两个小时不到就能交卷,但它筛人的方式很直接——选择题专挑语法细节上的问题,上机题专挑你能不能写出真正能跑通的代码。拉开差距的地方,往往在于 do-while 里那个后置自减、for 循环里变量的作用域、静态字段和实例字段谁先初始化这类看着眼熟、答起来心虚的细节;与是否会用 Java 关系不大。从考点分布看,循环与流程控制、继承与抽象、构造器执行顺序、异常匹配、线程基础这五块占了选择题的七成,剩下的分散在变量命名、数组转型、设计原则和设计模式上。上机四道是字符串排序、递归、三道 SQL、手写单例,全是初级岗入职第二周就要上手的东西。适合工作一年以内、转行自学、以及代码写得动却总在细节题上丢分的人。
2. 语法与流程控制:五道选择题加一道排序题,全是细节分
流程控制是这张卷子里性价比最高的一块。它不考你会不会写 for,考的是你能不能在脑子里把变量一次一次地改对、把作用域一刀切准。这一章的五个知识点,几乎每一道都有人答错,错的原因还不是不会,是「想当然」——看着见过,就直接选了最有道理的那个选项。上机第一题表面是排序,实际考的是你有没有意识到「字符串比较」和「数字比较」是两回事。
2.1 do-while 与后置自减:i-- 到底在哪一步减
原题是这样的:
int i = 10; do { i = i / 2; } while (i-- > 1); System.out.println(i); // 输出 -1很多人卡在i-- > 1这个表达式上。后置自减的语义是「先拿当前值参与比较,比较完再减一」,所以判断和减一是两步,不是一步。逐轮走一遍:
| 轮次 | 进入循环体时的 i | 循环体执行后 | 判断i-- > 1 | 判断后 i 变成 |
|---|---|---|---|---|
| 1 | 10 | 5 | 5 > 1 为真 | 4 |
| 2 | 4 | 2 | 2 > 1 为真 | 1 |
| 3 | 1 | 0 | 0 > 1 为假 | -1 |
循环退出时 i 已经是 -1,System.out.println(i)打印 -1。答案选 D。
这道题的失分点不在循环,而在最后一轮:判断为假的时候,自减动作已经发生了。面试官出这种题,就是想看你会不会把「条件判断」和「副作用」分开看。同类变体还有i++ > 1、++i > 1,只要记住前置是先加减再比较、后置是先比较再加减,画一张上面那样的表,基本不会翻车。
提示:考场上一旦遇到 do-while 加自增自减,别硬想,直接在草稿纸上列「进入时 / 执行后 / 判断时 / 判断后」四列,三行就能算完。
2.2 字符串拼接的从左到右:"Hello"+9+1 为什么不是 Hello10
String s = "Hello" + 9 + 1; System.out.println(s); // Hello91+在 Java 里是左结合的,而且一旦左边出现了字符串,后面的操作数就全部按字符串处理。所以执行顺序是:"Hello" + 9得到"Hello9",再"Hello9" + 1得到"Hello91"。整数 9 在这里不是被「加」上去的,是被转成字符串「拼」上去的。
对比一下这几种写法,考场上换个顺序就是另一道题:
System.out.println(9 + 1 + "Hello"); // 10Hello,先算加法再拼字符串 System.out.println("Hello" + (9 + 1)); // Hello10,括号改变优先级 System.out.println('A' + 1 + "x"); // 66x,char 参与算术运算变成 int第三种是最阴的:char和数字相加不会拼字符串,会按字符编码做加法。所以看到+,先从左往右扫一遍,看第一个字符串出现在哪个位置,它左边全是算术运算,它右边全是字符串拼接。
2.3 标识符规则:那道题到底错了两处还是三处
原题给了四个声明,问有几处编译错误:
String #name = "Jane Doe"; // 非法,# 不能出现在标识符中 int $age = 24; // 合法,$ 是允许的 Double _height = 123.5; // 合法,_ 是允许的,且自动装箱 double ~temp = 37.5; // 非法,~ 不能出现在标识符中答案是 2 处,选 C。
Java 标识符的规则可以压缩成三句话:首字符可以是字母、下划线_、美元符$,但不能是数字;后续字符可以再加数字;不能是关键字或字面量true/false/null。这里的字母范围比想象中宽,中文、日文在 Java 里都是合法的标识符字符,所以int 年龄 = 18;是能编译的——虽然没人这么写。
最容易错的是$age和_height。不少人凭感觉认为$和_是「特殊符号不能用」,实际上它们恰恰是除了字母之外仅有的两个合法开头。反过来,#和~才是真的不行。这道题真正考的是你有没有记过规则原文,而不是凭代码风格猜。
2.4 for 循环的作用域:人人都答 7 的那道题
for (int i = 0; i <= 10; i++) { if (i > 6) break; } System.out.println(i);选项里有 7、10、11、编译失败。很多人脑子里过了一遍循环,i 从 0 涨到 7 时 break,于是选 7。正确答案是「编译失败」,选 D。
原因只有一句话:int i声明在 for 的初始化部分,它的作用域被限定在 for 语句本身,包括循环体和条件表达式,出了这个范围就不可见。所以在循环外面println(i)是「找不到符号」,编译期直接报错,根本轮不到运行。
凡是遇到「变量声明在 for / if / while 头部,却在语句块外面被使用」的题,第一反应就该是作用域问题,而不是去算值。这类题在笔试卷里出现频率极高,因为它能一次性区分「会背语法」和「真的理解作用域」两种人。
顺带记住另外两个相关规则:方法体内声明的局部变量必须先赋值再使用,否则编译错误;而类的成员变量(包括静态字段)有默认值,int是 0,引用类型是 null,这一点在第 4 章那道静态变量题里会再用一次。
2.5 数组转型:Object 接一个 int 数组为什么合法
Object obj = new int[]{1, 2, 3}; // 合法,数组是对象 int[] someArray = (int[]) obj; // 合法,向下转型 for (int i : someArray) { System.out.print(i + " "); // 1 2 3 }这道题的三个选项分别指向三行,正确答案是第一行、第二行、第三行都不报错,输出1 2 3。
关键认知是:Java 里数组是对象,所有数组类型都直接或间接继承自Object,所以用Object接住一个int[]完全合法。第二行的强制转换也合法,因为运行时 obj 指向的确实是int[],向下转型不会抛ClassCastException。如果中间有人把它转成String[],那才会在运行期炸掉。
真正会编译失败的写法是把基本类型数组直接赋给包装类型数组,比如Integer[] arr = new int[]{1,2,3};——数组类型之间没有自动装箱,这一点和第 2.3 节里Double _height = 123.5的单值装箱是两码事。
2.6 上机第一题:字符串数字排序,别用字典序
题目要求把"100,10,1,0,15,20"里的数字升序输出成0,1,10,15,20,100。看清楚,这是按数值排,不是按字符串排。如果你直接Arrays.sort(String[]),得到的是0,1,10,100,15,20,因为字符串比较是逐字符比编码,"100"的第一个字符'1'比"15"的'1'相等,接着比'0'和'5',于是 100 排到了 15 前面。
标准写法:
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()); // 去空格再转 int } Arrays.sort(nums); // 对 int 排序才是数值序 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) { System.out.print(sort("100,10,1,0,15,20")); } }几个参数和细节要说清楚。split(",")的参数是正则表达式,逗号本身不是元字符所以安全,但如果分隔符是|或.,就必须写成"\\|"、"\\."。trim()是为了防止输入里有"100, 10"这种带空格的写法,parseInt遇到空格会直接抛NumberFormatException。StringBuilder而不是+=,是因为字符串不可变,循环里用+=每次都新建对象,长度一大就是明显的性能坑。
还有一种更省事的写法,用Integer::parseInt配合流:
String result = Arrays.stream(args.split(",")) .map(String::trim) .map(Integer::parseInt) .sorted() .map(String::valueOf) .reduce((a, b) -> a + "," + b) .orElse("");两种都能过,但笔试时建议写第一种。理由很实际:阅卷的人看得懂,你自己也不容易漏掉import。上机题的第一原则是「先跑通,再优雅」。
3. 面向对象:抽象、继承、构造器,三道输出题一道概念题
这一章是整张卷子里最容易「以为自己会」的部分。抽象类和接口的区别能背一堆,真给一段带静态字段、实例字段、父子构造器的代码让你说输出顺序,就卡住了。下面按考点从概念到执行顺序排,最后落到上机那道单例。
3.1 抽象方法:四个说法里只有一个对
原题问关于 abstract 的描述哪条正确,四个选项分别是「abstract 可修饰字段、方法和类」「抽象方法的 body 必须用大括号包住」「声明抽象方法大括号可有可无」「声明抽象方法不可写出大括号」。
正确答案是第四个。逐条拆:
abstract可以修饰类和方法,但不能修饰字段。字段没有「待实现」的概念,一个变量要么有值要么没值,抽象它没有意义。编译器对abstract int x;会直接报错。
抽象方法的定义以分号结尾,没有任何方法体,所以大括号既不是可有可无,也不是必须包住,而是压根不能出现。写成abstract void run() { }会编译失败,因为它变成了「有实现但被声明为抽象」,语义冲突。
这两条合起来就是一句话:抽象方法只声明「能做什么」,把「怎么做」留给子类。类里只要有一个抽象方法,这个类就必须声明为 abstract,否则编译不过。
提示:选择题里凡是出现「可有可无」「都可以」这类模糊表述的选项,八成是错的。Java 语法在这一点上很硬,要么必须写,要么不能写。
3.2 单继承与白盒复用:两个概念题的正确答案
关于继承,卷子里有两道概念题,答案分别是 C 和 D。
第一道问继承关系,正确说法是「一个子类可以有一个父类,但一个父类可以有多个子类」。Java 是单继承,一个类只能 extends 一个类,但一个父类可以被无数个子类继承。注意别和接口混淆,一个类可以 implements 多个接口,那是另一条规则。
第二道问关于继承表述错误的是哪条,答案是「继承本质上是白盒复用,对父类的修改不会影响到子类」。这句话前半句对、后半句错。继承确实是白盒复用,子类能看到父类的实现细节,破坏了封装;也正因为如此,父类一改,所有子类都可能受影响,这叫「继承的脆弱基类问题」。所以选项里说「不会影响」是明显错误的表述。
配套的还有一道设计原则题:Open-Close 原则的含义是「对扩展开放,对修改关闭」,选 A。这条原则和继承那道题其实是一体两面——正因为继承会让父类的修改波及子类,设计时才提倡多用组合、少用继承。卷子里还有一道问设计模式两大主题的题,答案是「对象组合与类的继承」,同一个知识点换个问法。
3.3 构造器:三个说法里错的那个最像对的
原题问关于构造函数描述错误的是哪条,答案是「构造函数的返回类型只能是 void 型」。
构造器没有返回类型,连 void 都不写。一旦你写了 void,它就不再是构造器,而是一个恰好和类同名的普通方法:
class Demo { Demo() { } // 构造器,没有返回类型 void Demo() { } // 普通方法,和类同名但带 void }这两者可以同时存在,编译器不会报错,但new Demo()只会调用第一个。这个坑在真实项目里也常见,有人写了个void init()想当构造器用,结果对象创建完字段全是默认值。
另外两条说法是对的:构造器名必须与类名相同;主要作用是完成对象初始化;创建对象时由系统自动调用。还有一条隐含规则要记住:如果类里一个构造器都没写,编译器会送你一个无参构造器;但只要你写了任意一个有参构造器,这个免费的无参构造器就没了,子类如果没显式调super(...)就会编译失败。
3.4 初始化顺序:那道 Super1、Super2、Super3、Sub2 的题
先看简单版本:
class Base { Base() { System.out.print("Base"); } } public class Alpha extends Base { public static void main(String[] args) { new Alpha(); // 隐式调用 super(),打印 Base new Base(); // 打印 Base } }输出BaseBase。new Alpha()时,Alpha 的构造器第一件事是调用父类无参构造器,所以先打印父类的Base,Alpha 自己没有写构造器,编译器补了一个默认的,里面没有打印语句。
再看进阶版本:
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); // 实例字段,每次 new 时执行 Sub(int i) { super(i); System.out.print("Sub" + i + ","); } public static void main(String[] args) { Super c1 = new Sub(2); } }输出是Super1,Super2,Super3,Sub2,,选 A。
顺序这样推:JVM 先加载 Sub 类,执行静态初始化,s1 = new Super(1)打印Super1。然后进入 main,new Sub(2)触发实例创建,第一步调用super(2),打印Super2;父类构造返回后,才轮到本类的实例字段初始化,s2 = new Super(3)打印Super3;最后执行 Sub 构造体,打印Sub2。
把这条链路背成一句话:静态先于实例,父类先于子类,字段先于构造体。四步顺序任何一步调换都是错答案。考场上遇到这类题,先在草稿上写「静态 → 父构造 → 实例字段 → 子构造体」四行,再往里填打印内容,比在脑子里模拟靠谱得多。
3.5 抽象类没实现抽象方法:编译期就挂了
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]); } } }这道题很多人会去算数组默认值,得出00000就选了。正确答案是编译错误。
原因:Sub 继承了抽象类 MineBase,但没有实现amethod(),而 Sub 自己又不是抽象类。Java 的规则是「非抽象类必须实现所有继承来的抽象方法」,违反这条在编译期就报错,跟 main 方法里写了什么完全无关。
这道题的启发是:看到继承抽象类的代码,先去数抽象方法有没有被实现,再去看输出。如果 Sub 老老实实写了void amethod() {},那答案就是00000,因为new int[5]的每个元素默认是 0,循环里用的是父类的静态字段 i,从 0 走到 5 正好打印五个 0。一个方法的有无,把答案从 A 换成了 C。
3.6 上机第四题:手写单例,别只写个 if
原题给的骨架有几个明显问题:getInstance后面漏了括号,没考虑多线程,字段也没加volatile。
public class Singleton { // volatile 禁止指令重排,避免别的线程拿到「半成品」对象 private static volatile Singleton instance; // 私有构造器,堵死外部 new private Singleton() { } public static Singleton getInstance() { if (instance == null) { // 第一次检查,避免每次都加锁 synchronized (Singleton.class) { if (instance == null) { // 第二次检查,防止重复创建 instance = new Singleton(); } } } return instance; } }这段叫双重检查锁定,四个要素缺一不可:私有构造器、静态私有字段、公有的静态获取方法、方法名后面的那对括号。volatile的作用是禁止instance = new Singleton()这三步(分配内存、初始化、赋值引用)被重排,否则另一个线程可能在第一次检查时看到一个非 null 但还没初始化完的对象。
如果面试官只要求「写个能用的」,饿汉式更短也更安全:
public class Singleton { private static final Singleton INSTANCE = new Singleton(); private Singleton() { } public static Singleton getInstance() { return INSTANCE; } }类加载时就创建,天生线程安全,缺点是没法延迟加载。笔试时如果没特别说明,写饿汉式不容易出错;如果题目强调「懒加载」或「线程安全」,就上双重检查。
4. 输出题与递归:异常、线程、传参、递归四类题怎么答
这一章的题有个共同特征:代码不长,但每一步都有陷阱,而且陷阱都藏在「看起来应该这样」的直觉里。try 里抛异常了 finally 还执行吗?catch 到父类还是子类?方法里改了参数,外面变不变?递归第 30 项会不会爆栈?把这四类题的判断路径固定下来,考场上就不用现推。
4.1 try-catch-finally:BCD 是怎么来的
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"); // finally 之后继续往下走 } public static void method1() { int j = 0; int i = 10 / j; // 抛 ArithmeticException }输出BCD,选 D。
推演路径:进入 try,调用 method1,10 / 0抛出ArithmeticException,这一行之后的print("A")永远不会执行;异常向上抛,被catch (Exception ex)接住,打印 B;不管有没有异常,finally 都执行,打印 C;finally 结束后,异常已经被处理,方法继续往下,打印 D。
三个必须记住的规则:try 块里异常点之后的代码不执行;finally 只要 JVM 没退出就一定会执行;catch 匹配成功之后,控制流不会回到 try 里原来那行继续。
如果 catch 块里写了return,finally 依然会执行,而且 finally 里的return会覆盖 catch 里的返回值——这是另一道高频题,答题时只要记住「finally 优先级最高」就能推。
4.2 异常匹配的就近原则:为什么抓到的是 Exc0
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"); } } }输出Ex0 caught,选 A。
这里抛出的对象类型是 Exc1,但 catch 是按「能否赋值」来匹配的:Exc1 是 Exc0 的子类,可以赋给 Exc0 类型的变量,所以第一个 catch 就接住了。catch 块从上往下依次匹配,第一个匹配成功的就执行,后面的不再看。
这也解释了一条编译规则:如果两个 catch 的顺序写反,先catch (Exception e)再catch (Exc0 e0),编译器会直接报「异常已被捕获」,因为父类在前面会把所有子类异常都拦下来,后面的 catch 永远到不了。写多 catch 时,子类异常必须写在父类前面。
4.3 线程:run() 是执行体,synchronized 是互斥锁
卷子里有两道线程题,考的都是定义层面的东西。
问「哪个方法用于定义线程的执行体」,答案是run()。start()是启动线程的方法,它会另起一个调用栈去执行run()里的代码;init()是初始化相关的方法;synchronized()根本不是方法名。这两者最直观的区别是:直接调run()就是一次普通方法调用,还在当前线程里跑,只有start()才是真的开新线程。
问「哪个关键字可以为对象加互斥锁」,答案是synchronized。transient是序列化时跳过字段,static是类级别,serialize不是关键字。synchronized修饰实例方法时锁的是 this,修饰静态方法时锁的是类的 Class 对象,修饰代码块时锁的是括号里那个对象——这三种锁的范围不同,是面试里常追问的下一层。
4.4 传参:modify(a) 为什么改不动静态变量
private static int a; public static void main(String[] args) { modify(a); System.out.println(a); // 0 } public static void modify(int a) { a++; // 改的是形参,不是静态字段 }输出 0,选 A。
Java 只有值传递。modify(a)传进去的是静态字段 a 的副本,方法里的a++改的是形参这个局部变量,方法一返回就没了。静态字段 a 从头到尾没被赋过值,保持默认值 0。
这道题的迷惑点在于形参名和字段名都叫 a,看着像在改同一个东西。判断方法很简单:看方法签名里的参数是不是基本类型或引用。基本类型传的是值的副本,改不动外面;引用类型传的是地址的副本,通过它可以改对象内部状态,但让它指向新对象也影响不到外面。
static void change(int[] arr) { arr[0] = 99; } // 能改,改的是堆里的对象 static void reassign(int[] arr) { arr = new int[]{9}; } // 改不动,只是形参换了指向提示:凡是题目里出现「方法内修改参数、main 里打印原变量」,默认答案就是没变,除非参数是数组或对象且方法里动的是它的成员。
4.5 上机第二题:递归求第 30 位,先写终止条件再写递推
题目规则是 1、1、2、3、5、8、13、21、34……求第 30 位,要求用递归。
public class TestNum { static int computNum(int index) { // 终止条件:第 1、2 位都是 1 if (index <= 2) { return 1; } // 递推关系:后一项等于前两项之和 return computNum(index - 1) + computNum(index - 2); } public static void main(String[] args) { System.out.println(computNum(30)); // 832040 } }递归题只看两件事:终止条件写没写、递推关系对不对。这道题的终止条件是index <= 2返回 1,递推是f(n) = f(n-1) + f(n-2)。如果终止条件写成index == 1,传 0 进去就会无限递归直到StackOverflowError;如果写成index < 2,第 2 位就会落到递推分支里算出f(1) + f(0),同样出问题。
第 30 位的结果是 832040,int装得下;但到第 47 位就超过int上限了,再往后必须换long甚至BigInteger。这也是常见追问:为什么线上算斐波那契要用迭代或记忆化?
因为朴素递归的时间复杂度是 O(2 的 n 次方),算第 30 位要调用上百万次,算第 45 位就肉眼可见地卡。改造方法很简单,加一个数组把算过的值存下来:
static long[] memo = new long[100]; static long fib(int n) { if (n <= 2) return 1; if (memo[n] != 0) return memo[n]; // 算过就直接取 memo[n] = fib(n - 1) + fib(n - 2); return memo[n]; }笔试时先交朴素版没问题,但如果题目后面跟一句「考虑性能」或者「n 可能很大」,就要主动把记忆化写出来。面试官看的是你会不会想到重复子问题这层。
5. 易错点:SQL 三题写错、单例漏括号,这些分丢得最冤
前面四章按知识点铺开,这一章换个角度,专挑那些「写出来像对的、跑起来是错的」地方。这些错误有个共同点:不是不会,是差一口气。考场上时间紧,差的那一口气往往就变成了十几分。
5.1 三道 SQL:分组、聚合、连接各埋一个坑
先看表结构。DEPT 有 deptno、dname 两个字段,EMP 有 empno、ename、job、sal、deptno 五个字段,两表通过 deptno 关联。
第一题,列出薪金比「某员工」多的所有员工姓名。写法是子查询:
SELECT ename FROM emp WHERE sal > (SELECT sal FROM emp WHERE ename = '某员工');这题错得少,但有两个细节要注意:子查询返回的必须是单值,如果同名员工有两条记录,>就会报「子查询返回多行」;另外比较时如果对方薪金是 NULL,整个条件会变成 unknown,一条都查不出来。
第二题,列出最低薪金大于 2000 的各种职位名称。这题是重灾区,最常见的错误写法是:
-- 错误写法:筛的是「有人的薪金大于 2000 的职位」,不是「最低薪金大于 2000 的职位」 SELECT job FROM emp WHERE sal > 2000;正确写法必须先用GROUP BY按职位分组,再用HAVING对分组结果做过滤:
SELECT job FROM emp GROUP BY job HAVING MIN(sal) > 2000;WHERE和HAVING的区别是这道题的核心:WHERE在分组之前过滤行,所以它看不到聚合函数;HAVING在分组之后过滤组,所以能用MIN、MAX、AVG。题目里出现「最低」「最高」「平均」加一个比较条件,基本都是HAVING的题。
第三题,列出每个部门的部门名称、员工数量和平均薪水。错误写法通常错在聚合的字段上:
-- 错误写法:平均的是部门编号,不是薪水 SELECT d.dname, COUNT(e.empno), AVG(d.deptno) FROM emp e INNER JOIN dept d ON e.deptno = d.deptno GROUP BY d.deptno;正确写法:
SELECT d.dname, COUNT(e.empno) AS 员工数量, AVG(e.sal) AS 平均薪水 FROM emp e INNER JOIN dept d ON e.deptno = d.deptno GROUP BY d.deptno, d.dname;三个要点:AVG里放的必须是e.sal,聚合函数的对象写错等于白写;GROUP BY里要把 SELECT 中所有非聚合列都带上,d.dname不写进去在严格模式下会直接报错;连接条件写ON而不是WHERE,写在一起虽然内连接结果一样,但换成外连接就会出错。
提示:SQL 题写完后默念一遍「先连表、再分组、后过滤」,顺序错了结果就错了。另外别用中文别名当列名,有些环境会报编码问题。
5.2 五个必查失分点:错在哪、为什么错、怎么答对
第一个,构造器返回值写成 void。错在把「没有返回值」理解成「返回 void」。为什么错:构造器在 JVM 里是<init>方法,本身就没有返回类型,写上 void 就从构造器降级成普通方法,new的时候根本不会调用它。怎么答对:只要看到选项里出现「构造函数的返回类型是 void」,直接判定为错误描述;自己写代码时,构造器名字前面什么都不加。
第二个,抽象类没实现抽象方法,却去算运行结果。错在先算输出、后看结构。为什么错:非抽象类必须实现全部继承来的抽象方法,这是编译期检查,代码连 class 文件都生成不了。怎么答对:拿到带abstract的代码,第一件事是数抽象方法、看子类有没有全部实现,这一步过了再谈输出。
第三个,字符串数字排序按字典序。错在直接Arrays.sort(String[])。为什么错:字符串比较是逐字符比编码,"100"排在"15"前面,和数值大小无关。怎么答对:只要题目里是「数字」「升序」「排序」三个词同时出现,一律先parseInt转成int[]再排,输出时再拼回字符串。
第四个,SQL 里 WHERE 和 HAVING 混用。错在把聚合条件写进 WHERE。为什么错:WHERE 执行在分组之前,那一行还没有「组」的概念,MIN(sal)这种聚合结果根本不存在。怎么答对:题干里出现「最低/最高/平均/总……大于/小于」,一律GROUP BY加HAVING;只筛单行数据的条件才放WHERE。
第五个,单例的 getInstance 漏括号、漏锁。错在照着题目骨架抄,题目里public static Singleton getInstance{本身就少了一对括号。为什么错:方法声明必须有参数列表,哪怕没有参数也要写(),否则编译不过;不加synchronized的话,两个线程同时进if (s == null)会各创建一个实例,单例就不单了。怎么答对:写完单例固定检查四件事——私有构造器、静态私有字段、静态公有方法、方法名后有没有();题目提到线程就补双重检查加volatile。
6. 考场节奏:选择题四十分钟,上机题留足八十分钟
这张卷子的时间分配比知识点更值得提前演练。20 道选择题里,真正需要动笔推的只有 do-while、初始化顺序、try-catch-finally、异常匹配这四道,其余靠规则记忆就能秒答。我的建议是把选择题压到 40 分钟内,上机四道留 80 分钟,其中 SQL 和单例各 15 分钟,字符串排序 20 分钟,递归 15 分钟,剩下 15 分钟专门用来重读题目要求。
| 阶段 | 时间 | 动作 | 检查点 |
|---|---|---|---|
| 选择题第一遍 | 25 分钟 | 会做的直接选,卡住的画圈跳过 | 不留空 |
| 选择题第二遍 | 15 分钟 | 只做画圈的题,用草稿纸列表推 | 作用域、初始化顺序 |
| 上机排序题 | 20 分钟 | 先写主流程再补边界 | 数值序不是字典序 |
| 上机递归题 | 15 分钟 | 先写终止条件 | 第 30 位是否溢出 |
| 上机 SQL | 15 分钟 | 三题一起写,统一检查分组 | HAVING、聚合字段 |
| 上机单例 | 15 分钟 | 写完立刻检查括号和锁 | 四要素齐全 |
| 回读 | 15 分钟 | 重读题干的关键词 | 「最低」「所有」「升序」 |
押题方向上,选择题的重复率很高:循环里的自增自减、变量作用域、抽象方法能不能有大括号、构造器有没有返回类型、静态字段与实例字段的初始化顺序、try-catch-finally 的输出、catch 的匹配顺序、run 与 start 的区别、值传递改不动基本类型。这九个点覆盖了这张卷子选择题的绝大部分,考前一晚过一遍就够。上机题的押题更集中:字符串切分加排序、斐波那契递归、分组聚合 SQL、单例模式,四道题对应四个最基础的工作场景,换汤不换药。
有个血泪教训值得说。我以前做这类卷子,习惯先把选择题从头到尾做完再检查,结果有次在一道初始化顺序题上耗了十二分钟,等做到上机题只剩四十分钟,SQL 第三题的分组字段写错都没时间回头看,白白丢了十几分。从那以后我每次模考都强制走一遍「先扫一遍标出卡壳题、按分值分配时间、最后留十五分钟回读题干」的流程,哪怕题再简单也不跳步。这套流程不保证你全会,但能保证会的不丢分。
把上面这些题按章节过一遍,自己动手把六段代码敲一遍跑通,再去对着答案解释每一行为什么这么输出,这张卷子基本就稳了。希望帮到你。
本文还有配套的精品资源,点击获取