java 面试

8大基本数据类型11

类型 字节 范围
byte 1 -128~127
short 2 -32768~32767
int 4 常用整型
long 8 长整型,后缀 L
float 4 单精度,后缀 F
double 8 双精度,默认小数
char 2 单个字符
boolean 1 true/flase

== 和 equals 区别

==:基本比数值,引用比内存地址

equals:重写后对比对象内容

面向对象 OOP 三大特性

封装

私有化成员,提供 get/set,隐藏内部细节。

权限修饰符:private < 默认 < protected < public

java 复制代码
class Person {
    // 私有属性,外部无法直接访问
    private String name;
    private int age;

    // 公开方法,对外提供访问入口
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    // 校验数据,保证合法
    public void setAge(int age) {
        if(age > 0){
            this.age = age;
        }
    }
}

解释:属性私有化隐藏细节,仅通过方法操作数据,可做数据校验,避免非法赋值。

继承extends

子类复用父类代码,Java单继承。

this:当前对象

super:父类对象、父构造

java 复制代码
// 父类
class Animal {
    public void eat(){
        System.out.println("动物进食");
    }
}
// 子类继承父类,复用父类方法
class Dog extends Animal{

}

public class Test {
    public static void main(String[] args) {
        Dog dog = new Dog();
        // 直接调用继承来的方法
        dog.eat();
    }
}

解释:子类继承父类属性方法,不用重复编写代码,实现代码复用。

多态

父类引用指向子类对象,编译看父类,运行看子类。

java 复制代码
// 父类
class Animal {
    public void shout(){
        System.out.println("动物叫声");
    }
}
// 子类重写方法
class Cat extends Animal{
    @Override
    public void shout() {
        System.out.println("喵喵喵");
    }
}
class Dog extends Animal{
    @Override
    public void shout() {
        System.out.println("汪汪汪");
    }
}

public class Test {
    public static void main(String[] args) {
        // 父类引用指向子类对象
        Animal animal1 = new Cat();
        Animal animal2 = new Dog();
        // 执行子类重写后的方法
        animal1.shout();
        animal2.shout();
    }
}

解释:同一个父类行为,不同子类展现不同效果,运行时执行子类逻辑,提升程序拓展性。

重写和重载

重载 Overload同类中,方法名相同,参数个数 / 类型 / 顺序不同,返回值无关。

java 复制代码
public class Demo {
    // 重载方法
    public void say(){}
    public void say(String name){}
    public void say(int age){}
}

重写 Override子类重写父类方法,方法名、参数、返回值一致;权限不能缩小。

java 复制代码
class Father{
    public void run(){
        System.out.println("父类跑步");
    }
}
class Son extends Father{
    // 重写
    @Override
    public void run(){
        System.out.println("子类快跑");
    }
}

三大关键字

final

修饰类:不能被继承

修饰方法:不能重写

修饰变量:常量,只能赋值一次

static

静态变量 / 方法:属于类,所有对象共享

静态代码块:类加载只执行一次

静态不能直接调用非静态

abstract 抽象

抽象类:不能实例化,可含抽象 / 普通方法

抽象方法:无方法体,子类必须重写

final、finally、finalize 区别

final关键字,修饰类、方法、变量,表不可变、不可继承、不可重写。

finally用在 try-catch 里,无论是否异常,finally 代码一定执行。用于关闭资源。

finalizeObject 里的方法,对象被 GC 回收前调用一次,不推荐使用。

String、StringBuilder、StringBuffer

String:底层 final char[] 不可变字符串,每次修改都会生成新对象,效率低、线程安全

StringBuilder:底层 char[] 可变字符串,不生成新对象,效率最高、线程不安全

StringBuffer:底层 char[] 可变字符串,线程安全(加了锁),效率中等

性能速度

StringBuilder > StringBuffer > String

使用场景

少量字符串操作 → 用 String

单线程大量拼接、修改 → 用 StringBuilder

多线程环境大量操作 → 用 StringBuffer

synchronized 与 Lock

