(拿铁)- Java基础&集合-真实面试题/超高频八股速成/中频八股提升/低频八股扩展(持续更新中)
之前看面经分享帖的时候,学到了已经上岸大厂的前辈的做法。在准备暑期实习时,我也效仿着根据以往的真实面经整理八股。从牛客、小破站等各个平台搜集了上千篇真实面经,自己整理得到了面试题,根据题目在面试题中出现的频率以及我自己、交流群、好朋友面试被问到的频率进行了分类整理,得到⭐🌟💡三种级别的。在此,给大家分享一下我自己面试被问到的题目,以及我根据以往面经整理得到的题目。各位uu可在专栏关筑一波:
https://www.nowcoder.com/creation/manager/columnDetail/Mq7Xxv
牛客 Top 博主都订阅了,比如“Java 抽象带篮子”(7000+ 粉丝),在这里感谢篮子哥的支持!
Java基础&集合-共计4万6千字,拿铁八股,无需多言~
所有内容经过科学分类与巧妙标注,针对性强,让你的学习事半功倍:
- ⭐ 必须掌握(必看):时间紧迫时的救命稻草,优先攻克核心要点。(可参考神哥的高频题,但我整理出来的比神哥还会多一些,另外还包括以下内容)
- 🌟 尽量掌握(有时间就看):适合两周以上备考时间的同学稳步提升,冲击大厂的uu们建议看!
- 💡 了解即可(知识拓展):时间充裕时作为补充,拓宽视野,被问到的概率小,但如果能答出来就是加分项
- 🔥 面试真题:根据真实面经整理出来的面试题,有些可能难度很高,可根据自身水平酌情参考。
按照推荐观看顺序 “🔥⭐> 🔥🌟 > > 🔥💡” 有条不紊地学习,让每一分每一秒都用在刀刃上,自此一路畅行。
全面覆盖面试核心知识
我的面试真题涵盖技术领域的核心考点,从高频热点到冷门难点一网打尽。以下是部分模块概览:
Java基础&集合 :
https://www.nowcoder.com/issue/tutorial?zhuanlanId=Mq7Xxv&uuid=4f5b4cac4b9f4dee8b4b213851c154c5
JVM篇:
https://www.nowcoder.com/issue/tutorial?zhuanlanId=Mq7Xxv&uuid=c87d9ad65eb840728ae63774893bccf5
Java并发编程&JUC:
https://www.nowcoder.com/issue/tutorial?zhuanlanId=Mq7Xxv&uuid=28c748189f6b471f9f4218791778f41c
MySQL:
https://www.nowcoder.com/issue/tutorial?zhuanlanId=Mq7Xxv&uuid=55b03d6d16604319a24395f393d615be
Redis:
https://www.nowcoder.com/issue/tutorial?zhuanlanId=Mq7Xxv&uuid=77bd828f85984c22858c3724eef78723
计网:
https://www.nowcoder.com/issue/tutorial?zhuanlanId=Mq7Xxv&uuid=65e9951c2e754d7086d26b9b46aa4a1a
后续还将持续更新 操作系统、设计模式、场景题、智力题等丰富内容
独特解析:知其然,更知其所以然
我整理的八股面经绝非简单的问答堆砌。
每一道题目都配有深度剖析的思考过程,在你看题之前,便清晰呈现出题意图,让你迅速抓住题目核心,加深对题目的理解与记忆,做到 “知己知彼,百战不殆”。
Java基础&集合举例
MySQL
Redis
JVM
Java并发(JUC)
计算机网络
助力你举一反三,深度梳理知识点之间的内在逻辑联系,真正实现知识的融会贯通,做到知其然更知其所以然。
后续还会分享如何包装项目、leetcode 刷题模版与刷题技巧、各种学习经验以及真实面经等,从多个角度助力牛u提升技术能力和面试水平。
还是那句话:
1、简历上相关技术点对应的面试题一定要准备,因为写在简历上了,面试官就默认你会,答不上来的话就很减分
2、抓大放小,优先重点高频八股,根据自身情况进行准备。
持续更新中,更新日志:
4.9 更新Java基础部分
4.12 更新 面向对象部分
4.14 更新 反射、异常部分
4.15 更新集合部分(暂时更新完毕)
Java基础(主要考Java语言特性、编译和解释的区别,以及Java的IO,可能会考JDK/JRE/JVM的区别,Java不同版本的特性也要了解)
🔥⭐Q: 能聊聊 Java 语言的特性吗?
思考过程:
这个问题考察对 Java 语言核心特性的理解。需要涵盖面向对象、平台独立性、稳健性等方面。
- 面向对象: 封装、继承、多态、抽象。
- 平台独立性/移植性: "Write once, run anywhere",JVM 的作用。
- 稳健性: 强类型语言、编译时检查、异常处理。
回答提问:
好的面试官,Java 语言有很多重要的特性,我主要理解的有以下几个方面:
首先是 面向对象,这是 Java 最核心的特性之一。它支持封装、继承、多态和抽象这四大特性。封装能够提高代码的安全性并降低出错风险;继承实现了代码的复用;抽象有助于设计与实现分离;而多态则提高了程序的可扩展性。
其次是 平台独立性和移植性。Java 有一句著名的口号 "Write once, run anywhere",这得益于 Java 虚拟机(JVM)。我们编写的 Java 代码会被编译成字节码(.class
文件),然后可以在任何安装了对应 JVM 的平台上运行,实现了跨平台的能力。
再者是 稳健性。Java 是一门强类型语言,它在编译时会进行严格的类型检查,这有助于我们尽早发现潜在的类型不匹配问题。Java 还要求显式的方法声明,并且拥有强大的异常处理机制,通过 try-catch-finally
语句块,我们可以更好地处理程序运行中可能出现的异常情况,使得程序更加可靠。
🔥⭐Q: Java 是如何实现跨平台特性的呢?
思考过程:
这个问题考察对 Java 跨平台原理的理解,核心在于 JVM 的作用。
- JVM(Java Virtual Machine): 不同平台有不同版本的 JVM。
- 字节码(.class 文件): Java 代码编译后的中间格式。
- 翻译成机器码: JVM 将字节码翻译成特定平台的机器码才能运行。
回答提问:
好的面试官,Java 实现跨平台的核心在于 Java 虚拟机(JVM)。您可以把 JVM 理解成一个运行 Java 程序的软件。针对不同的操作系统(比如 Windows、Linux、macOS),都有特定版本的 JVM。
我们编写的 Java 代码首先会被 编译成 .class
文件,这种文件被称为字节码文件。JVM 的作用就是负责将这些字节码文件翻译成当前操作系统能够识别的机器码,只有翻译成机器码之后,程序才能真正运行起来。
关键在于,无论在哪个平台上编写的 Java 代码,编译后生成的字节码文件都是一样的。但是,不同的操作系统上的 JVM 会将这些相同的字节码翻译成各自平台对应的机器码。这样,只要我们在不同的平台上安装了相应的 JVM,就可以运行相同的字节码文件,也就是我们编写的 Java 程序了。因此,运行 Java 程序必须要有 JVM 的支持,因为它负责将编译后的字节码“翻译”成可执行的机器指令。
🔥🌟Q: 能区分一下 JDK、JRE 和 JVM 吗?
思考过程:
这个问题考察对 Java 开发和运行环境组成部分的理解,需要清晰地说明它们各自包含的内容和作用。
- JVM(Java Virtual Machine): 运行 Java 字节码的虚拟机。
- JRE(Java Runtime Environment): JVM + Java 核心类库 + 其他基础构件,用于运行已编译的 Java 程序。
- JDK(Java Development Kit): JRE + Java 工具 + 编译器 + 调试器等,用于开发、编译和运行 Java 程序。
回答提问:
好的面试官,JDK、JRE 和 JVM 是 Java 开发和运行环境中最核心的三个概念,它们的区别如下:
JVM(Java Virtual Machine) 可以理解为是一个虚拟的计算机,它负责运行 Java 字节码。JVM 针对不同的操作系统有不同的实现版本,但它们都能执行相同的字节码,这是 Java 语言 "一次编译,随处可以运行" 的关键。
JRE(Java Runtime Environment) 是 Java 运行时环境,它是运行已编译 Java 程序所必需的所有内容的集合。JRE 包含了 JVM,以及一些Java 核心类库、java 命令和其他一些基础构件。简单来说,如果你只是想运行已经开发好的 Java 程序,只需要安装 JRE 就可以了。
JDK(Java Development Kit) 是 Java 开发工具包,它是用于开发和编译 Java 程序的工具集。JDK 包含了 JRE,同时还提供了编译器(javac)、Java 工具(如 javadoc 和 jdb)等。因此,如果你需要编写、编译和运行 Java 程序,那么你需要安装 JDK。
总结一下,JDK 包含了 JRE,JRE 又包含了 JVM。JVM 是运行 Java 程序的核心,JRE 是运行已编译程序的必要环境,而 JDK 则是进行 Java 开发的完整工具包。
🔥⭐Q: 为什么说 Java 编译和解释是共存的呢?
思考过程:
这个问题考察对 Java 执行方式的理解,需要说明先编译成字节码,再由 JVM 解释执行,以及 JIT 编译器的作用。
- 编译成字节码: Java 源代码先被编译成 .class 文件。
- JVM 解释执行: 字节码由 JVM 的解释器逐条解释执行。
- JIT 编译器: HotSpot VM 中将热点字节码直接编译为机器码以提高性能。
- 两个角度的“编译”: 源代码到字节码,字节码到机器码(JIT)。
回答提问:
好的面试官,之所以说 Java 编译和解释共存,是因为 Java 程序的执行过程包含了这两个步骤。
首先,我们编写的 Java 源代码会通过 编译器(javac
) 被整体编译成字节码,也就是 .class
文件。这就像传统编译型语言的编译过程。
然后,当我们运行这个 .class
文件时,Java 虚拟机(JVM) 会逐条读取字节码并进行解释执行。这类似于解释型语言的执行方式。
因此,Java 程序需要先经过编译生成字节码,然后再由 JVM 解释执行,所以我们说 Java 语言既具有编译型语言的特征,也具有解释型语言的特征。
此外,在常见的 HotSpot 虚拟机中,为了解决解释执行效率相对较低的问题,还引入了 即时编译(JIT)技术。JVM 会对那些运行频率很高的热点代码(字节码)进行分析,并将它们直接编译成本地机器指令来执行,而不是每次都进行解释。这进一步提高了 Java 程序的运行性能。
所以,当我们谈到 Java 的“编译”时,可以从两个角度理解:一是源代码被整体编译成字节码;二是运行过程中,热点代码对应的字节码会被 JIT 编译成机器码。
🔥🌟Q: 谈谈你对 Java 注解的理解,解决了什么问题?
思考过程:
这个问题考察对 Java 注解的概念、本质、解析方式以及作用的理解。
- 概念: 特殊的注释,用于修饰类、方法、变量,提供信息供编译或运行时使用。
- 本质: 继承了 Annotation 接口的特殊接口。
- 解析方法: 编译期直接扫描,运行期通过反射处理。
- 解决的问题: 编译检查、代码生成、运行时处理,简化配置和开发。
回答提问:
好的面试官,Java 注解(Annotation) 是从 Java 5 开始引入的一个特性,它可以看作是一种特殊的注释。与普通的注释不同,注解不仅可以提供信息给开发者阅读,更重要的是,它还可以携带信息供程序在编译期或者运行时使用,用来修饰类、方法、变量、参数等等。
从本质上讲,一个注解其实是一个继承了 Annotation
接口的特殊接口。
注解的生效需要被解析。常见的解析方法有两种:
- 编译期直接扫描:编译器在编译 Java 代码的时候会扫描对应的注解并进行处理。比如 @Override 注解,编译器会检查被标记的方法是否真正覆盖了父类的方法。
- 运行期通过反射处理:很多框架中的注解(比如 Spring 的 @Value、@Component)都是在程序运行时通过反射机制来获取和处理的。
注解主要解决了以下几个问题:
- 编译检查:比如使用 @Override 注解可以确保方法是正确地重写了父类的方法,如果在编译期发现没有对应的父类方法,编译器会报错,从而提前发现潜在的错误。@FunctionalInterface 注解可以确保接口是函数式接口。
- 代码生成:有些注解可以在编译期自动生成代码,比如 Lombok 框架的 @Getter 和 @Setter 注解可以自动生成类的 getter 和 setter 方法,大大减少了样板代码的编写。JPA 的 @Entity 注解可以帮助生成数据库相关的代码。
- 运行时处理:很多框架利用注解在运行时进行特定的处理。比如 Spring 框架的 @Autowired 注解用于实现依赖注入,@Transactional 注解用于管理事务。这些注解在程序运行时会被容器解析并执行相应的逻辑,从而简化了配置和开发。
总的来说,注解提供了一种元数据的方式,可以方便地为代码添加额外的信息,并且可以在编译期或运行时被工具或框架处理,从而实现各种各样的功能,极大地提高了开发效率和代码的可维护性。
🔥🌟Q: Java的IO知道吗?能对比一下 BIO、NIO 和 AIO 吗?(结合操作系统的IO理解)
思考过程:
这个问题考察对 Java IO 模型演进的理解,需要从同步/异步、阻塞/非阻塞以及适用场景等方面进行对比。
- BIO(Blocking IO): 同步阻塞,一个连接一个线程,资源开销大,适用于连接数少且固定的场景。
- NIO(Non-blocking I/O): 同步非阻塞,一个请求一个线程(通过多路复用器),适用于连接数多且轻量级操作的场景。
- AIO(Asynchronous IO): 异步非阻塞,IO 操作由操作系统完成再通知服务器,适用于连接数多且重量级操作的场景。
回答提问:
好的面试官,BIO、NIO 和 AIO 是 Java 中三种不同的 I/O 模型,它们在处理 I/O 操作的方式上有着显著的区别:
BIO(Blocking IO) 是同步阻塞的 I/O 模型。在 BIO 中,服务器会为每一个客户端连接创建一个独立的线程进行处理。如果连接上没有任何数据可读或可写,那么对应的线程就会一直阻塞在那里,直到有数据准备好。这种模型实现简单,但当连接数增多时,会创建大量的线程,导致服务器资源消耗过大,并发能力较差。它适用于连接数目比较小且固定的架构。
NIO(Non-blocking I/O) 是同步非阻塞的 I/O 模型。与 BIO 不同的是,在 NIO 中,客户端的连接请求会注册到多路复用器上,比如 Linux 下的 epoll
。多路复用器会轮询所有注册的连接,只有当某个连接上有 I/O 请求时,才会通知服务器启动一个线程进行处理。这样,一个线程可以处理多个连接的 I/O 事件,大大减少了线程的创建和管理开销。NIO 适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器。但需要注意的是,NIO 本身是同步的,I/O 操作的实际读写过程仍然会阻塞当前线程,只不过这个阻塞发生在数据准备就绪之后。
AIO(Asynchronous IO) 是异步非阻塞的 I/O 模型。在 AIO 中,客户端发起的 I/O 请求(比如读操作)会直接交给操作系统去处理,应用程序不需要等待,当操作系统完成 I/O 操作后,会通过回调或者事件通知的方式告知服务器。服务器只需要在 I/O 操作完成后处理结果即可,整个 I/O 过程都是异步的,不会阻塞应用程序的主线程。AIO 适用于连接数目多且连接比较长(重操作)的架构,比如文件服务器。
🔥🌟Q: final
, finally
, finalize
这三个关键字有什么区别?
思考过程:
这个问题考察对 Java 中三个相似但功能不同的关键字的理解,需要分别说明它们的用途。
- final: 修饰类(不可继承)、方法(不可重写)、变量(不可修改)。
- finally:try-catch 语句块中用于执行必须完成的代码的块。
- finalize:Object 类的方法,在对象被垃圾回收前调用(不推荐使用)。
回答提问:
好的面试官,final
, finally
, 和 finalize
是 Java 中三个非常容易混淆的关键字,它们的作用是完全不同的:
final
是一个修饰符,它可以用来修饰类、方法和变量。
- 当用 final 修饰一个类时,表示这个类不能被继承。
- 当用 final 修饰一个方法时,表示这个方法不能被子类重写(override)。
- 当用 final 修饰一个变量时,表示这个变量一旦被赋值后,其值就不能再被修改,相当于一个常量。对于 final 的成员变量,必须在声明时或者在构造方法中进行初始化。
finally
是用于 try-catch
语句块中的一个关键字。finally
块中的代码总是会被执行,无论在 try
块中是否发生了异常,以及是否被 catch
块捕获。通常我们在 finally
块中进行一些清理工作,比如关闭资源(如文件流、数据库连接)等,以确保这些操作一定会完成。
finalize
是 Object
类的一个方法。它的设计目的是在对象被垃圾回收器回收之前,提供一个执行清理操作的机会。当垃圾回收器准备回收一个对象时,会先调用该对象的 finalize()
方法。但是需要注意的是,JVM 并不保证 finalize()
方法一定会被调用,而且它的执行时机也不确定,甚至可能引起一些性能问题,因此从 JDK 9 开始,finalize()
方法已经被标记为 deprecated(不推荐使用)。我们通常会使用其他的机制,比如 try-with-resources
语句或者显式的资源关闭方法来管理资源的回收。
🔥💡Q: 你能介绍一下 Java 近年来引入的一些新特性吗?
思考过程:
这个问题考察对 Java 新版本特性的关注程度,需要了解一些 LTS 版本(Java 8、11、17、21)中引入的重要特性。
- Java 8: Lambda 表达式、Stream API、默认方法、Optional 类、Date Time API。
- Java 11: ZGC。
- Java 17: Sealed Classes, Pattern Matching for switch。
- Java 21: Virtual Threads。
回答提问:
好的面试官,Java 近年来引入了很多令人兴奋的新特性,我主要关注一些 LTS(长期支持)版本中的重要更新:
在 Java 8 中,引入了非常多的重要特性,包括:
- Lambda 表达式:它允许我们将函数作为方法的参数进行传递,使得代码更加简洁和灵活。
- Stream API:这是一个强大的用于处理集合数据的 API,引入了函数式编程风格,可以进行高效的数据处理操作。
- 默认方法:允许在接口中定义带有实现的方法,这在不破坏现有接口实现的情况下向接口添加新功能非常有用。
- Optional 类:用于解决空指针异常问题,提供了一种更加优雅的方式来处理可能为空的值。
- Date Time API:对原有的日期和时间处理 API 进行了大幅改进,提供了更加易用和强大的日期时间操作功能。
在 Java 11 中,一个值得关注的特性是 ZGC,这是一个可伸缩的低延迟垃圾收集器,尤其适用于大堆内存的应用场景。
在 Java 17 中,也引入了一些重要的特性,比如 Sealed Classes(密封类),它限制了哪些类可以继承一个类,增强了代码的安全性。还有 Pattern Matching for switch(switch 语句的模式匹配),使得 switch
语句可以更加灵活地处理不同类型的数据。
最新的 LTS 版本是 Java 21,其中一个备受关注的新特性是 Virtual Threads(虚拟线程),它旨在大幅度降低并发编程的复杂性,并提高应用程序的吞吐量。虚拟线程是轻量级的用户态线程,可以极大地减少线程创建和管理的开销。
总的来说,Java 语言一直在不断发展和完善,新的特性使得我们能够编写更加高效、简洁和可靠的代码。我会持续关注 Java 的新版本和新特性。
🔥💡Q: 什么是 SPI(Service Provider Interface)?
思考过程:
这个问题考察对 Java SPI 机制的理解,需要说明其定义、作用以及应用场景。
- 定义: 服务提供者接口,由服务调用方定义接口规范,服务提供者实现。
- 作用: 解耦服务接口和实现,提高扩展性和可维护性,修改或替换实现不影响调用方。
- 应用场景: Spring 框架、数据库驱动加载、日志接口、Dubbo 扩展实现。
- 类比: 公司 H 定义芯片生产标准,多家芯片公司按标准提供不同芯片。
回答提问:
好的面试官,SPI(Service Provider Interface) 字面意思是“服务提供者接口”。我的理解是,它是一种服务发现机制,允许服务调用方在运行时发现和使用服务提供者提供的实现。
更具体地说,SPI 通常由服务调用方定义一套接口规范,然后由不同的服务提供者根据这个规范提供具体的实现。这样,服务调用方只需要依赖这个接口,而不需要关心具体的实现是由哪个服务提供者提供的。
SPI 的主要作用是将服务接口和具体的服务实现分离开来,实现了服务调用方和服务实现者之间的解耦,从而能够提高程序的扩展性和可维护性。当我们需要修改或者替换服务的实现时,只需要更换对应的服务提供者,而不需要修改服务调用方的代码。
Java 本身就提供了 SPI 机制,并且被很多框架广泛使用。例如:
- Spring 框架就使用了 SPI 机制来实现很多扩展点。
- 数据库加载驱动也是通过 SPI 机制实现的。
- 日志接口(如 SLF4j)的底层绑定不同的日志实现(如 Logback、Log4j)也用到了 SPI。
- 像 Dubbo 这样的 RPC 框架,其扩展功能的实现也大量使用了 Java 的 SPI 机制。
简单来说,SPI 提供了一种灵活的方式来扩展和替换系统的组件,使得系统更加开放和可配置。
面向对象(主要考察面向对象的特性、接口抽象类的区别、equals、hashcode等Object的方法的作用以及为什么要重写、还有String三剑客的区别以及应用场景)
🔥⭐Q: 面向对象的三大特性是什么?你能简单解释一下吗?
思考过程:
这个问题考察对 面向对象编程基本概念的掌握,需要清晰地阐述封装、继承和多态的定义和作用。
- 封装: 隐藏内部状态,通过方法暴露操作。
- 继承: 子类继承父类的属性和方法,实现代码重用。
- 多态: 父类引用指向子类对象,实现行为的多样性。
回答提问:
好的面试官,面向对象的三大特性是封装、继承和多态,它们是面向对象编程的核心理念:
- 封装可以理解为将一个对象的状态(属性)和行为(方法)捆绑在一起,并对外部隐藏对象的内部实现细节,只暴露必要的接口供访问。这样做的好处是提高了代码的安全性和可维护性。
- 继承是一种机制,允许我们基于已存在的类(称为父类或超类)来创建新的类(称为子类或派生类)。子类会自动拥有父类的属性和方法(除了私有的),并且还可以扩展自己的属性和方法。继承的主要目的是实现代码的重用,提高开发效率。
- 多态指的是一个对象可以表现出多种形态。在 Java 中,多态通常通过父类的引用指向子类的对象来实现。这样,同一个方法调用在不同的子类对象上可能会产生不同的行为。多态是面向对象编程中非常重要的特性,它提高了代码的灵活性和可扩展性。
🔥⭐Q: 你是如何理解面向对象中的多态性的?它有什么作用?
思考过程:
这个问题深入考察对 多态性的理解,需要从概念、前提条件、特点以及在软件设计中的作用等方面进行阐述。
- 概念: 父类引用指向子类对象,同一行为的不同表现形式。
- 前提条件: 继承/实现关系。
- 特点: 运行时确定方法调用,不能调用子类特有方法,执行子类重写的方法。
- 作用: 提高可维护性、可扩展性,实现更灵活的设计。
回答提问:
好的面试官,我对多态的理解是这样的:多态是指同一个行为,在不同的对象上会产生不同的结果或表现形式。它就像我们说“跑”这个动作,对于人来说是跑步,对于汽车来说是行驶。
在 Java 中,多态主要体现在父类的引用变量可以指向子类的对象。要实现多态,通常需要满足几个前提条件:
- 首先,必须存在继承或实现关系。也就是说,要有父类和子类,或者接口和实现类。
- 其次,子类通常会重写(override)父类的方法。
多态具有以下几个特点:
- 方法的调用在运行时才能确定,也就是说,到底执行的是哪个类的方法,要看实际指向的是哪个子类对象。
- 通过父类引用,我们只能调用父类中定义的方法,而不能直接调用子类特有的方法。如果需要调用子类特有的方法,可能需要进行向下转型。
- 如果子类重写了父类的方法,那么通过父类引用调用的就是子类重写后的方法。
多态在面向对象设计中非常重要,它主要有以下作用:
- 提高了代码的可维护性:当需要增加新的功能时,只需要添加新的子类并重写相应的方法,而不需要修改原有的代码。
- 提高了代码的可扩展性:通过多态,我们可以更容易地扩展系统的功能,因为新的子类可以无缝地融入到现有的框架中。
- 实现了更灵活的设计:多态使得我们可以编写更加通用的代码,可以处理不同类型的对象,只要它们是同一个父类或接口的子类型。
🔥⭐Q: 重载和重写有什么区别?
思考过程:
这个问题考察对 Java 中多态性的两种重要体现方式的理解,需要从定义、发生阶段、条件等方面进行区分。
- 重载(Overload):定义:同一个类中多个同名方法,但参数列表不同。发生阶段:编译期。条件:方法名相同,参数列表不同(类型、个数、顺序)。返回值类型和访问修饰符可以不同。
- 重写(Override):定义:子类对父类方法的重新实现。发生阶段:运行期。条件:方法名、参数列表、返回值类型(或子类型)、抛出异常范围(或子范围)、访问修饰符(更大或相等)相同。
回答提问:
好的面试官,重载(Overload)和重写(Override)是 Java 中实现多态性的两种重要方式,它们之间有明显的区别:
重载(Overload) 是指在同一个类中,可以定义多个方法名相同但参数列表不同的方法。这里的参数列表不同指的是参数的类型、个数或者顺序不同。重载主要体现在编译时多态,编译器会根据方法调用的参数类型和个数来决定调用哪个重载方法。重载的特点是发生在同一个类中,方法名相同,但参数列表必须不同,返回值类型和访问修饰符可以相同也可以不同。
重写(Override) 是指在子类中,可以对父类中继承过来的方法进行重新实现。重写主要体现在运行时多态,当程序运行时,会根据对象的实际类型(而不是声明类型)来决定调用哪个版本的方法。重写的特点是发生在父子类之间,子类的方法名、参数列表必须与父类被重写的方法相同,子类方法的返回值类型应该与父类方法返回值类型相同或者是其子类型,抛出的异常范围应该小于等于父类方法抛出的异常范围,子类方法的访问修饰符的权限必须大于等于父类方法的访问修饰符的权限。需要注意的是,父类的私有方法(private)、final 方法和静态方法(static)不能被子类重写。
简单来说,重载是“同一个类中的不同方法”,通过参数列表的不同来区分;而重写是“子类对父类方法的重新实现”,方法签名必须相同。
🔥⭐Q: Java 中的基本数据类型有哪些?对应的包装类型是什么?各自占用多少字节呢?
思考过程:
这个问题考察对 Java 基本数据类型及其包装类的掌握情况,需要列出所有基本类型、对应的包装类以及它们占用的字节数。
- 六种数字类型:四种整数型:byte (1 字节), short (2 字节), int (4 字节), long (8 字节)。两种浮点型:float (4 字节), double (8 字节)。
- 一种字符类型:char (2 字节)。
- 一种布尔型:boolean (JVM 实现相关,通常认为 1 字节或 4 字节)。
- 对应的包装类型:Byte, Short, Integer, Long, Float, Double, Character, Boolean.
回答提问:
好的面试官,Java 中有 8 种基本数据类型,它们分别是:
- 整数类型: byte:占用 1 个字节,取值范围是 -128 到 127。对应的包装类型是 Byte。short:占用 2 个字节,取值范围是 -32,768 到 32,767。对应的包装类型是 Short。int:占用 4 个字节,取值范围大约是正负 21 亿。对应的包装类型是 Integer。long:占用 8 个字节,取值范围非常大。对应的包装类型是 Long。
- 浮点类型: float:占用 4 个字节,是单精度浮点数。对应的包装类型是 Float。double:占用 8 个字节,是双精度浮点数。对应的包装类型是 Double。
- 字符类型: char:占用 2 个字节,表示一个 Unicode 字符。对应的包装类型是 Character。
- 布尔类型: boolean:它的具体大小在 JVM 规范中没有明确规定,通常在逻辑上可以认为是 1 个比特位,但在实际 JVM 实现中,可能占用 1 个字节或者 4 个字节。它只有两个值:true 和 false。对应的包装类型是 Boolean。
🔥⭐Q: 能谈谈自动装箱与拆箱吗?它们的原理是什么?
思考过程:
这个问题考察对 Java 中基本类型和包装类型之间自动转换机制的理解,需要说明装箱和拆箱的概念以及其背后的实现原理。
- 装箱: 基本类型 -> 包装类型。
- 拆箱: 包装类型 -> 基本类型。
- 原理: 装箱调用 valueOf() 方法,拆箱调用 xxxValue() 方法。
回答提问:
好的面试官,自动装箱(Autoboxing) 和 自动拆箱(Unboxing) 是 Java 5 引入的非常方便的特性,它允许我们在基本数据类型和对应的包装类型之间进行自动的转换。
自动装箱指的是将一个基本数据类型的值自动转换成对应的包装类型对象。例如,我们可以直接将一个 int
类型的值赋给一个 Integer
类型的变量,而不需要显式地调用 new Integer()
方法。
自动拆箱则是指将一个包装类型的对象自动转换成对应的基本数据类型的值。例如,我们可以直接将一个 Integer
类型的对象赋给一个 int
类型的变量,而不需要显式地调用 intValue()
方法。
它们的原理其实是通过编译器在编译时期完成的。当我们进行装箱操作时,比如 Integer i = 10;
,编译器会自动将其转换为调用包装类的 valueOf()
方法,也就是 Integer i = Integer.valueOf(10);
。而当我们进行拆箱操作时,比如 int n = i;
,编译器会自动将其转换为调用包装类对应的 xxxValue()
方法,比如对于 Integer
就是 intValue()
,也就是 int n = i.intValue();
。
虽然自动装箱和拆箱使得代码更加简洁,但在使用时也需要注意,频繁的拆装箱操作可能会带来一定的性能开销,因此在对性能有较高要求的场景下,我们应该尽量避免不必要的拆装箱操作。
🔥⭐Q: Java 中的参数传递是值传递还是引用传递?
思考过程:
这个问题考察对 Java 方法参数传递机制的理解,这是一个经典的面试题,需要明确 Java 中只有值传递。
- Java 中只有值传递。
- 基本类型: 传递的是变量值的副本。
- 引用类型: 传递的是引用地址的副本。
回答提问:
好的面试官,Java 中参数传递的方式是值传递。
对于基本数据类型(比如 int
, float
, boolean
等),在方法调用时,会将实际参数的值复制一份传递给形式参数。因此,在方法内部对形式参数的修改不会影响到方法外部的实际参数。
对于引用数据类型(比如对象),传递的也是实际参数的值的副本,但是这个值是一个引用地址。也就是说,方法内部的形式参数和方法外部的实际参数指向的是内存中的同一个对象。因此,在方法内部,我们可以通过形式参数来修改对象的属性,这些修改会反映到方法外部的实际参数所引用的对象上。但是,如果在方法内部改变了形式参数的引用,比如让它指向一个新的对象,那么这不会影响到方法外部的实际参数。
所以,总结来说,Java 中无论是基本类型还是引用类型,都是按值传递的。对于引用类型,传递的是引用的值的副本,也就是对象在堆内存中的地址的副本。
🔥⭐Q: 接口和抽象类有什么共同点和区别?你能从设计目的、继承性等方面谈谈吗?
思考过程:
这个问题考察对 接口和抽象类的深入理解和应用场景的区分,需要从定义、实现、继承、设计目的、字段和静态方法、访问修饰符等方面进行对比。
- 共同点: 都不能被实例化,都可以包含抽象方法。
- 区别:定义和实现: 接口只有抽象方法(JDK 1.8 后有默认和静态方法),抽象类可以有抽象和具体方法。继承性: 类可以实现多个接口(多重继承),只能继承一个抽象类(单一继承)。设计目的: 接口定义规范和契约,抽象类提供通用类型定义和代码重用。字段和静态方法: 接口不能定义字段(JDK 1.8 后可定义静态常量),不能定义静态方法(JDK 1.8 后可以);抽象类可以定义字段和静态方法。访问修饰符: 接口方法默认 public,抽象类方法可以有 public, protected, private。
回答提问:
好的面试官,接口(Interface)和抽象类(Abstract Class)在面向对象编程中都是用来实现抽象的机制,它们既有共同点,也有很多重要的区别:
共同点:
- 都不能被直接实例化:我们不能直接创建接口或抽象类的对象。
- 都可以包含抽象方法:接口中的方法在 JDK 1.8 之前都是抽象的,抽象类可以包含抽象方法(没有具体实现的方法)。
区别:
- 定义和实现:接口是一种完全抽象的类型,它只定义了方法的签名,没有具体的实现(在 JDK 1.8 之后可以有默认方法和静态方法)。抽象类是一种不完全抽象的类型,它可以包含抽象方法,也可以包含已经实现的具体方法。
- 继承性:一个类可以实现多个接口,这被称为多重实现。而一个类在 Java 中只能继承一个抽象类,这是单一继承的特性。
- 设计目的:接口的主要目的是定义一种规范或契约,它规定了实现该接口的类应该具备哪些方法,强调的是“能做什么”。抽象类的主要目的是为子类提供一个公共的、通用的类型定义和代码重用,它通常表示一种“是什么”的关系,子类是父类的一种特殊类型。
- 字段和静态方法:在 JDK 1.8 之前,接口中不能定义实例字段,只能定义静态常量。抽象类中可以定义各种类型的字段。在 JDK 1.8 中,接口允许定义静态方法,而抽象类一直都可以定义静态方法。
- 访问修饰符:接口中的方法默认是 public 的,并且不能使用其他的访问修饰符。抽象类中的方法可以使用 public、protected 或 private 等不同的访问修饰符。
从设计目的来看,如果多个不相关的类需要实现某些相同的功能,那么使用接口更合适,因为它更侧重于定义行为的规范。而如果一组相关的类具有一些共同的属性和行为,并且需要代码重用,那么使用抽象类可能更合适。
🔥⭐Q: 你了解深拷贝和浅拷贝的区别吗?什么是引用拷贝?
思考过程:
这个问题考察对 对象拷贝的理解,需要区分深拷贝、浅拷贝以及引用拷贝的概念和实现方式。
- 引用拷贝: 简单赋值,两个引用指向同一对象。
- 浅拷贝: 创建新对象,但对于对象中的引用类型属性,只拷贝引用。
- 深拷贝: 创建新对象,并递归拷贝对象中所有引用类型属性指向的对象。
回答提问:
好的面试官,我对深拷贝、浅拷贝和引用拷贝是有了解的:
- 引用拷贝是最简单的一种拷贝方式。当我们将一个对象的引用赋值给另一个变量时,这两个变量实际上指向内存中的同一个对象。这意味着,如果我们通过一个变量修改了对象的状态,那么通过另一个变量访问该对象时,也会看到相同的修改。这本质上不是创建一个新的对象。
- 浅拷贝会创建一个新的对象,这个新对象的属性值如果是基本数据类型,那么会直接拷贝过来。但是,如果属性是引用类型,那么只会拷贝这个引用的地址,而不会拷贝引用指向的实际对象。也就是说,新对象和原始对象会共享同一个引用类型的属性所指向的对象。因此,如果通过新对象修改了这个共享的引用对象,原始对象也会受到影响。
- 深拷贝也会创建一个新的对象,并且会递归地拷贝原始对象中所有引用类型属性所指向的对象,直到拷贝到基本数据类型为止。这样,新对象和原始对象完全独立,它们拥有各自的属性,包括引用类型的属性所指向的对象也是不同的。所以,修改新对象不会影响原始对象。
简单来说,引用拷贝只是复制了对象的地址,浅拷贝只复制了对象本身和其直接引用的对象(引用指向的还是同一个),而深拷贝则会复制对象及其所有关联的对象。
🔥⭐Q: 能详细解释一下 Java 中的 hashCode()
和 equals()
方法吗?它们与 ==
操作符有什么区别?
思考过程:
这个问题深入考察对 hashCode()
和 equals()
方法以及 ==
操作符的理解,需要说明它们的作用、联系以及在比较对象时的不同行为。
- hashCode(): 返回对象的哈希值,用于哈希表。
- equals(): 判断两个对象是否相等,默认比较引用,通常需要重写比较内容。
- ==: 比较基本类型的值,比较引用类型的内存地址。
- 联系: 如果 equals() 相等,hashCode() 必须相等;如果 hashCode() 不相等,equals() 一定不相等。
回答提问:
好的面试官,hashCode()
和 equals()
方法是 Java 中 Object
类定义的两个非常重要的方法,它们与 ==
操作符在比较对象时有着不同的作用和行为:
- hashCode() 方法:这个方法用于获取对象的哈希码,它是一个 int 类型的数值。hashCode() 方法的主要作用是支持基于哈希的集合,比如 HashMap 和 HashSet。在这些集合中,对象首先会根据其哈希码被分配到不同的“桶”中,这样在查找对象时,只需要在对应的桶中进行搜索,而不需要遍历整个集合,从而提高了查找效率。根据 Java 规范,如果两个对象通过 equals() 方法比较是相等的,那么它们的 hashCode() 值也必须相等。
- equals(Object obj) 方法:这个方法用于判断当前对象是否与另一个对象 obj 相等。在 Object 类中,equals() 方法的默认实现是使用 == 操作符来比较两个对象的引用是否指向同一个内存地址。但是,在很多情况下,我们希望比较的是对象的内容是否相等,这时就需要在自己的类中重写 equals() 方法,定义我们自己的相等逻辑。例如,对于 String 类,它的 equals() 方法被重写为比较两个字符串的内容是否相同。
- == 操作符:== 操作符用于比较两个变量的值。对于基本数据类型来说,== 比较的是它们的值是否相等。而对于引用数据类型来说,== 比较的是这两个引用是否指向内存中的同一个对象,也就是比较它们的内存地址是否相等。
它们之间的区别和联系可以总结如下:
- == 既可以用于基本数据类型的比较,也可以用于引用数据类型的比较;而 equals() 只能用于引用数据类型的比较。
- == 比较引用数据类型时比较的是对象的内存地址;而 equals() 在没有被重写的情况下,行为与 == 相同,也是比较内存地址。但是,通常我们会重写 equals() 方法来比较对象的内容是否相等。
- 根据 Java 规范,如果两个对象通过 equals() 方法比较是相等的,那么它们的 hashCode() 值必须相等。反过来,如果两个对象的 hashCode() 值不相等,那么它们通过 equals() 方法比较也一定不相等。但是,如果两个对象的 hashCode() 值相等,它们通过 equals() 方法比较不一定相等(这被称为哈希冲突)。
因此,在自定义类中,如果需要将对象放入基于哈希的集合中,或者需要自定义对象相等的逻辑,通常都需要同时重写 hashCode()
和 equals()
方法,并遵守它们之间的约定。
🔥⭐Q: String
、StringBuffer
和 StringBuilder
这三者有什么区别?String
为什么是不可变的?
思考过程:
这个问题考察对 Java 中处理字符串的三个常用类的理解,需要区分它们的特性,特别是 String
的不可变性及其原因。
- String: 不可变,适用于少量字符串操作。
- StringBuffer: 可变,线程安全,性能相对较低。
- StringBuilder: 可变,非线程安全,性能较高。
- String 不可变原因:final 类修饰,内部 char[] value 被 final 修饰且私有,没有提供修改方法。
回答提问:
好的面试官,String
、StringBuffer
和 Stri
都是 Java 中用于处理字符串的类,它们之间主要的区别在于:
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
在准备暑期实习时,从等各个平台搜集了上千篇真实面经,自己整理得到了面试题,根据题目在面试题中出现的频率以及我自己、交流群、好朋友面试被问到的频率进行了分类整理,所有内容经过科学分类与巧妙标注,针对性强: 得到⭐🌟💡三种级别的,其中⭐为最高频的题目(类似神哥总结的高频八股),只是我自己整理出来的这部分更多一些,🌟为中高频题目(冲击大厂的uu们建议看)、💡为低频题,可以作为补充,时间充裕再看!