2023年Java最新面试题,附详解答案
写在前面:本篇面试题整理是我在广州地区进行面试整理得出,常见的基本都在下面了。基本都是可以直接在面试时用白话回答的答案总结,面试时照此回答即可,有其他常见面试问题也欢迎在评论区补充。
首次发布:202 3年 1月
更新日期:2023年 5月2023年1月更新修改了一些可能造成误解的回答,并使用粗体将回答的关键部分标出,你可以使用便于自己记忆的方法组织语言来回答,只需要回答中包含这些答案关键字即可。
2023年最新常见Java开发面试题、面试常问Java面试题整理(附白话答案)
一、Java基础部分面试题
1. Java面向对象的三个特征
封装 :对象只需要 选择性的对外公开一些属性和行为 。
继承 :子对象可以 继承父对象的属性和行为 ,并且可以在其之上进行修改以适合更特殊的场景需求。
多态 : 允许不同类的对象对同一消息做出响应 。
2. Java中基本的数据类型有哪些 以及他们的占用字节
数据类型 | 占用字节 |
---|---|
byte | 1 |
short | 2 |
int | 4 |
long | 8 |
float | 4 |
double | 8 |
char | 2 |
boolean | 4 |
3. int和Integer的区别
int是Java中的 原始类型 ,Integer是Java为int提供的 封装类 ,他们有不同的特征和用法,包括 大小、速度、默认值
。
4. String、StringBuilder、StringBuffer的区别及使用场景
String一旦定义就 不可改变 ,可空赋值。操作少量数据时使用。
StringBuilder 可改变,线程不安全 。操作单线程大量数据时使用。
StringBuffer 可改变,线程安全 。操作多线程大量数据时使用。
5. ArrayList、Vector和LinkedList的区别及使用场景
ArrayList和Vector都是 使用数组方式存储数据 ,允许 按序号索引元素 ,但是插入数据会涉及到元素移动等内存操作,所以
索引快插入慢 。
ArrayList懒加载 默认大小10 每次扩容1.5倍 线程不安全 性能较高
Vector 实例化时初始化 默认大小10 每次扩容2倍 线程安全 性能较低 已弃用
额外回答加分项:
多读少写建议使用CopyOnWriteArrayList
CopyOnWriteArrayList原理是发生修改的时候复制一份
多写少读或读写比较均匀建议使用Connections.synchronizedList
LinkedList 使用 双向链表 方式存储数据,插入只需要记录本项的前后项,索引需要向前或向后进行遍历,所以 插入速度较快,线程不安全
,频繁在任意位置插入和删除的情况可以使用,如果需要多线程访问,可以使用Connections.synchronizedList()或ConcurrentLinkedQueue
6. Collection和Collections的区别
Collection是 集合类上级接口 ,继承他的主要有List和Set
Collections是 集合类的帮助类 ,提供了对集合的搜索、排序、线程安全化等操作。
7. List和Map的区别
List是 存储单列数据的集合 ,Map是 存储键值对双列数据的集合 。
List存储的数据是 有顺序且可重复 的,Map存储的数据是 无顺序,键不可重复,值可重复的 。
8. HashMap和HashTable的区别
HashMap是Map接口的实现, 非线程安全,允许空键值 。
HashTable是Dictionary的子类, 线程安全,不允许空键值 。几乎被淘汰,建议使用ConcurrentHashMap来替代它。
HashMap使用的是快速失败迭代器,在迭代器创建后,除非通过迭代器自身的remove或者add方法,其他任何方式的修改都会抛出异常。
9. HashMap底层实现原理和扩容机制
JDK1.8以前: 数组+单链表 的组合,以键值对的方式存储元素。
JDK1.8及以后:引入 红黑树 结构,添加元素时,若 链表个数大于8,链表会转换为红黑树 ,反之 小于6时会修剪或还原成链表结构
。
选择6和8可以有效防止频繁的链表和红黑树转换。
扩容条件:
- 存放新值的时候当前已有元素个数大于阈值。
- 存放新值的时候当前存放数据发生hash碰撞(当前key计算的hash值换算出来的数组下标位置已经存在值)
默认容量是16,负载因子0.75,所以扩容阈值是12。
每次扩容的容量是原有的2倍。
10. HashMap什么样的类适合作为键
String最为常见,因为String对象不可变,且 重写了equals和hashcode方法 。
不可变性是必要的 ,如果key的hashcode存入和获取是不一致,就无法找到。
获取对象时需要用到equals和hashCode方法,正确的重写这两个方法是非常重要的,因为两个不相等的对象返回不同的hashCode的话,碰撞的几率就会小些,就可以提高HashMap的性能。
11. final、finally、finalize的区别
final 用于修饰属性、方法和类,分别表示 属性不可变,方法不可覆盖,类不可继承 。
finally 是 异常处理语句结构 的一部分,表示总是执行。
finalize 是Object类的一个方法,在 GC执行时会调用被回收对象的此方法 。
12. sleep()和wait()的区别
sleep()是 Thread类 的,wait()是 Object类 的方法
sleep不会释放锁,wait会释放锁。
sleep可在任意地方使用,wait notify notifyAll只能在synchronized块方法中使用。
sleep必须捕获异常,而wait不需要。
13. 抽象类和接口的区别、以及使用场景
抽象类中可以有构造方法、静态方法、普通方法、普通成员变量。接口中不能有。
抽象类中的抽象方法访问类型可以是public、protected和默认类型,接口中只能是public。
抽象类中的静态成员变量访问类型可以任意,接口中只能是public的。
一个类只能继承一个类,但是可以实现多个接口。
抽象类和子类为“是不是”的关系。主要用于为一些类提供公共实现代码。
接口和实现为“有没有”的关系。主要用于代码的扩展性和可维护性。
14. Overload(重载)和Override(重写)的区别
重载 是一个类中多态性的一种表现, 在一个类中定义了多个同名的方法,他们有不同的参数列表 。
重写 是父类与子类之间多态的一种表现, 子类中定义了与父类有相同名称和参数的方法时,子类对象使用该方法会调用子类中的定义。
15. forward(转发)和redirect(重定向)的区别
forward 是 服务器请求资源 ,服务器访问目标URL,把响应内容发给用户,用户不知道数据是从哪来的。
redirect 是 服务器向客户端发送一个状态码,告知重新请求该URL 。
16. 连接池的工作机制
服务器 启动时会建立一定数量的池连接 ,客户端需要连接时,池会 返回一个未使用的连接并将其标记为忙 ,如果没有空闲连接,池会
新建一定数量的连接 ,当连接使用完毕后,池会将其标记为空闲。
17. 什么是序列化
序列化就是一种 用来处理对象流的机制 ,就是 将对象的内容进行流化,可以对流化后的对象进行读写操作,也可以将流化后的对象传输于网络之间。
可通过实现 java.io.Serializable 接口来实现序列化。
二、第三方框架部分
1. 什么是AOP、Spring AOP的底层原理是什么
AOP是 面向切面编程 ,用于 在不改变原有逻辑的基础上增加一些额外的功能 ,如事务管理、日志、缓存、权限控制等。
Spring AOP是 基于代理 的。
如果目标对象 实现了接口 ,则默认采用 JDK动态代理 。
如果目标对象 没有实现接口 ,则采用 CgLib进行动态代理 。
如果目标对象实现了接口,且 强制CgLib 代理,则采用CgLib动态代理。
2. 什么是IOC、IOC注入方式有哪些
IOC翻译为 控制反转 ,他还有个别名为DI( 依赖注入 )。
IOC就是由IOC容器来 负责对象的生命周期和对象之间的关系 。
控制反转就是 本来应该你做的事情,让系统去做 ,比如通常获取一个对象需要通过new,而使用IOC则是IOC将对象创建后注入到被注入的对象中。
注解注入 (Spring)、 构造器注入 、 setter方法注入 、接口方式注入(不推荐)
3. Mybatis中 #{} 和 ${}的区别
#{}是 预编译 ,可 防止SQL注入 。
${}是直接 拼接 在SQL语句中。
4. Spring Boot的核心注解是什么,它是由哪几个注解组成的
核心注解: @SpringBootApplication
包含:
@SpringBootConfiguration 实现配置文件功能
@EnableAutoConfiguration 打开自动配置功能
@CompoentScan 组件扫描功能
5. SpringBoot 怎么读取配置文件
属性上使用@Value注解
类上使用@ConfigurationProperties注解
读取指定文件注解可在类上使用@PropertySource(不支持yml文件读取)
注入Environment对象获取到。
6. SpringCloud和Dubbo的区别
SpringCloud采用基于HTTP的 REST API ,Dubbo采用 RPC 方式。
7. SpringCloud的Hystrix断路器特性
请求熔断
:请求服务失败量超过一定比例(默认50%)断路器会切换到开路状态,这时所有请求不会发送到后端服务,断路器在保持开路状态一段时间后(默认5秒),自动切换到半开路状态。这时如果下一次请求成功,断路器切回闭路状态,否则重新切换到开路状态。
服务降级 :对于查询操作,可以实现一个fallback方法。当请求服务出现异常时,可以使用fallback方法返回的值。
依赖隔离
:通过线程池来实现资源隔离,比如一个服务调用另外两个服务,如果这两个服务在同一线程池,那么如果一个服务卡住,后面的请求又来了,就会导致后面的请求都会卡住等待。
请求缓存 :缓存上次请求结果,返回给后续请求。
请求合并 :把多个请求合并成一个请求,提升效率。
三、MySQL数据库部分
1. 事物的四大特性和隔离级别
原子性 :不可分割的操作单元,要么全部成功,要么回滚。
一致性 :如果执行事物之前数据库是一致的,那么执行后还是一致的。
隔离性 :事物操作之间彼此独立和透明,互不影响。
持久性 :事物一旦提交,其结果就是永久的。
未提交读 :允许脏读,其他事物只要修改了数据,即使未提交,本事物也能看到修改后的数据值。
提交读 :只能读取到已提交的数据。
可重复读 (innoDB默认):无论其他事物是否修改并提交了数据,这个事物中的数据不受影响。
串行读 :完全串行化的读,每次读都要获得锁,读写相互都会阻塞。
2. MySQL优化相关
使用更小的整数类型 、尽可能的 定义字段为not null (否则会导致索引复杂)、
只创建需要的索引、分库分表。
使用explain检查复杂SQL语句、LIMIT语句尽量要跟order by或distinct、
插入多条数据时使用单条INSERT语句。
3. MySQL存储引擎InnoDB和MyISAM的区别
InnoDB支持 事物 ,MyISAM不支持。
InnoDB支持 外键 ,MyISAM不支持。
InnoDB是 聚集索引 ,MyISAM是非聚集索引。索引和数据文件是分离的。
InnoDB必须要有 主键 (没有会自己找或创建),MyISAM可以没有。
InnoDB不保存表的行数,MyISAM用了一个变量保存表的行数。
InnoDB支持表、行级锁 默认行级锁,MyISAM只支持表级锁。
4. MySQL在哪些情况下不使用索引
like查询 使用%开头不能使用索引 ,但用%结尾的可以使用索引。
where语句中使用<>或!=。
where语句中使用or,且没有把or中的所有字段加上索引。
where语句中对字段表达式操作。
where语句中使用NOT IN。使用简单的IN会使用索引。
5. MySQL分库分表策略
垂直切分 :某个表字段过多,可以 将不常用或字段长度较大的字段拆分出去到扩展表 中。
水平切分 :分为库内分表和分库分表,是根据表内数据的逻辑关系, 按照不同的条件分散到多个数据库或表 中。
四、扩展阅读
1. 关于Spring对JDK和CgLib动态代理的选择
通过查阅Spring-AOP包中的org.springframework.aop.framework.DefaultAopProxyFactory
类得到以下代码:
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
Class<?>[] ifcs = config.getProxiedInterfaces();
return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));
}
}
可以看到,Spring先使用isOptimize()方法、isProxyTargetClass()方法以及hasNoUserSuppliedProxyInterfaces()方法进行判断,这三个方法的含义分别如下:
isOptimize()
是否对生成代理策略进行优化
当返回值为true时为进行优化,如果有接口就代理接口(使用JDK动态代理),没有接口代理类(CGLIB代理)
当返回值为false时为不进行优化(default)
isProxyTargetClass()
是否强制使用CGLIB来实现代理
当返回值为true时为强制使用CgLib来实现代理
当范围值为false时为不强制使用CgLib来实现代理,而是首选JDK来实现代理(default)
hasNoUserSuppliedProxyInterfaces()
判断代理的对象是否只有指定了SpringProxy的接口或未实现接口
当返回值为true时代表没有实现接口或仅实现了指定SpringProxy的接口
当返回值为false时代表实现了接口,直接使用JDK动态代理
当了解这三个方法的含义后,接下来阅读代码就很简单了。
如果以上三个方法有任意方法返回值为true,就进入下一步判断,如果所有的返回值均为false,说明即不强制使用CgLib,或又实现了接口,则使用JDK动态代理。
在第二步判断中,首先对其targetClass进行了判空,然后判断它是否为接口或代理类,如果是则使用JDK动态代理。
反之,则使用CgLib进行代理。
2. 为什么CgLib可以代理任何类,但还是需要JDK的动态代理?CgLib和JDK动态代理的区别。
这就不得不说到CgLib的特点:创建速度慢但执行速度快,而JDK的动态代理与其刚好相反:创建速度快但执行速度慢。
如果在程序运行时不断地使用CgLib去创建代理的话,系统运行的性能会大打折扣,所以建议一般在系统初始化时采用CgLib来创建代理,并放入Spring的ApplicationContext中。