对比项 synchronized Lock
实现层面 JVM原生内置 Java代码API实现
锁释放 代码执行完 / 异常自动释放 必须手动 unlock 释放
中断等待 不可中断 可中断等待线程
锁类型 默认非公平锁 支持公平 / 非公平锁切换
等待队列 单个队列 可多个条件队列
性能 低版本较差,新版本优化提升 竞争激烈时性能更优

关键点

异常处理

synchronized 抛异常自动释锁;Lock 必须 finally 手动解锁,死锁风险更高

公平锁

synchronized 非公平;ReentrantLock 构造传 true 开启公平锁

唤醒机制

synchronized 搭配 wait/notify;Lock 搭配 Condition 精准唤醒

适用场景

简单同步、少量并发用 synchronized;高并发、需灵活控制锁逻辑用 Lock

线程创建方式

继承 Thread、实现 Runnable、实现 Callable、线程池

继承 Thread 类

1、继承 Thread

2、重写 run()

3、调用 start() 启动

java 复制代码
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("线程1运行");
    }
}

// 使用
new MyThread().start();

实现 Runnable 接口(无返回值)

1、实现 Runnable

2、重写 run()

3、传入 Thread 启动

java 复制代码
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("线程2运行");
    }
}

// 使用
new Thread(new MyRunnable()).start();

实现 Callable 接口(有返回值)

1、实现 Callable

2、重写 call()

3、可以返回结果、抛出异常

java 复制代码
class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "线程3返回结果";
    }
}

// 使用
FutureTask<String> task = new FutureTask<>(new MyCallable());
new Thread(task).start();
String result = task.get(); // 获取返回值

使用 线程池

通过 ExecutorService 创建线程,复用线程、控制并发通过 ExecutorService 创建线程,复用线程、控制并发。

java 复制代码
ExecutorService pool = Executors.newFixedThreadPool(3);

pool.submit(() -> {
    System.out.println("线程池执行");
});

ThreadPoolExecutor vs Executors

Executors

Executors.newFixedThreadPool(3);

封装好的,不用写 7 个参数

内部用了无界队列 new LinkedBlockingQueue<>()

任务无限堆积 → 内存爆掉 → OOM 宕机

ThreadPoolExecutor

7 大参数完全自定义

队列容量自己设 → 不会无限堆积任务

拒绝策略自己配 → 高并发下不会崩

线程池七大参数

corePoolSize(核心线程数)

maximumPoolSize(最大线程数)

keepAliveTime(空闲线程存活时间)

unit(时间单位)

workQueue(阻塞队列)

threadFactory(线程工厂)

handler(拒绝策略)

java 复制代码
ThreadPoolExecutor pool = new ThreadPoolExecutor(
    2,        // 核心线程
    5,        // 最大线程
    3L,       // 空闲时间
    TimeUnit.SECONDS, // 时间单位
    new ArrayBlockingQueue<>(10), // 队列容量10
    Executors.defaultThreadFactory(), // 线程工厂
    new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);

拒绝策略

AbortPolicy:直接抛出RejectedExecutionException异常,中断任务提交。(默认)

CallerRunsPolicy:把任务退回给调用者线程执行,减缓提交速度,削峰限流。

DiscardPolicy:默默丢弃当前新来任务,无任何提示。

DiscardOldestPolicy:丢弃队列里等待最久的任务,放入新任务。

业务核心、不能丢任务 → CallerRunsPolicy

允许报错、及时感知过载 → AbortPolicy(默认)

实时数据、旧数据无用 → DiscardOldestPolicy

线程生命周期

新建(NEW)→就绪(RUNNABLE)→运行(RUNNING)→阻塞(BLOCKED)→死亡(TERMINATED)

常用切换方法

start():新建→就绪

sleep():运行→阻塞

wait():运行→阻塞

notify():阻塞→就绪

join():进入阻塞等待其他线程

JVM

Java Virtual Machine,Java 虚拟机是一台虚拟的计算机,运行 Java 字节码文件,屏蔽软硬件差异,实现一次编写,到处运行。

核心作用

1、执行.class字节码,不直接运行源码

2、内存管理、垃圾回收、权限校验、代码编译

3、跨平台:Windows/Linux/Mac 都能跑同一套 Java 程序

