阿里菜鸟 java后端开发最新面试题
1. 介绍 Java 面向对象特性
面向对象是一种编程范式,它将现实世界中的事物抽象为对象,对象具有属性(称为字段或属性)和行为(称为方法)。面向对象编程的设计思想是以对象为中心,通过对象之间的交互来完成程序的功能,具有灵活性和可扩展性,通过封装和继承可以更好地应对需求变化。
Java面向对象的三大特性包括:封装、继承、多态:
-
封装:封装是指将对象的属性(数据)和行为(方法)结合在一起,对外隐藏对象的内部细节,仅通过对象提供的接口与外界交互。封装的目的是增强安全性和简化编程,使得对象更加独立。
-
继承:继承是一种可以使得子类自动共享父类数据结构和方法的机制。它是代码复用的重要手段,通过继承可以建立类与类之间的层次关系,使得结构更加清晰。
-
多态:多态是指允许不同类的对象对同一消息作出响应。即同一个接口,使用不同的实例而执行不同操作。多态性可以分为编译时多态(重载)和运行时多态(重写)。它使得程序具有良好的灵活性和扩展性。
2. 重载和重写的区别是什么?
重载是指在同一个类中定义多个同名方法,而重写是指子类重新定义父类中的方法。
-
重载(Overloading)指的是在同一个类中,可以有多个同名方法,它们具有不同的参数列表(参数类型、参数个数或参数顺序不同),编译器根据调用时的参数类型来决定调用哪个方法。
-
重写(Overriding)指的是子类可以重新定义父类中的方法,方法名、参数列表和返回类型必须与父类中的方法一致,通过@override注解来明确表示这是对父类方法的重写。
3. ArrayList和HashSet的底层数据结构是什么?
简单来说,ArrayList使用数组存储元素,适合频繁访问元素的场景;HashSet基于哈希表实现,适合频繁添加、删除元素和判断元素是否存在的场景。
-
ArrayList 的底层数据结构:基于动态数组实现,默认初始容量为 10,当元素数量超过当前容量时,会自动进行扩容(通常扩容为原容量的 1.5 倍),允许存储重复元素,且元素有序(按照插入顺序保存)。
-
HashSet 的底层数据结构:基于哈希表(HashMap)实现,HashSet 内部维护了一个 HashMap 对象,元素作为 HashMap 的 key 存储,value 则是一个固定的 Object 对象。不允许存储重复元素,且元素无序。JDK 8 及以上版本中,当哈希表中某个链表的长度超过 8 时,会将该链表转换为红黑树,以提高查询效率。
4. Bean的生命周期说一下?

