ARTICLE DETAIL

资讯详情

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

Java初级笔试题解析:基础语法、面向对象与上机易错点

Java初级笔试题解析:基础语法、面向对象与上机易错点

简介:这份金航数码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 变成
11055 > 1 为真4
2422 > 1 为真1
3100 > 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 位是否溢出
上机 SQL15 分钟三题一起写,统一检查分组HAVING、聚合字段
上机单例15 分钟写完立刻检查括号和锁四要素齐全
回读15 分钟重读题干的关键词「最低」「所有」「升序」

押题方向上,选择题的重复率很高:循环里的自增自减、变量作用域、抽象方法能不能有大括号、构造器有没有返回类型、静态字段与实例字段的初始化顺序、try-catch-finally 的输出、catch 的匹配顺序、run 与 start 的区别、值传递改不动基本类型。这九个点覆盖了这张卷子选择题的绝大部分,考前一晚过一遍就够。上机题的押题更集中:字符串切分加排序、斐波那契递归、分组聚合 SQL、单例模式,四道题对应四个最基础的工作场景,换汤不换药。

有个血泪教训值得说。我以前做这类卷子,习惯先把选择题从头到尾做完再检查,结果有次在一道初始化顺序题上耗了十二分钟,等做到上机题只剩四十分钟,SQL 第三题的分组字段写错都没时间回头看,白白丢了十几分。从那以后我每次模考都强制走一遍「先扫一遍标出卡壳题、按分值分配时间、最后留十五分钟回读题干」的流程,哪怕题再简单也不跳步。这套流程不保证你全会,但能保证会的不丢分。

把上面这些题按章节过一遍,自己动手把六段代码敲一遍跑通,再去对着答案解释每一行为什么这么输出,这张卷子基本就稳了。希望帮到你。

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

返回列表