内存模型

SpringBoot自动装配实现原理

自动装配,其实本质上就是约定大于配置。当你导入了SpringBoot的某一个starter依赖时,这个依赖会有提前写好的一些关于整合到Spring中的配置信息。

回答方式:

注解的形式

  • 启动类上有一个@SpringBootApplication的注解,这个注解是一个组合注解
  • 在上述注解中包含了@EnableAutoConfiguration的注解,这又一个组合注解
  • 在上述注解中,又包含了一个@Import的注解,这个注解导入了AutoConfigurationImportSelector类
  • 在上述导入的类中,就加载了META-INF/spring.factories文件 大致流程如下
  • getAutoConfigurationEntry->getCandidateConfigurations->SpringFactoriesLoader.loadFactoryNames->classLoader.getResources("META-INF/spring.factories")

main方法启动形式

  • main方法的形式。依然要关注到启动类上的注解
  • 在启动类的main方法执行时,优先启动run方法,run方法往里找会找到Spring非常核心的refresh方法
  • 在refresh方法中,会找到执行BeanFactoryPostProcessor的位置
  • 在上述位置会发现加载了一个ConfigurationClassPostProcessor的实例
  • 上述实例会加载被@Configuration修饰的类,启动类本质也是被@Configuration修饰的
  • 根据上述的方式,会基于ConfigurationClassParser的去加载@Import注解,如此依赖前面的启动类中的@Import引入的的示例 AutoConfigurationImportSelector 会被加载到,并且执行它的process的方法,将所有的AutoConfiguration加载上
  • 最好也会走到这个流程
  • getAutoConfigurationEntry->getCandidateConfigurations->SpringFactoriesLoader.loadFactoryNames->classLoader.getResources("META-INF/spring.factories")

Spring创建Bean的过程

  • 首先Spring会根据反射,将对象实例化好
  • 其次将对象进行初始化,将属性赋值
  • 会执行各种Aware接口
  • 会执行BeanPostProcessor的before方法
  • 会执行基于InitializingBean提供的afterPropertiesSet方法
  • 会执行提供很好的init-method方法
  • 会执行BeanPostProcessor的after方法

Spring IOC

IOC 控制反转

原本开发者手动new创建对象、维护依赖,反转为由 Spring 容器全权负责对象实例化、依赖装配、生命周期管理,程序只需直接使用对象。

完整执行流程

  1. 资源加载定位
    读取配置源:XML 文件、注解、JavaConfig 类,扫描项目标注组件
  2. 解析封装 BeanDefinition
    扫描到 @Component、@Bean、XML 标签等,解析类信息,统一封装为 BeanDefinition,存入注册表
  3. 后置处理器处理配置
    BeanFactory 后置处理器修改、完善 BeanDefinition 数据
  4. 实例化 Bean
    容器启动或首次获取 Bean 时,根据 BeanDefinition 反射创建对象实例
  5. 依赖注入
    自动解析属性依赖,通过构造器、setter、字段三种方式完成依赖装配
  6. Bean 初始化
    执行 @PostConstruct 初始化方法
    调用 InitializingBean 接口初始化方法
    执行自定义 init-method
  7. 后置处理加工
    Bean 后置处理器对初始化后的 Bean 做代理、属性修改等增强
  8. 放入单例缓存池
    成品 Bean 存入一级缓存,后续直接复用
  9. 容器销毁
    关闭容器时,执行 @PreDestroy、DisposableBean、destroy-method 销毁逻辑

优势

1、彻底解耦,类无需关心对象创建

1、统一管控对象生命周期

1、依赖自动注入,简化开发

1、便于扩展、测试、事务与 AOP 整合

Spring AOP

依靠动态代理生成代理对象,调用时走代理执行增强逻辑

核心专业术语

切面 Aspect:存放通用增强逻辑的类

连接点 JoinPoint:可以被拦截到的方法

切点 Pointcut:具体要拦截哪些方法

通知 Advice:拦截后执行的增强代码

目标对象 Target:被增强的原始业务类

代理对象 Proxy:AOP 生成的增强后的对象