Bean 的生命周期可以简单分成 4 个阶段,流程很清晰:
-
创建阶段:Spring 先找到需要管理的 Bean,实例化对象,然后给属性赋值(依赖注入)。
-
初始化阶段:
-
先调用各种 Aware 接口(比如 BeanNameAware、ApplicationContextAware),让 Bean 知道自己的 ID、上下文等信息。
-
接着执行 BeanPostProcessor 的前置处理方法。
-
然后调用 InitializingBean 的 afterPropertiesSet 方法,或者自定义的 init-method 初始化方法。
-
最后执行 BeanPostProcessor 的后置处理方法,此时 Bean 才算初始化完成。
-
-
使用阶段:Bean 在容器中待命,随时被程序调用。
-
销毁阶段:容器关闭时,先调用 DisposableBean 的 destroy 方法,或者自定义的 destroy-method 销毁方法,完成资源清理。
5. 数据库索引为字段(a,b,c),请问查询哪些字段能命中索引?
查询语句:select * from table where a=? and b> ? and c=?
能命中索引的部分是字段a和b,但c无法命中索引,但是字段c的条件可以通过 “索引下推”。**
具体分析:
-
联合索引
(a, b, c)的结构决定了先匹配a,再匹配b,最后是c。 -
由于
b使用了范围查询(b> ?),按照最左前缀原则,c原本无法直接使用索引进行匹配。 -
但开启索引下推后,存储引擎在遍历索引时,会同时检查
c=?的条件,直接过滤掉不满足c=?的记录,而无需将这些记录回表查询完整数据后再过滤。
虽然c不参与索引的范围定位,但通过索引下推,c=?的条件会在索引层面被提前过滤,减少了回表的记录数,提高查询效率。
6. 讲一下进程,线程,协程区别
-
首先,我们来谈谈进程。进程是操作系统中进行资源分配和调度的基本单位,它拥有自己的独立内存空间和系统资源。每个进程都有独立的堆和栈,不与其他进程共享。进程间通信需要通过特定的机制,如管道、消息队列、信号量等。由于进程拥有独立的内存空间,因此其稳定性和安全性相对较高,但同时上下文切换的开销也较大,因为需要保存和恢复整个进程的状态。
-
接下来是线程。线程是进程内的一个执行单元,也是CPU调度和分派的基本单位。与进程不同,线程共享进程的内存空间,包括堆和全局变量。线程之间通信更加高效,因为它们可以直接读写共享内存。线程的上下文切换开销较小,因为只需要保存和恢复线程的上下文,而不是整个进程的状态。然而,由于多个线程共享内存空间,因此存在数据竞争和线程安全的问题,需要通过同步和互斥机制来解决。
-
最后是协程。协程是一种用户态的轻量级线程,其调度完全由用户程序控制,而不需要内核的参与。协程拥有自己的寄存器上下文和栈,但与其他协程共享堆内存。协程的切换开销非常小,因为只需要保存和恢复协程的上下文,而无需进行内核级的上下文切换。这使得协程在处理大量并发任务时具有非常高的效率。然而,协程需要程序员显式地进行调度和管理,相对于线程和进程来说,其编程模型更为复杂。
7. 进程间通信的几种方式?
Linux 下进程间通信主要有 6 种核心方式,各自特点很明确:
-
管道:分匿名和命名。匿名管道(如
|)只用于父子进程,单向通信,数据存内核;命名管道有文件系统标识,可跨无亲缘关系进程,均按先进先出传无格式流。 -
消息队列:内核中的消息链表,数据是带类型的消息体(需收发方匹配类型),解决管道无格式问题,但需用户态与内核态数据拷贝,速度不及时。
-
共享内存:内核分配共享空间,进程直接访问,无需拷贝,是最快的方式,但多进程访问需同步,否则数据易错乱。
-
信号量:本质是资源计数器,通过 P/V 原子操作控制,用于保护共享资源(互斥)或同步进程,解决共享内存的竞争问题。
-
信号:异步通知机制,内核通知进程系统事件(如
Ctrl+C、kill命令),进程可默认处理、捕捉或忽略,仅SIGKILL和SIGSTOP无法捕捉 / 忽略。 -
Socket:可跨主机通信,也支持本地通信,分 TCP(可靠)、UDP(不可靠)、本地进程间通信三种类型,是跨主机进程通信的核心方式。
8. 设计模式的六大原则是什么?
设计模式的六大原则可以理解为写代码时的 “潜规则”,目的是让代码更灵活、好维护,说起来很直白:
-
单一职责:一个类就干一件事,比如订单类只处理订单相关的,别掺和支付的事,改起来不影响其他。
-
开闭原则:对扩展开放,对修改关闭。比如要加新功能,最好是新增代码,而不是改原来的老代码,这样不容易出 bug。
-
里氏替换:子类能直接替掉父类用。比如父类是 “鸟”,子类 “麻雀” 能飞,但若子类是 “企鹅”(不会飞),就别继承 “鸟”,不然用的时候会出问题。
-
依赖倒置:高层别依赖低层,都依赖抽象。比如老板(高层)不用管员工(低层)具体怎么干活,只要求按 “工作规范”(抽象)来做,换个人也照样合作。
-
接口隔离:接口别搞太大,按需拆分。比如 “动物” 接口,别把 “会飞”“会游泳” 都塞进去,不然兔子(不会飞也不会游泳)实现这个接口就很别扭。
-
迪米特法则:少知道别人的细节,也就是 “只和朋友说话”。比如 A 类要做件事,直接找 B 类就行,别去打听 B 类里面 C 类的情况,关系越简单越好。
这六个原则说白了就是让代码 “职责清、改得少、换得顺、依赖松、接口简、关系浅”,最终好维护、好扩展。
9. Redis在项目中的用途是什么?
Redis 是一种基于内存的数据库,对数据的读写操作都是在内存中完成,因此读写速度非常快,常用于缓存,消息队列、分布式锁等场景。
-
缓存: Redis最常见的用途就是作为缓存系统。通过将热门数据存储在内存中,可以极大地提高访问速度,减轻数据库负载,这对于需要快速响应时间的应用程序非常重要。
-
排行榜: Redis的有序集合结构非常适合用于实现排行榜和排名系统,可以方便地进行数据排序和排名。
-
分布式锁: Redis的特性可以用来实现分布式锁,确保多个进程或服务之间的数据操作的原子性和一致性。
-
计数器 由于Redis的原子操作和高性能,它非常适合用于实现计数器和统计数据的存储,如网站访问量统计、点赞数统计等。
-
消息队列: Redis的发布订阅功能使其成为一个轻量级的消息队列,它可以用来实现发布和订阅模式,以便实时处理消息。
10. Redis数据类型你知道哪些?
Redis 提供了丰富的数据类型,常见的有五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)、Zset(有序集合)。


