简介:面向Java初学者、课程复习与笔试备考人群的基础知识题库,围绕语法规则与常用API梳理高频考点,适合系统学习后自测薄弱环节,也可用于面试前快速回顾。内容覆盖Java基本概念、八种基本数据类型、运算符、控制结构、函数、数组、集合框架、泛型、封装与程序设计等模块;题型兼顾选择题、简答题和编程题,并附参考答案与代码示例,涉及包装类、equals比较、Calendar实例获取、Set/Map/List特性、字符串拼接与替换、日期计算、Stack实现等具体知识点,便于边练边核对思路。整份资料为1个docx文档,压缩包约25KB,目录集中、检索方便,也适合打印成纸质练习。目前已有1308人学习,适合希望用较短时间梳理Java基础、通过做题查漏补缺的读者。
1. 从一道「输出 B 还是报错」的题说起:Java 基础笔试到底在考什么
有人复习时遇到这样一行代码:一个刚 new 出来的 LinkedList,先塞进 "A",接着list.add(2, "B")。手写答案时觉得"不就插到第 2 个位置嘛,能跑",真敲进 IDE 一点运行,直接红着抛异常。这类 java 基础笔试题的共同点是:语法看着都对,错在看不见的规则上。笔试题反复出现在复习清单里,不是因为题目刁钻,而是它们把语言里那些平时不用想、出事才想起的默认约定摆到台面上——索引从哪算、null 从哪来、谁的返回值说了算。把这些约定捋顺,比背答案有用得多。
2. 看着简单却总做错:Java 基础笔试真正在测的认知边界
一份试卷里最容易被轻视的,往往是那种"看着会、做起来错"的题。它们不考你记不记得某个方法名,而是考你心里有没有一杆秤:这段代码是在什么时候出问题、出的是哪种问题。这个判断决定了你是去改语法,还是去加判空,还是去调整逻辑。
打个比方,编译期像是出门前的安检,你的行李(代码)在门口就被翻了一遍,不合规的当场拦下,根本走不到登机口;运行期则是飞起来之后才发生的颠簸,安检看不出来,飞上天才知道。很多初学者把这两件事混在一起,看到报错就慌,却不分辨这是"根本没编译过"还是"编译过了但跑崩了"。这两类错误的修法完全不同。
2.1 编译期与运行期:两把不同的尺子
先看那道索引题。原样的代码大致长这样:
import java.util.LinkedList; public class ListAddDemo { public static void main(String[] args) { LinkedList list = new LinkedList(); list.add("A"); // 此时 size() 为 1,合法索引只有 0 和 1 list.add(2, "B"); // 传 2 越界:add(index, e) 允许 0..size() String s = (String) list.get(1); System.out.println(s); } }这段代码编译完全通过,编译器不会拦你,因为add(int, Object)这个方法签名是存在的,参数类型也对得上。问题出在运行时:add(int index, E element)对 index 的合法要求是"从 0 到 size() 之间(含 size())",size() 为 1 时只能传 0 或 1,传 2 就抛IndexOutOfBoundsException。把add(2, "B")改成add(1, "B"),再get(1),输出就是 B;而原题不改的话,程序在add那行就断了,走不到后面。
同样的分界线还出现在另外几个地方。String str = null; String strTest = new String(str);这一句编译没问题,因为String(String)这个构造器确实存在,null 也是合法的 String 引用;一执行就抛NullPointerException,因为构造器内部要去读那个引用里的内容,读到的是空。再看int a = 5 / 0;,整除零抛ArithmeticException;但如果写成double b = 5.0 / 0;,结果是Infinity,不抛异常——整数除零和浮点除零在 java 里是两套规则,这也是选择题爱挖的坑。
理解了这把尺子,你再看"编译时发生错误 / 运行时引发异常 / 正确运行输出 X"这种三选一,判断路径就很清楚:先看方法签名和类型对不对得上,对得上就不是编译错误;再看运行时的边界条件(索引、空引用、除零、类型转换),踩到哪个就是哪个异常。
2.2 集合家族的三条基本规则:List、Set、Map 各管什么
集合相关的题几乎每套卷子都有,因为 List、Set、Map 这三个名字太像,初学者容易把它们当成"三种容器"随便换着用。其实它们的分工从设计之初就定死了。
| 接口 | 是否有序 | 能否存重复元素 | 存的是什么 | 典型用途 |
|---|---|---|---|---|
| List | 有序,按插入位置索引 | 能 | 单个元素 | 按顺序排列的数据,如通讯录列表 |
| Set | 通常无序 | 不能 | 单个元素 | 去重、判断存在性 |
| Map | 键无序(部分实现有序) | 键不能重复,值可以 | 键值对 | 姓名到成绩、单词到释义的映射 |
"哪个集合不能存重复元素"这类题的标准说法落在 Set 上,因为 Set 的语义定义就是"不重复的集合"。Map 的键也不允许重复,但它存的是键值对而不是单个元素,一般不参与"元素是否重复"的比较——你往 Map 里 put 同一个键两次,第二次是覆盖,不是报错。
再看那道关于 Map 和 List 关系的题,四个选项里正确的是"Map 的 value 可以是 List 或 Map"。这条规则很实用:Map 的值是个 Object 引用,什么都能装,套一层 List 就是"一个键对应多个值",套一层 Map 就是嵌套结构。而"Map 继承 List"是彻底错的,两者是并列的接口,谁都不继承谁;"List 中可以保存 Map 或 List"其实也成立,只是原题把它和更准确的选项放在一起做了取舍。至于"只能保存从数据库中取出的数据",更是把用途当成了限制,集合只认类型,不认数据来源。
顺带说一句,Map不属于Collection接口体系,List和Set才属于。这个层级关系在选择题里也常出现:问"哪个不是 Collection 的子接口",答案就是 Map。
2.3 默认值:数组和对象被创建时自带的"出厂设置"
还有一类题,代码短得让人放松警惕,输出结果却要背下来:
public class DefaultValueDemo { public static void main(String[] args) { int[] nums = new int[5]; System.out.println(nums[4]); // 输出 0 Object[] objs = new Object[5]; System.out.println(objs[2]); // 输出 null int[] k = new int[4]; System.out.println(k[3]); // 输出 0 } }三行输出分别是 0、null、0。规则是:数组一旦被new出来,它的每个元素都会被赋予该类型的默认值——整数系是 0,浮点是 0.0,布尔是 false,字符是空字符,所有引用类型(包括 Object、String、数组本身)都是 null。注意这里说的是"数组元素",而不是"局部变量"。方法体里单独声明的int x;如果不赋值就用,是编译错误;但new int[4]里的四个格子,是堆上的内容,创建时就填好了默认值。
这个区分是很多人的血泪经验:以为自己"没初始化",其实是把"引用变量本身"和"引用指向的那块内存"混为一谈了。引用变量必须初始化才能用,它指向的数组或对象内部的字段,由创建过程负责初始化。
3. 包装类、字符串与相等性:三个概念把「存储」和「比较」讲透
如果只允许从一份 java 基础题库里挑三个最常被误解的点,大概就是这三个:包装类到底包了什么、==和equals差在哪、String.valueOf为什么值得单独记。它们分别对应"数据怎么装""两个东西怎么算相等""东西怎么变成字符串"。
3.1 包装类到底包装了什么
java 有八种基本类型,它们不是对象,不能直接扔进集合里(集合只收引用类型)。为了让这些"裸数据"也能像对象一样被使用,语言给每一种基本类型配了一个对应的类,这就是包装类。
| 基本类型 | 包装类 | 默认值 |
|---|---|---|
| byte | Byte | 0 |
| short | Short | 0 |
| int | Integer | 0 |
| long | Long | 0L |
| float | Float | 0.0f |
| double | Double | 0.0d |
| char | Character | '\u0000' |
| boolean | Boolean | false |
"包装"这个词很形象:基本类型是一颗糖,包装类是外面那层带标签的盒子,盒子里还是那颗糖,但盒子能被放进集合、能调用方法、能判空。
这里有个需要点破的细节。有些题会问"java 提供了哪个包装类来包装原始字符串类型",选项里 Integer、Char、Double、String,正确答案只能是 String——因为另外三个跟字符串没关系。但严格来说,String 并不在八种包装类之列,它本身就是个引用类型,只是恰好承担了"表示一串字符"的职责。这类题考的是排除法,不是定义;明白了这一点,你就不会误以为"包装类有九个"。
包装类还有一个常见副作用:自动装箱和拆箱。Integer a = 100;会自动把 int 装成 Integer,int b = a;会自动拆回来。方便是方便,但装箱时对较小范围内的整数会复用同一个对象,超出范围才新建,这导致两个"数值相同"的 Integer 用==比可能一个 true 一个 false。这条规则不属于必须背的题,但它是理解下一节的关键伏笔。
3.2==和equals:一个比"是不是同一个",一个比"内容像不像"
这两个符号在新手眼里几乎等价,实际职责完全不同。==比较的是"两个引用是不是指向同一块内存",equals比较的是"两个对象的内容是否相等"(前提是类重写了 equals,String、Integer 这些常用类都重写了)。
String str1 = "It is"; String str2 = "It is"; String str3 = new String("It is"); System.out.println(str1 == str2); // true System.out.println(str1 == str3); // false System.out.println(str1.equals(str3)); // true第一行输出 true,很多人第一次见会愣住:明明是两个变量,怎么会相等?原因在于字符串字面量会被放进常量池,str1和str2赋值时指向的是池子里同一份内容,所以地址相同。第三行用new强制在堆上开了新对象,地址自然不同,==立刻变 false,而equals比内容,仍是 true。
这就解释了一个常见困惑:为什么有的代码里==能判等,有的就不能。答案取决于这两个引用当初是怎么来的,而不是字符串长什么样。所以工程里比较字符串内容,一律用equals,别赌常量池。
equals还有一个容易忽视的细节:如果调用方是 null,str1.equals(str2)在 str1 为 null 时会抛空指针。稳妥的写法是把常量放前面,"It is".equals(str1),常量不可能是 null,怎么都不会炸。这条习惯在笔试题里不常直接考,但在真实代码里能省掉一整类 bug。
顺便把 Objects 工具类带一句:需要比较两个都可能为 null 的引用时,Objects.equals(a, b)会先判空再比,比手写更省心。
3.3String.valueOf为什么比toString稳
把别的东西变成字符串,最直觉的写法是obj.toString()。问题在于,如果 obj 是 null,这一句直接抛NullPointerException。String.valueOf则有一组重载,专门处理各种输入:
char[] data = {'a', 'b', 'c', 'd'}; System.out.println(String.valueOf(10D)); // 10.0 System.out.println(String.valueOf(3 > 2)); // true System.out.println(String.valueOf(data, 1, 3)); // bcd System.out.println(String.valueOf((Object) null)); // null第一个输出是 10.0 而不是 10,因为传的是 double 字面量,valueOf(double)保留小数形式;第二个传的是布尔表达式的结果,输出 true;第三个用的是valueOf(char[] data, int offset, int count)这个重载,从下标 1 开始取 3 个字符,得到 bcd,注意它是"从 1 开始数、数 3 个",不是"取到下标 3"。第四个传 null 引用时返回字符串 "null",而不是抛异常。
这四种行为说明一件事:String.valueOf是一组按参数类型分派的重载方法,不是单个方法。你传什么类型,它就走哪个版本,字符数组还额外享受"截取"能力。这也是为什么转换字符串时优先用它——它对手里这个值的类型更宽容。
4. 泛型、封装与异常:三个被名字耽误的核心机制
泛型、封装、异常,这三个词在教材里都带着一股学术味,读起来像要背定义。但它们的实际作用非常朴素:泛型是给集合加一道闸,封装是把不该外露的东西藏起来,异常是把"出错了怎么办"从业务代码里拆出去。理解了它们各自要解决的麻烦,定义自然就记住了。
4.1 泛型不是"高级语法",是给集合加一道闸
先看没有泛型的日子。一个普通 List 什么都能塞:
import java.util.ArrayList; import java.util.List; public class RawListDemo { public static void main(String[] args) { List list = new ArrayList(); list.add("hello"); list.add(100); // 编译器不拦,因为元素类型是 Object String s = (String) list.get(1); // 取出来才发现是 Integer System.out.println(s); } }这段代码编译通过,运行时在(String) list.get(1)那一行抛ClassCastException。问题不在强转写错了,而在于"这个 List 里到底能放什么"这件事,编译器完全不知情,只能等运行到那一行才炸。
泛型就是把这个信息提前告诉编译器:
import java.util.ArrayList; import java.util.List; public class GenericListDemo { public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("hello"); // list.add(100); // 这一行直接编译报错,根本到不了运行期 String s = list.get(0); // 不需要强转 System.out.println(s); } }差别就三处:一是编译期就拦住类型不符的写入,错误提前暴露;二是取元素时不用强转,代码干净;三是同一份泛型代码能适配多种类型,复用率上去了。这三点正好对应"提高类型安全、消除强制类型转换、提高代码重用率"这套说法。
泛型还能用在方法上。比如写一个方法,既能求整数数组的最大最小值,也能求字符串数组的(按字典序):
class Pair<T> { private T min; private T max; public Pair(T min, T max) { this.min = min; this.max = max; } public T getMin() { return min; } public T getMax() { return max; } } class ArrayAlg { // <T extends Comparable> 限定 T 必须能比较大小,否则 compareTo 调不了 public static <T extends Comparable> Pair<T> minmax(T[] a) { if (a == null || a.length == 0) return null; T min = a[0]; T max = a[0]; for (int i = 1; i < a.length; i++) { if (min.compareTo(a[i]) > 0) min = a[i]; if (max.compareTo(a[i]) < 0) max = a[i]; } return new Pair<T>(min, max); } }关键在<T extends Comparable>这个上界:它告诉编译器"T 不一定是任何类型,但至少得实现 Comparable 接口",于是compareTo才调得动。如果去掉这个上界,方法体里那两行比较会直接编译不过。这也是泛型最容易让人困惑的地方——类型参数不是万能的,你需要给它划一个能力下限。
4.2 封装:隐藏实现,不等于给每个字段配个 getter
封装的标准定义是"把数据项和方法隐藏在对象内部,把方法实现细节隐藏起来"。听起来抽象,落到代码上其实就一句话:外部只该知道这个对象能做什么,不该知道它内部长什么样。
很多人的理解停在"字段全设成 private,然后生成一堆 getter/setter",这只能算完成了一半。真正的封装是把"规则"也收进去:
public class Score { private int value; public void setValue(int value) { if (value < 0 || value > 100) { throw new IllegalArgumentException("分数必须在 0 到 100 之间"); } this.value = value; } public int getValue() { return value; } }如果只是无脑生成 setter,外界传 -5 也能存进去,这个类就变成了一根裸的 int,封装等于没做。把校验写在 setter 里,外界无论从哪条路径改值,都会经过这道关卡,这才是"隐藏"的价值——不是藏起来不让人看,是不让人乱改。
封装类这个词还有另一层含义:前面提到的 Integer、Double、Character 这些包装类,有时也被称作"封装类",因为它们把基本类型封装成了对象。同一个词两种用法,看上下文区分即可。
4.3 异常体系:Throwable 是根,Error 和 Exception 是两大分支
异常的继承结构不难,但选择题特别爱考"哪个是所有异常的父类"。答案不是 Exception,而是 Throwable。整棵树大致是这样:
| 层级 | 代表类型 | 特点 |
|---|---|---|
| Throwable | 根类 | 所有可抛出对象的父类 |
| Error | 内部错误、致命错误 | 程序一般无法处理,如系统级故障 |
| Exception | 普通异常 | 程序可以捕获并处理 |
| RuntimeException | 空指针、数组越界、算术异常、类型转换异常 | 运行时才出现,编译器不强制处理 |
由此可以推出几条常考结论:Error和Exception是兄弟,不是父子;RuntimeException是Exception的子类;捕获时如果先写父类再写子类,父类分支会把子类"吃掉",编译器会报"该 catch 块不可达"的错误。
多重 catch 的顺序规则也是从这棵树来的:越具体的异常越往前放,越笼统的越往后放,Exception这种覆盖所有情况的必须放最后。原因很直白——如果前面已经有个兜底的把什么都接住了,后面的分支永远轮不到,那写它干嘛。
还有个语义上的区分值得说清:throw是"主动扔一个异常",写在方法体里;throws是"声明这个方法可能扔出哪些异常",写在方法签名上。前者是动作,后者是告知。至于try,它是唯一能和finally单独搭配的块——catch可以不写,finally照常执行,这个组合在"只关心收尾、不关心异常类型"时特别顺手。
4.4finally与return的先后:那道"返回 3"的题
public int count() { try { return 5 / 0; } catch (Exception e) { return 2 * 3; } finally { return 3; } }这个方法返回 3。执行顺序是:进 try,除零抛ArithmeticException,跳到 catch,算出 6 准备返回;但在真正返回之前,finally一定会执行,而finally里又写了一个 return,它直接把前面那个"待返回的 6"覆盖掉,最终返回 3。
如果去掉 finally 里的 return,结果才是 6。这道题考的其实不是异常,而是"finally 的执行优先级高于 try/catch 里的 return"。反过来说,这也是一条劝退级实践:别在 finally 里写 return,它会把异常和返回值一起吞掉,调试时很难看出问题出在哪。
再补一道:try 里System.out.print("try,"); return;,catch 打印 "catch,",finally 打印 "finally"。因为没有异常,catch 不执行,输出是"try,finally",顺序上 finally 在 return 之后、方法真正退出之前。这两道题放到一起看,finally 的位置感就建立起来了。
5. 常见误区与易混概念:五个初学者最容易混的地方
复习到这一步,剩下的坑基本都在"以为懂了其实没懂"的区间。下面五条是错得最有代表性的,每一条都按"错在哪、为什么会错、怎么分"来说。
5.1 误区一:以为 String 是基本类型
初学者常把 String 和 int、double 并列,觉得"字符串不就是一种数据类型嘛"。其实 String 是标准的引用类型,它是java.lang包下的一个类,有自己的字段和方法,也能被赋值为 null。
会混的原因很实际:String 用起来太顺手了,可以直接写String s = "abc";,不用 new,看起来跟int i = 1;没区别。这属于语言给的语法糖——字面量可以直接赋给引用类型,编译器帮你创建对象。
怎么区分:看它能不能为 null,看它有没有方法。int i = null;编译不过,String s = null;天经地义;i.length()不存在,s.length()合理。另外八种基本类型的首字母都是小写,String 是大写,命名也能看出身。
5.2 误区二:以为 Map 继承 List,或以为集合只能存一种来源的数据
有一道题问"关于 Map 和 List 哪个说法正确",选项里塞了"Map 继承 List""集合只能保存从数据库取出的数据"这类干扰项。会选错,往往是因为把集合当成了"一种东西的不同叫法"。
真实关系是:List、Set 都继承自 Collection,Map 自成一家,与 Collection 并列,彼此没有继承关系。至于能存什么,集合只受类型系统约束,跟数据来自数据库、文件还是手工 new 出来毫无关系。
怎么区分:记住 Map 的形态是"键值对",一次 put 两个参数;List 的形态是"序列",一次 add 一个元素。参数个数的差异,背后就是接口设计的根本分歧。另外"List 里能不能放 Map 或 List"——能,引用类型随便套,Map 的 value 也能放 List 或 Map,嵌套结构就这么来的。
5.3 误区三:以为多重 catch 的顺序可以随便写
写了几行 catch 之后,有人会把Exception放在最前面图省事,结果编译报错,提示 catch 块不可达。也有人反过来,把具体异常写在后面,IDE 只是警告却不明白为什么。
会混的原因是:从语义上看,"先捕获更宽的范围"似乎更保险,直觉里宽泛的应该更能兜底。但编译器的逻辑是执行顺序——前面的分支一旦匹配,后面的就不会再看,所以父类在前等于让子类分支成为死代码。
怎么区分:按继承树的深度排,越具体越靠前。NumberFormatException和ArrayIndexOutOfBoundsException都是RuntimeException的子类,RuntimeException又是Exception的子类,所以书写顺序只能是"具体异常 → RuntimeException → Exception"。实在记不住,就先把所有子类写完,最后统一放 Exception。
5.4 误区四:以为 Calendar 的月份从 1 开始
拿到日期相关代码,很容易写出cal.get(Calendar.MONTH)然后直接拼进字符串,输出发现月份少了一个。Calendar 的月份字段是 0 到 11,一月是 0,十二月是 11。年份和日期倒是正常从 1 起算,这种"同一组字段规则不一致"的设计最容易翻车。
会混的原因是:人的直觉里"月份"就是从 1 开始的,而日期、年份也确实从 1 开始,只有月份这一个例外,属于历史遗留的坑。
怎么区分:凡是取 MONTH,一律加 1;凡是set月份,一律减 1。前面的日期计算代码里出现(cal.get(Calendar.MONTH) + 1)不是啰嗦,是在做这个换算。另外Calendar.getInstance()才是拿实例的正确方式,它是静态工厂方法;getTime()返回的是 Date 对象,不是"获取时间"这个语义本身,两个名字很像但用途不同。
5.5 误区五:以为泛型 List 和普通 List 只是写法不同
看着List和List<String>,很容易以为是"可选标注",加不加都一样,反正存进去的都是对象。这个理解在编译期就会被打脸:两者的检查强度完全不同。
会混的原因是:java 的泛型在运行期会被擦除,List<String>和List<Integer>跑起来其实是同一个类,这让"泛型只是装饰"的说法听起来很有道理。但擦除发生在编译之后,编译期的类型检查是真实存在的,也正是它挡住了那些会抛ClassCastException的写法。
怎么区分:把泛型看成"编译期的类型契约"而非"运行期的容器标签"。契约期内,往里塞错类型直接编译失败;契约期结束(编译完),信息被擦掉,所以不能对泛型做instanceof判断。另外泛型的类型参数只能是引用类型,List<int>不合法,要写List<Integer>——这也是包装类存在的实际意义之一。
6. 什么时候该用、什么时候别用:从笔试题到真实代码的边界
题库里的知识点,有些在日常开发中天天用,有些只在考卷上出现。分不清这条界线,容易出现两种极端:一种是把笔试结论生搬硬套进项目,另一种是觉得"这些题都是八股,学了没用"。两种都亏。
先说高频且必须掌握的部分。equals比较字符串内容是每天的活,==判断引用相等在业务代码里基本只用于和 null 比。集合选型也是日常决策:需要顺序和重复就 List,需要去重就 Set,需要按名字查值就 Map。异常处理更是绕不开——捕获具体异常、记录日志、不要空 catch,这些习惯直接决定线上问题能不能定位。泛型也一样,写一个通用工具方法、定义带类型的容器,是常规操作。
再说说只适合"练思维、不适合搬进项目"的部分。手写一个 Stack 类(用 LinkedList 的 addFirst/getFirst/removeFirst 模拟后进先出),用来理解数据结构的运作非常好,但真实项目里直接用现成的双端队列实现更稳,自己维护一个没有任何收益。手写十进制转十六进制的循环加 switch 分支,能把进制转换的原理吃透,实际写代码时一行Integer.toHexString(365)就够了。用 Calendar 做日期加减和天数差,在旧代码里仍然常见,但新写的代码更倾向于用后来引入的时间 API,因为后者不可变、线程安全、月份从 1 开始。
| 知识点 | 笔试卷里的分量 | 真实代码里的分量 | 建议 |
|---|---|---|---|
==与equals的区别 | 必考 | 高频 | 必须吃透,尤其常量池那段 |
| 包装类与自动装箱 | 常考 | 中频 | 知道缓存范围和判空即可 |
| 集合家族分工 | 必考 | 高频 | 必须吃透 |
| 手写进制转换 | 偶尔 | 极低 | 理解原理即可,落地用现成方法 |
| 自定义 Stack | 偶尔 | 极低 | 理清思路,落地用标准库 |
| Calendar 月份偏移 | 常考 | 中频 | 记住 +1,改代码时格外小心 |
| 泛型与类型擦除 | 常考 | 高频 | 会用<T extends Comparable>这种上界 |
| 多重 catch 顺序 | 必考 | 中频 | 记住"具体在前,笼统在后" |
| finally 与 return | 常考 | 低频 | 知道会覆盖返回值,并且别这么写 |
有一条我自己的教训值得一说。早年做一个小工具,图省事在 finally 里写了 return,本意是"无论如何都返回一个默认值"。结果某次上游抛了异常,日志里干干净净什么都没留下,排查了两个多小时才定位到是那个 finally 把异常吞了。从那以后我每次写 try 块,都强制自己走一遍三问:这里可能抛什么、catch 要捕到多具体、finally 里到底该不该有返回语句。规则不复杂,但漏一次就够难受的。
至于这份资料本身,它的价值不在"背完这些答案就能过",而在于它把散落在教材各章的规则压缩成了几十道具体情境:索引从哪算、null 从哪来、谁的返回值说了算、哪两个概念长得像但不是一回事。顺着这些题把每一处"为什么"补上,你对 java 基础的那层模糊感会明显变薄。需要完整题目和参考答案的话,按文末方式获取即可。
希望帮到你。
本文还有配套的精品资源,点击获取