织入 Weaving:把增强逻辑嵌入目标方法的过程

五种通知类型

@Before 前置通知:方法执行前触发

@AfterReturning 返回通知:方法正常执行完毕触发

@AfterThrowing 异常通知:方法抛出异常触发

@After 后置通知:方法结束必触发(正常 / 异常都走)

@Around 环绕通知:包裹整个方法,可控制执行、拦截、放行

AOP 执行顺序

环绕前置 → 前置通知 → 目标方法 → 返回 / 异常通知 → 后置通知 → 环绕后置

Spirng 事务失效场景

1、同类内部方法调用

java 复制代码
@Service
public class UserService {

    // 没有 @Transactional
    public void methodA() {
        // 内部调用 methodB
        methodB(); 
    }

    @Transactional
    public void methodB() {
        // 数据库操作
    }
}

Spring 事务是基于 AOP 动态代理实现的!只有代理对象调用才会走事务增强。

同类内部调用 → 是 this 对象调用,不是代理对象 → 事务不生效!

解决方案

自己注入自己

把 @Transactional 加到外层方法

2、方法不是 public

java 复制代码
@Transactional
private void method(){ } // 事务失效

Spring 事务代理只支持 public 方法,非 public 方法会被 Spring 直接忽略事务。

解决方案

改成 public

3、异常被 try-catch 吃掉了

java 复制代码
@Transactional
public void test(){
    try{
        // 数据库操作
    }catch(Exception e){
        // 异常被捕获,没有抛出去 → 事务不回滚!
    }
}

Spring 事务默认只对 运行时异常(RuntimeException)和 Error 回滚。

异常被你 catch 住了,Spring 不知道出错了 → 不会回滚。

解决方案

不要吞异常

catch 后重新抛出 throw new RuntimeException(e);

4、抛出的异常类型不对

java 复制代码
@Transactional
public void test() throws Exception {
    throw new Exception(); // 事务不回滚
}

Spring 默认只回滚 RuntimeException,你抛了 Exception(受检异常) → 不回滚

解决方案

@Transactional(rollbackFor = Exception.class)

5、多线程调用(新线程不受 Spring 管理)

java 复制代码
@Transactional
public void test(){
    new Thread(() -> {
        // 数据库操作
    }).start();
}

事务存在当前线程 ThreadLocal 里,新开线程拿不到事务 → 独立连接 → 事务失效。

解决方案

把 @Transactional 加到子线程方法上。

6、Bean 没有被 Spring 管理

没被 Spring 管理 → 不会生成代理 → 事务无效。

7、开启了新的事务 / 传播机制错误

java 复制代码
@Transactional(propagation = Propagation.NOT_SUPPORTED)

传播属性设置错误,导致以非事务方式执行。

8、final /static 方法

java 复制代码
@Transactional
public final void test(){}

无法被代理重写 → 事务失效。

Redis 数据

结构

常用 5 种基础类型

String、List、Set、Hash、Sorted Set(ZSet)

各自适用场景

String:缓存、计数器、验证码、分布式 ID

List:消息队列、栈、排行榜列表

Hash:存储对象、购物车

Set:去重、交集并集、好友共同关注

ZSet:有序排行榜、延时任务

Redis 持久化

两种持久化方式

RDB 快照、AOF 日志,4.0 后支持混合持久化

RDB持久化

定时把整个内存数据生成二进制快照文件dump.rdb,保存到磁盘。

触发方式

自动触发

save 秒数 改动次数:满足条件自动快照

默认配置:save 900 1、save 300 10、save 60 10000

手动触发

save:阻塞主线程,直到快照完成

bgsave:fork 子线程后台生成快照,不阻塞业务

优点

文件体积小,备份、迁移速度快

重启恢复数据速度最快

占用 CPU 低,适合冷备份

缺点

两次快照间新增数据会丢失,无法做到实时持久化

大数据量 fork 子进程,瞬间消耗内存

恢复流程

重启 Redis,自动加载dump.rdb文件还原数据

AOF 持久化

记录每一条写操作命令,追加到日志文件appendonly.aof,重启重放命令恢复数据。

三种刷盘策略

