news 2026/9/30 8:54:35

Java基础知识点复习笔记(上):数据类型、面向对象、字符串、集合与异常

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java基础知识点复习笔记(上):数据类型、面向对象、字符串、集合与异常

“java基础知识点复习笔记(上)”——看到这个标题,我第一反应是:又要准备跳槽或者准备校招了吧。说实话,这些年我面试过不少人,也被别人面试过,一个Java开发者基础扎不扎实,往往聊不到十分钟就能看出来。基础这东西,平时写业务代码感觉不到它的存在,但一到面试、一到排查线上问题、一到做代码重构,它就会跳出来考验你。

这份笔记我整理了很久,定位是给两类人看的:一类是准备Java面试的开发者,需要把零散的知识点系统过一遍;另一类是工作了两三年、平时CRUD写得多但想查漏补缺的朋友。内容不会像教科书那样面面俱到,而是把高频考点、容易踩坑的点、面试官真正想听的点拎出来,配合代码示例和实战经验来讲。

既然是“上”篇,我先聚焦最核心的地基部分:数据类型、面向对象、字符串、集合框架、异常处理。这些都是Java体系里最基础也最常考的内容,把这些吃透了,再往JVM、并发、Spring那些深水区走,才不会心虚。

1. 数据类型:基础中的基础,却藏着最多的坑

1.1 八大基本类型与包装类,你真的分清楚了吗

Java的基础数据类型是八种:byte、short、int、long、float、double、char、boolean。这是面试开场最常见的问题,但很多人答得并不完整,只会背“int 4字节、long 8字节”,一旦问到float和double为什么不能直接用==比较,就卡住了。

先给一张我复习时自己整理的速查表:

类型字节数默认值取值范围(大致)应用场景
byte10-128 ~ 127二进制流处理、节约内存的数组
short20-32768 ~ 32767较少使用,兼容老协议时见过
int40约±21亿最常用的整数类型
long80L很大时间戳、ID、大数值计算
float40.0f约±3.4E38科学计算,精度有限
double80.0d约±1.7E308默认浮点类型
char2'\u0000'0 ~ 65535字符处理
boolean未明确定义falsetrue/false逻辑判断

注意boolean的字节数,Java虚拟机规范里没有明确规定,实际实现中JVM通常用int代替boolean,所以一个boolean在数组中可能占1字节,单独声明时可能占4字节。这个细节面试官偶尔会挖,能答出来会加分。

包装类是另一个高频考点,重点是自动装箱与拆箱的底层实现。Integer i = 100这行代码,编译后实际是Integer.valueOf(100);而int j = i编译后是i.intValue()。所以每一种包装类都对应一个静态工厂方法和一个xxxValue方法,这是基础中的基础,必须记牢。int的默认值是0,但Integer的默认值是null,我见过太多人在写实体类时用int做字段类型,数据库一查出来没有值,直接NPE,这是真实项目里最常见的低级错误。

1.2 Integer缓存、浮点精度与==陷阱

Integer缓存是面试题里的常青树。Integer.valueOf()方法在 -128 到 127 之间会返回缓存的同一个对象,超出这个范围才new新对象。所以Integer a = 100; Integer b = 100; a == b返回true,而把100换成128就返回false。这个坑我在代码评审里见到过不止一次,有人用==比较两个包装类,小数值没问题,一上线数据大了就开始出bug。

解决办法很简单:两个包装类比较,一律用equals(),或者用Objects.equals()。但这里还有个新坑,就是包装类的equals对类型敏感,new Integer(100).equals(100L)返回false,因为一边是Integer一边是Long。所以如果要比较不同类型数值,建议先把它们转成同一个类型再比较,或者用BigDecimal。

浮点精度问题是另一个必须掌握的考点。0.1 + 0.2 == 0.3在Java里返回false,因为浮点数在二进制中无法精确表示,这是计算机组成原理决定的。面试官通常在这个问题后面会追问:那怎么办?标准答案是涉及金额计算时用BigDecimal,而且BigDecimal构造时必须传字符串,否则new BigDecimal(0.1)得到的仍然是不精确的值。我自己写金融相关代码时,会统一封装一个金额工具类,内部用BigDecimal,任何金额计算都不允许用double直接做。