随着 Redis 版本的更新,后面又支持了四种数据类型:BitMap(2.2 版新增)、HyperLogLog(2.8 版新增)、GEO(3.2 版新增)、Stream(5.0 版新增)。Redis 五种数据类型的应用场景:
-
String 类型的应用场景:缓存对象、常规计数、分布式锁、共享 session 信息等。
-
List 类型的应用场景:消息队列(但是有两个问题:1. 生产者需要自行实现全局唯一 ID;2. 不能以消费组形式消费数据)等。
-
Hash 类型:缓存对象、购物车等。
-
Set 类型:聚合计算(并集、交集、差集)场景,比如点赞、共同关注、抽奖活动等。
-
Zset 类型:排序场景,比如排行榜、电话和姓名排序等。
Redis 后续版本又支持四种数据类型,它们的应用场景如下:
-
BitMap(2.2 版新增):二值状态统计的场景,比如签到、判断用户登陆状态、连续签到用户总数等;
-
HyperLogLog(2.8 版新增):海量数据基数统计的场景,比如百万级网页 UV 计数等;
-
GEO(3.2 版新增):存储地理位置信息的场景,比如滴滴叫车;
-
Stream(5.0 版新增):消息队列,相比于基于 List 类型实现的消息队列,有这两个特有的特性:自动生成全局唯一消息ID,支持以消费组形式消费数据。
11. 关系型数据库和NoSQL的区别是什么?
SQL数据库,指关系型数据库 - 主要代表:SQL Server,Oracle,MySQL(开源),PostgreSQL(开源)。
关系型数据库存储结构化数据。这些数据逻辑上以行列二维表的形式存在,每一列代表数据的一种属性,每一行代表一个数据实体。

NoSQL指非关系型数据库 ,主要代表:MongoDB,Redis。NoSQL 数据库逻辑上提供了不同于二维表的存储方式,存储方式可以是JSON文档、哈希表或者其他方式。

选择 SQL vs NoSQL,考虑以下因素。
ACID vs BASE
关系型数据库支持 ACID 即原子性,一致性,隔离性和持续性。相对而言,NoSQL 采用更宽松的模型 BASE , 即基本可用,软状态和最终一致性。
从实用的角度出发,我们需要考虑对于面对的应用场景,ACID 是否是必须的。比如银行应用就必须保证 ACID,否则一笔钱可能被使用两次;又比如社交软件不必保证 ACID,因为一条状态的更新对于所有用户读取先后时间有数秒不同并不影响使用。
对于需要保证 ACID 的应用,我们可以优先考虑 SQL。反之则可以优先考虑 NoSQL。
扩展性对比
NoSQL数据之间无关系,这样就非常容易扩展,也无形之间,在架构的层面上带来了可扩展的能力。比如 redis 自带主从复制模式、哨兵模式、切片集群模式。
相反关系型数据库的数据之间存在关联性,水平扩展较难 ,需要解决跨服务器 JOIN,分布式事务等问题。
12. 场景题:怎么实现二维码扫码登录
二维码扫码登录的核心逻辑其实就是 “用已登录的手机给电脑授权”,具体分五步走:
-
电脑端生成二维码:用户打开网页时,后端生成一个唯一的二维码 ID(比如 UUID),存在 Redis 里,状态设为 “未扫描”,有效期 5 分钟左右。前端用这个 ID 生成二维码展示,同时每隔几秒查一次后端,看状态有没有变。
-
手机扫码:用户用已登录的 APP 扫这个二维码,就能读到里面的 ID,然后手机端带着这个 ID 和自己的登录凭证(比如 token)发给后端,说 “我扫了这个码”。
-
手机确认登录:后端收到后,先验证手机 token 是不是有效(确保手机确实登录着),然后把 Redis 里那个二维码 ID 的状态改成 “已确认”,并关联上用户信息(比如用户 ID)。
-
电脑端获取结果:电脑端一直在轮询,发现状态变成 “已确认”,就知道授权通过了。
-
完成登录:后端给电脑端生成一个登录凭证(比如 session 或 token),返回给前端,电脑端就登录成功了。
这里有几个关键点要注意:一是二维码必须有有效期,防止被复用;二是状态要在 Redis 里管理,查起来快;三是手机确认时一定要验证身份,不能随便授权;四是电脑端轮询可以用长轮询,减少请求次数。
这样整个流程既安全又能保证用户体验,手机上的登录状态就 “传递” 到电脑上了。
13. 智力题:两根香确定15分钟
用两根香确定 15 分钟,核心是利用 “两头烧” 来控制燃烧时间,具体步骤特别直接:
-
第一步,拿两根香同时操作:第一根香两头都点燃,第二根香只点燃一头。
-
第二步,等第一根香烧完。因为它两头烧,原本能烧 1 小时的香,现在 30 分钟就会烧完。这时候,第二根香已经烧了 30 分钟,还剩 30 分钟的燃烧时间。
-
第三步,在第一根香烧完的瞬间,马上把第二根香的另一头也点燃。这时候第二根香剩下的 30 分钟燃烧时间,因为两头烧,时间就会减半,变成 15 分钟。
从点燃第二根香另一头开始,到它完全烧完,这段时间就是 15 分钟。
更多推荐


所有评论(0)