always:每条命令立刻刷盘,数据最安全,性能最差

everysec:每秒刷一次盘,默认策略,丢最多 1 秒数据,均衡性能安全

no:交由系统自动刷盘,性能最高,丢失数据最多

AOF 重写

日志文件会越来越大,Redis 自动精简合并重复命令,压缩文件体积,不丢失实际数据。

优点

数据安全性极高,最多丢失秒级数据

日志可读,误操作可找回命令

缺点

文件体积远大于 RDB

数据恢复速度比 RDB 慢

频繁刷盘轻微损耗性能

对比项 RDB AOF
存储形式 二进制快照 文本命令日志
数据完整性 易丢失最后时段数据 基本不丢失数据
恢复速度
文件大小
性能消耗 偏高

混合持久化

先以 RDB 格式保存全量快照,后续增量写命令用 AOF 追加。

兼顾 RDB 快速恢复 + AOF 高数据安全,生产环境主流配置。

Redis 缓存穿透 缓存击穿 缓存雪崩

缓存穿透

查询数据库不存在的数据,缓存查不到,请求直接打到数据库,恶意空查询可压垮库。

解决方式

空值缓存:查询为空也存入缓存,设置短过期时间

布隆过滤器:提前过滤不存在 key,拦截非法请求

接口参数校验:非法参数直接拦截,不访问缓存与数据库

互斥锁:少量并发兜底防护

缓存击穿

热点高频 Key刚好过期,海量并发瞬间全部直达数据库。

解决方式

永不过期:业务层面手动更新,不设置自动过期

互斥锁:单个线程查库写缓存,其他线程等待

热点数据常驻内存,规避过期临界点

缓存雪崩

大量缓存 key同一时间过期,或 Redis 服务宕机,所有请求全部冲击数据库。

解决方式

过期时间加随机偏移,错开批量过期

Redis 集群高可用,主从 + 哨兵避免单点宕机

服务限流、熔断、降级,挡住过量请求

多级缓存:本地缓存 + Redis 双层防护

数据库索引失效场景

违背最左匹配

字段做运算、函数操作

使用!=、<>、not in

字符串不加引号

or 左右字段无索引

like % 前缀模糊查询

事务四大特性

原子性:事务要么全执行,要么全回滚

一致性:前后数据状态合法统一

隔离性:事务间互不干扰

持久性:提交后数据永久保存

事务隔离级别

读未提交:可查未提交数据,脏读、不可重复读、幻读都存在

读已提交:默认级别,解决脏读,存在不可重复读、幻读

可重复读:InnoDB 默认,解决脏读、不可重复读,存在幻读

串行化:最高级别,全部问题解决,并发性能最低

幂等执行

前端生成唯一请求 ID(requestId)

请求进入后端

后端先查 Redis/DB:该 requestId 是否处理过

已处理 → 直接返回结果,不执行业务

未处理 → 执行业务

执行成功 → 记录 requestId

返回成功

相关推荐
装不满的克莱因瓶11 小时前
【项目亮点四】支付订单超时处理与状态补偿机制设计
java·开发语言·后端·rabbitmq·消息中间件
lsx20240611 小时前
Scala 字符串处理指南
开发语言
小许同学记录成长11 小时前
Qt 自研测控软件-配置测试项
开发语言·qt
迈巴赫车主11 小时前
码蹄集 MC0457符咒封印java
java·数据结构·算法
biter down11 小时前
6:控件操作与鼠标模拟
开发语言·python
摇滚侠11 小时前
Java 零基础全套教程,数据结构与集合源码,笔记 168-174
java·数据结构·笔记
徐安安ye11 小时前
KV Cache的生老病死:FlashAttention里的显存管理全流程
java·服务器·前端
郝学胜-神的一滴11 小时前
CMake 011:跨平台动态库编译
开发语言·c++·嵌入式硬件·qt·程序人生·cmake·liunx
xifangge202511 小时前
jdk版本不一样怎么办?一台电脑如何完美共存 JDK 8/11/17/21?多版本无缝切换与 IDEA 环境隔离实战指南
java·开发语言·jdk·intellij-idea