2. 面向对象:从概念背诵到真正理解

2.1 封装、继承、多态,面试官真正想听的是什么

面向对象三大特性,几乎每个Java面试都会问。但大部分人的回答就是背定义:“封装就是把属性私有化,继承就是子类继承父类,多态就是一个接口多种实现。”这些没错,但不够。面试官问三大特性,其实是想听你对“设计”的理解。

封装的核心不是private修饰符,而是“隐藏实现细节,暴露稳定接口”。我复盘自己的开发经验,封装做得好不好,直接表现在需求变更时的改动量上。比如一个订单金额计算逻辑,如果只有getOrderAmount()这个方法对外暴露,里面怎么调整折扣、怎么计算税费,调用方根本不用关心,这就是封装的意义。

继承在面试里的重点,往往会引导到“继承与组合怎么选”这个问题上。继承是Is-A关系,组合是Has-A关系。我见过很多代码滥用继承,比如为了复用两个方法就extends一个类,结果父类一改动,子类莫名其妙出bug。实际项目里,我倾向优先使用组合,因为组合更灵活,不会打破封装。但继承在框架代码里确实随处可见,比如Spring的@Service类通常继承一个抽象基类,Template Method模式就是靠继承实现的。面试时能讲出这个权衡,比单纯背概念要加分得多。

多态是我认为三大特性中最核心的。Java的多态体现在方法重载和方法重写,但底层原理是动态绑定——虚拟机在运行时根据对象的实际类型来调用方法。这里面试官常会追问:重载和重写的区别?重载是编译期决定的,看的是引用类型和参数列表;重写是运行期决定的,看的是实际对象类型。所以Father f = new Son(); f.method();调用的到底是Father的method还是Son的method?答案是Son的(如果Son重写了method的话),因为JVM在运行时才做动态分派。这个考点我面试别人时几乎必问,答得清楚的人,说明对多态的理解是到位的。

2.2 抽象类与接口:Java 8之后,界限已经模糊了

抽象类和接口的区别是经典面试题,但很多人的答案是老版本Java的答案。Java 8之后接口可以有default方法和static方法,Java 9之后接口还可以有private方法,再加上接口的字段只能是public static final常量,抽象类可以有各种成员变量——两者的边界确实模糊了,但设计语义仍然不同。

我的理解是:抽象类描述的是“是什么”,接口描述的是“能做什么”。比如AbstractList和List接口的关系就很典型。AbstractList是抽象类,它把List接口的部分方法实现了,留给子类去扩展;而List接口定义了列表该有的行为契约。一个类只能继承一个抽象类,但可以实现多个接口,这是Java设计上的硬性约束。

面试时如果你能主动提到Java 8的default方法带来的一个经典问题——菱形继承,会让面试官觉得你是有思考的。一个类实现了两个接口,两个接口都有同名的default方法,编译会报错,必须重写这个方法并手动指定调用哪个接口的default方法。代码是这样:

interface A { default void hello() { System.out.println("A.hello"); } } interface B { default void hello() { System.out.println("B.hello"); } } class C implements A, B { @Override public void hello() { A.super.hello(); // 指定调用A的默认方法 } }

这个知识点不常考,但能答出来会明显提升面试的印象分。我做技术方案评审时,也会用这个案例提醒团队:接口新增default方法要考虑兼容性,不能想加就加。

2.3 重载与重写:别把这两个概念搞混了

重载和重写的区别,几乎每次笔试都会出选择题,但很多人还是会踩坑。我总结一句话:重载是同一个类里方法名相同、参数列表不同;重写是子类方法覆盖父类方法,方法签名必须一致(返回类型可以是协变类型,访问修饰符不能比父类更严格,抛出的异常不能比父类更宽泛)。

实际项目里重写的坑点我遇到过一个很经典的:子类重写方法时把参数类型改了,比如父类是method(String s),子类写成了method(Object o),这其实变成了重载而不是重写,加上@Override注解编译器立刻就会报错。所以我会跟团队说:重写方法必须加@Override注解,这不是可选项,是强制规范。

重载的另一个注意点是自动装箱的参与:method(int i)和method(Integer i)同时存在时,调用method(1)会优先匹配method(int),因为基本类型的匹配优先级更高,不会触发装箱操作。而method(null)在这种情况下会报歧义错误,因为null既可以装箱成Integer也可以匹配Object等类型,编译器无法判断。这个考点我在字节的笔试里见过,属于没写过就容易栽的题。

3. 字符串:Java中最特殊的一个类

3.1 String为什么是不可变的

String的不可变性(immutability)是面试高频题,也是理解JVM字符串常量池的前提。String类被final修饰,字符数组也是final的,所有修改字符串的方法返回的是新对象,原对象不变。这个设计不是随意的,背后有几个原因:

一是字符串常量池的复用。如果字符串可变,那么两个指向同一个字符串常量的引用,一个把内容改了,另一个也会跟着变,这将摧毁常量池的安全性和缓存意义。二是安全性,String在Java里被广泛用作参数、类名、URL等,不可变可以避免恶意修改。三是线程安全,不可变对象天然线程安全,不需要额外加锁。

实际开发中,我遇到过因为拼接字符串导致的内存问题。在一个循环里用+做字符串拼接,循环几千次,每次都会在堆里产生新的字符串对象,GC压力很大。正确的做法是用StringBuilder或者StringBuffer。面试官可能会问这两者的区别:StringBuilder线程不安全,StringBuffer线程安全(方法加了synchronized),单线程场景下StringBuilder性能更好。

这里给个我自己的实用建议:不要在方法内使用StringBuffer,因为方法内没有多线程竞争,StringBuffer的同步开销毫无意义;但如果字符串是类的成员变量且可能被多个线程操作,那就必须用StringBuffer或者加锁。这个细节面试时主动提出来,面试官会认为你有真实的并发编程经验。

3.2 常量池与intern(),追根溯源的一道好题

常量池相关最经典的一道面试题是:String s = new String("abc")创建了几个对象?答案是可能两个:一个是常量池里的"abc"(如果之前没有的话),一个是堆里new出来的对象。如果常量池里已经有"abc",则只创建一个。这个题考察的是JVM内存结构和字符串驻留机制,回答时最好把两种情况分开说清楚。

intern()方法的作用是把字符串放入常量池,如果常量池已有相同内容的字符串,则返回池中的引用。实际操作中,我很少主动调intern(),但有个场景很有用:在大量重复字符串的场景下,比如从数据库查出来一万条数据,每条都有相同的状态字段,用intern()可以极大降低内存占用。但要小心,JDK 7之前intern的字符串放在永久代,容易OOM;JDK 7之后移到了堆里,风险低了但也不是完全没风险。面试能说到这里,基本就很完整了。

关于字符串比较的区分,我还要强调一点:"abc".equals(s)和s.equals("abc")在s可能为null时,前者更安全,因为常量字符串的equals不会抛NPE。我见过项目里因为s.equals("xx")这种写法,在s为null时直接抛异常,后来排查发现是数据源里出现了一条脏数据。代码规范上,我推荐字符串比较统一写成常量在前,或者直接用Objects.equals(s, "abc")。

3.3 字符串拼接:编译器的优化与反例

字符串拼接还有一个隐藏考点:编译器对+的优化。Java 8及之前,字符串常量拼接如"a" + "b"在编译期就合并成"ab"了,所以"a" + "b" == "ab"是true。但涉及变量的拼接str + "b",编译后实际上是new StringBuilder().append(str).append("b").toString()。Java 9之后引入了invokedynamic和StringConcatFactory,优化方式变了,但结果仍然是生成新字符串。

一个常见的错误认知是:循环里用+拼接和StringBuilder性能差不多,因为编译器会优化。实际上,在循环体内每次都会创建新的StringBuilder,性能反而更差。我在一次性能排查中发现,一个字符串拼接逻辑在循环里执行了五万次,耗时直接拖慢了接口响应,改成循环外创建StringBuilder后,耗时几乎降为原来的三分之一。所以结论是:循环内拼接字符串,一定要手动用StringBuilder。

4. 集合框架:核心高频考点的聚集地

4.1 集合体系结构:先建立整体认知

Java集合框架从顶层看分为两大体系:Collection(存放单列元素)和Map(存放键值对)。Collection下面又分为List、Set、Queue三大接口。List是允许重复、有序的集合;Set是不允许重复的集合;Queue是队列。Map则是独立的体系,每个元素是一个键值对。

面试官一般会先从宏观问起:ArrayList和LinkedList的区别。这个题的基础答案是ArrayList底层是数组,随机访问快、插入删除慢;LinkedList底层是双向链表,插入删除快、随机访问慢。但深入一点的考法是:具体在什么场景下用哪个?我的实战经验是,开发中90%以上的情况都选ArrayList,因为遍历、索引的需求占绝大多数;LinkedList的插入删除快是有前提条件的,它在中间插入时需要先遍历找到位置,复杂度是O(n),真正快的只有头尾操作。

这里我给一个面试加分点:LinkedList在Java 6之前实现的是纯双向循环链表,所以addFirst和addLast都是O(1);Java 7之后去掉了循环结构,变成普通双向链表,但对实际使用影响不大。能答出这个版本差异的候选人,说明是真的看过源码的。

4.2 HashMap原理:面试必问的重头戏

HashMap是Java集合里最核心的考点,几乎每场面试必问。面试官会连环追问:底层数据结构是什么?put的流程是什么?扩容机制是什么?为什么用红黑树?我按这个顺序把核心逻辑梳理一遍。

HashMap在JDK 1.8之后的底层是数组+链表+红黑树。put一个键值对时,先通过hash(key)算出哈希值,再用(n - 1) & hash定位到数组下标(这里n是数组长度,这个位运算等价于取模,但效率更高)。如果该位置没有元素,直接放入;如果有元素,比较key是否相同,相同则覆盖,不同则插入链表尾部(尾插法)。当链表长度超过8时,链表会转化为红黑树,这是为了把极端情况下的查询复杂度从O(n)降到O(log n)。

扩容是另一个高频考点。HashMap的默认初始容量是16,负载因子是0.75,当元素个数超过容量 * 负载因子时,容量翻倍为原来的两倍。为什么负载因子是0.75,这是个权衡:太小了浪费空间,太大了增加哈希冲突概率。而且HashMap的容量要求是2的幂次方,因为只有容量是2的幂时,(n - 1) & hash才能均匀分布。

关于HashMap为什么用红黑树而不是平衡二叉树,我补充一个理解:红黑树不是严格平衡的,它只保证从根到叶子的最长路径不超过最短路径的两倍,这样插入和删除的代价低,查询效率也能接受。AVL树是严格平衡的,插入删除时需要更多旋转操作,实际性能反而不好。这个细节能答出来,说明你真的研究过数据结构。

4.3 线程安全集合:fail-fast与并发容器的选择题

集合相关还有一个面试必考点:哪些集合是线程安全的?ArrayList线程不安全,Vector线程安全(已过时),HashMap线程不安全,Hashtable线程安全(已过时),ConcurrentHashMap线程安全(推荐)。

Vector和Hashtable虽然线程安全,但实现方式是在方法上直接加synchronized,多线程竞争时并发度极低,性能很差。现代Java开发里基本不用它们,替代方案分别是CopyOnWriteArrayList和ConcurrentHashMap。

ConcurrentHashMap在JDK 1.8之后的实现是CAS+synchronized锁头节点,锁粒度比Hashtable锁整个对象细得多,所以并发性能更好。CopyOnWriteArrayList则是读时不加锁、写时复制整个数组,适合读多写少的场景,比如监听器列表。

fail-fast机制也值得单独记一下:ArrayList和HashMap的迭代器是fail-fast的,意思是迭代过程中如果结构被修改(增删元素),会抛出ConcurrentModificationException。但这个机制不保证一定发生,它只是用modCount属性做快速检测,属于并发修改的保护手段,不是强一致性保证。实际项目里如果在遍历集合时需要删除元素,不要用集合的remove()方法,而应该用迭代器的remove()方法,这样不会触发fail-fast。这个点我代码评审时经常纠正别人。

5. 异常处理:写好异常,项目少出问题

5.1 异常体系结构:受检与非受检的边界在哪

Java的异常体系顶层是Throwable,下面分Error和Exception两个分支。Error代表JVM层面的严重错误,比如StackOverflowError、OutOfMemoryError,程序一般不该去捕获它们,也基本捕获不了。Exception下面又分为受检异常(Checked Exception)和非受检异常(RuntimeException)。

受检异常是编译期强制要求处理的异常,比如IOException、SQLException,不处理就编译不过。非受检异常是运行时异常,比如NullPointerException、IllegalArgumentException、IndexOutOfBoundsException,编译期不强制处理。这是Java的一个设计特点,很多现代语言(如C#、Kotlin)并不区分受检异常。面试时如果面试官问“你对受检异常怎么看”,可以表达自己的观点:受检异常强制调用方处理,但有时会导致代码里充斥着try-catch而忽略了真正的错误处理逻辑。

我自己的经验是,Service层抛出受检异常会导致Controller层被迫处理,代码很脏。所以很多实际项目的做法是把业务异常定义成RuntimeException的子类,配合@RestControllerAdvice做统一异常处理。比如定义BizException extends RuntimeException,抛的时候带错误码和错误消息,全局异常处理器统一捕获并返回给前端。这样代码干净、调用方无感、错误信息还能统一管理。

5.2 善用try-with-resources与正确的异常处理姿势

JDK 7引入了try-with-resources语法,让资源管理变得优雅了很多。以前关闭流是这样写的:

BufferedReader reader = null; try { reader = new BufferedReader(new FileReader("test.txt")); // 读文件... } catch (IOException e) { log.error("读取文件失败", e); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { log.error("关闭流失败", e); } } }

用try-with-resources之后:

try (BufferedReader reader = new BufferedReader(new FileReader("test.txt"))) { // 读文件... } catch (IOException e) { log.error("读取文件失败", e); }

写法简化了很多,而且实现了AutoCloseable的资源会自动关闭,关闭顺序和资源声明顺序相反。这里有个容易忽略的细节:try-with-resources语法在编译后生成的字节码,会在try块中发生异常时,把close()抛出的异常作为suppressed异常附加到主异常上,而不是覆盖主异常。用getSuppressed()方法可以拿到这些被抑制的异常。面试能提到这个,说明你真的读过相关源码。

关于异常处理的另一个常见问题是:catch了异常但是不做任何处理,只打个空日志或者干脆不写。这是我最讨厌的代码之一。正确的做法有三个层次:向上抛出让上层处理、记录完整日志(堆栈信息)后转成业务异常抛出、catch后做降级处理并记录日志。原则是:要么处理,要么抛出,绝不能静默吞掉。因为这个坑,我排查线上问题时花费的冤枉时间太多了,一条日志里看到catch块里只写了e.printStackTrace(),日志文件里还只是无用的堆栈,完全无法定位问题所在。

5.3 日志与异常:把堆栈信息记录完整

说到日志,我这些年踩过的坑就是:很多人catch异常后,日志只记录异常消息,比如log.error("xxx失败:" + e.getMessage()),这在多线程、高并发的环境里,根本没有办法定位到具体是哪一行代码出的问题。正确做法是用log.error("操作xxx失败,参数xxx", e),把异常对象作为最后一个参数传进去,这样日志里会输出完整的堆栈信息。第二个参数的意义在于:异常波基和上下文参数一起输出,排查问题时能还原现场。

还有一个容易被忽视的点:不要吞掉检查型异常的读取操作。我在数据库操作、文件操作相关的代码里见过太多免检型的catch,结果异常被吞了,后续代码继续运行,整个业务数据都错了。不管是什么异常,如果一个catch块决定不抛出、不处理,那么至少要记录日志并返回一个合理的fallback值,而且要打warning甚至error级别,让监控系统能感知到。

6. 复习自查:用这些题检验你的基础牢不牢

整理完上面的知识点,我出一套自查题,大家复习完之后可以对着检验一下自己的掌握程度:

  1. Integer a = 127, b = 127; a == b结果是?如果换成128呢?为什么?
  2. float f = 1.1;这行代码能编译通过吗?为什么?
  3. 重写和重载的本质区别是什么?@Override注解的作用是什么?
  4. 抽象类和接口在Java 8之后还有什么本质区别?实际项目中怎么选?
  5. String s1 = new String("abc"); String s2 = "abc"; s1 == s2结果是?s1.equals(s2)结果是?
  6. 字符串拼接在循环体内做和循环体内用StringBuilder,性能差距有多大?为什么?
  7. HashMap的put流程是什么?链表转红黑树的条件和原因是什么?
  8. ArrayList在遍历时能不能删除元素?安全的方式是什么?
  9. 受检异常和非受检异常的区别是什么?业务异常应该继承哪一类?
  10. try-with-resources为什么比传统finally写法更好?suppressed异常是什么?

这十道题如果都能答清楚,Java基础的上半部分基本是过关的。答不清楚的地方,就回去把对应的章节再翻一遍。我自己复习时有个习惯,不是背书,而是每看一个知识点,就尝试用自己的话讲给旁边的人听,讲不出来就是没掌握。这个方法比闷头刷题有效得多。

这篇“上”篇覆盖了数据类型、面向对象、字符串、集合和异常处理,写完之后我自己也重新理清了几个以前模糊的概念,特别是异常处理那块和HashMap红黑树转化的细节。后续如果大家需要,我可以继续整理“下”篇,把JVM内存模型、类加载机制、多线程并发、泛型与反射这些进阶内容补齐。复习Java基础这件事,看起来是在应付面试,但实际上,把这些底层逻辑弄明白了,写业务代码的时候你会发现自己对很多问题都有了一种“原来如此”的掌控感。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 8:54:21

YOLOv11零售落地实战:货架商品识别与库存联动全链路解析

简介:本资源是一份面向零售行业技术从业者与计算机视觉学习者的实战型技术文档,聚焦YOLOv11在货架商品识别与库存自动化管理中的落地应用,解决传统人工盘点效率低、误差高、响应滞后等核心痛点。文档共38页PDF,结构完整、支持目录…

作者头像 李华
网站建设 2026/9/30 8:54:11

Python Pandas 数据分析实战:从零搭建环境到业务洞察的一站式教程

摘要:这是一篇手把手的 Pandas 数据分析实战教程。你将学会用 conda 从零搭建隔离的分析环境,理解 Series 与 DataFrame 两大核心数据结构,并掌握缺失值清洗、分组聚合与条件筛选等高频操作。全文通过一份模拟销售数据,串联起「读取 CSV → 清洗 → 聚合 → 绘制趋势图」的…

作者头像 李华
网站建设 2026/9/30 8:53:07

程序员赚钱

程序员接私活去哪里?盘点10个靠谱的网上接单平台 对于拥有技术壁垒的程序员来说,利用业余时间接单做兼职,不仅能开辟第二收入来源,还能在不同项目的实战中锤炼技术。然而,选对平台是接单成功的关键。 本文总结了 10 个…

作者头像 李华
网站建设 2026/9/30 8:53:02

AI Agent工作流拆解:个人AI工具如何做到让文科生闭眼玩?

文科生用AI最常见的画面,不是科幻电影里的智能助手,而是对着一个聊天框反复改措辞:问得浅,答案空;问得细,模型懵。更让人绝望的是,好不容易生成了一份报告,格式要自己调、数据要自己…

作者头像 李华
网站建设 2026/9/30 8:53:01

Spring Boot 排除自动配置:核心原理、三种方式与实战避坑

写一篇关于“Spring Boot 排除自动配置”的博客文章。我平时在帮团队排查各种启动异常时,最常打的交道就是自动配置。表面上EnableAutoConfiguration帮我们省去了一大堆 XML,但一旦项目里引入了多个 Starter,平平无奇的“自动”二字就容易引出…

作者头像 李华
网站建设 2026/9/30 8:52:26

AI工程从零搭建:避开环境配置与模型选型陷阱的实战指南

1. 从零搭建AI工程能力,为什么大多数人卡在第一步就放弃了这两年“AI工程”这个词被说得太多了,多到有点变味。打开任何一个技术社区,满屏都是“大模型应用落地”“RAG实战”“Agent开发”,看起来好像不跟着学就要被淘汰。但真正动…

作者头像 李华