Java与Spring全家桶面试题详解
一、Java基础
1. Java中的值传递与引用传递
Java只有值传递,没有引用传递。
- 值传递:方法调用时,实参的值被复制给形参
- 引用传递:方法调用时,实参的引用(地址)被传递给形参
在Java中传递对象时,传递的是对象引用的副本,而不是对象本身或引用本身。
public void test(Person p) {
p.setName("Tom"); // 原对象被修改
p = new Person(); // 只改变了形参引用,原对象不变
}
2. Java基本数据类型与包装类
基本数据类型 | 大小 | 默认值 | 包装类 |
---|---|---|---|
byte | 8位 | 0 | Byte |
short | 16位 | 0 | Short |
int | 32位 | 0 | Integer |
long | 64位 | 0L | Long |
float | 32位 | 0.0f | Float |
double | 64位 | 0.0d | Double |
char | 16位 | '\u0000' | Character |
boolean | 1位 | false | Boolean |
区别:
- 基本类型存储在栈中,包装类是对象,存储在堆中
- 基本类型不能为null,包装类可以为null
- 基本类型不能调用方法,包装类可以调用方法
3. 自动装箱与拆箱
- 装箱:基本类型转换为包装类(
int → Integer
) - 拆箱:包装类转换为基本类型(
Integer → int
)
Integer i = 10; // 自动装箱,底层使用Integer.valueOf(10)
int n = i; // 自动拆箱,底层使用i.intValue()
性能影响:
- 频繁装箱拆箱会创建大量对象,增加GC压力
- 在循环或算法中使用包装类会导致性能下降
4. == 与 equals()的区别
比较方式 | 比较内容 | 适用情况 |
---|---|---|
== | 基本类型比较值,引用类型比较引用(内存地址) | 判断是否是同一个对象 |
equals() | 默认与==相同,但很多类重写了此方法比较内容 | 判断对象内容是否相同 |
相同结果的情况:
- 基本类型使用==比较
- 两个引用指向同一对象
- 未重写equals()方法的类,equals()等同于==
- String常量池中的相同字符串
5. String、StringBuilder与StringBuffer的区别
类 | 可变性 | 线程安全 | 性能 | 适用场景 |
---|---|---|---|---|
String | 不可变 | 安全 | 低 | 少量字符串操作 |
StringBuilder | 可变 | 不安全 | 高 | 单线程大量拼接 |
StringBuffer | 可变 | 安全(同步) | 中 | 多线程大量拼接 |
6. final、finally、finalize的区别
关键字 | 用途 |
---|---|
final | 修饰类(不能被继承)、方法(不能被重写)、变量(不能被修改) |
finally | try-catch-finally结构中,保证一定会执行的代码块 |
finalize | Object类方法,对象被垃圾回收前调用,不推荐使用 |
7. static关键字的作用
- 修饰变量:所有实例共享一个静态变量
- 修饰方法:不需要实例即可调用
- 修饰代码块:类加载时执行
- 修饰内部类:不需要外部类实例即可使用
- 静态导入:导入静态成员,直接使用
执行顺序:静态变量/静态代码块(按编写顺序) → 实例变量/实例代码块(按编写顺序) → 构造方法
8. try-catch-finally执行顺序
- 正常情况:执行try块 → 执行finally块
- 异常情况:执行try块(出现异常处中断) → 匹配catch块 → 执行finally块
即使try或catch中有return语句,finally仍会执行,但finally中的修改对基本类型的return值无效(值传递),对引用类型有效(引用传递)。
9. Java反射机制
反射是在运行时检查、修改类、接口、方法和字段的机制。
原理:通过Class对象获取类的所有信息
应用场景:
- 框架开发(Spring IoC、ORM框架)
- 动态代理
- 注解处理
- 测试工具
Class<?> clazz = Class.forName("java.lang.String");
Method[] methods = clazz.getDeclaredMethods();
10. 动态代理实现方式
实现方式 | 原理 | 优缺点 |
---|---|---|
JDK动态代理 | 基于接口,通过InvocationHandler实现 | 只能代理实现了接口的类 |
CGLIB动态代理 | 基于子类,通过MethodInterceptor实现 | 可以代理未实现接口的类,但不能代理final类和方法 |
Javassist | 直接操作字节码 | 更灵活,性能较好 |
11. Java泛型
特性:
- 类型安全
- 消除类型转换
- 支持泛型算法
泛型擦除:编译器在编译时去除泛型信息,替换为Object或上界,保留必要的类型转换。
使用限制:
- 不能用基本类型实例化
- 不能创建泛型数组
- 不能创建泛型异常类
- 不能对静态变量使用泛型类型参数
12. Java序列化机制
序列化:将对象转换为字节流,便于存储或传输 反序列化:将字节流恢复为对象
实现自定义序列化:
- 实现Serializable接口(标记接口)
- 添加serialVersionUID,保证版本兼容
- 使用transient关键字排除不需要序列化的字段
- 重写readObject/writeObject方法自定义序列化逻辑
Serializable与Externalizable区别:
- Serializable是标记接口,自动序列化
- Externalizable需实现readExternal和writeExternal方法,手动控制序列化过程
13. IO与NIO
特性 | BIO (传统IO) | NIO |
---|---|---|
处理方式 | 阻塞IO | 非阻塞IO |
面向 | 流(Stream) | 缓冲区(Buffer) |
通道 | 无 | Channel |
选择器 | 无 | Selector |
适用场景 | 连接数少,IO操作密集 | 连接数多,延迟低 |
NIO核心组件:
- Buffer(缓冲区)
- Channel(通道)
- Selector(选择器)
14. 类加载机制
类加载过程:加载 → 连接(验证、准备、解析) → 初始化 → 使用 → 卸载
类加载器:
- 启动类加载器(Bootstrap ClassLoader):加载JDK核心类
- 扩展类加载器(Extension ClassLoader):加载扩展库类
- 应用类加载器(Application ClassLoader):加载应用程序类
- 自定义类加载器:自定义加载逻辑
双亲委派模型:先委托父加载器加载,父加载器无法加载时子加载器才尝试加载。确保类的唯一性和安全性。
二、Java面向对象
1. 面向对象三大特性
特性 | 描述 | 示例 |
---|---|---|
封装 | 隐藏实现细节,提供公共访问方法 | 私有属性,公共getter/setter |
继承 | 子类继承父类的特性,实现代码复用 | class Dog extends Animal {} |
多态 | 同一方法不同表现形式 | 方法重写,接口多实现 |
2. 抽象类与接口的区别
特性 | 抽象类 | 接口 |
---|---|---|
实现方式 | extends | implements |
构造方法 | 可以有 | 不能有 |
成员变量 | 任意访问修饰符 | 默认public static final |
成员方法 | 抽象或非抽象 | 默认public abstract(Java 8后有default和static) |
多继承 | 单继承 | 多实现 |
使用场景:
- 抽象类:表示"是什么"关系,多个相关类有共同特性
- 接口:表示"能做什么"关系,不相关类实现相同行为
3. Java 8接口默认方法与静态方法
默认方法:接口中带有实现的方法,用default关键字修饰 静态方法:接口中的静态方法,属于接口,不属于实现类
引入原因:
- 向已有接口添加方法而不破坏已有实现
- 提供工具方法
interface Vehicle {
default void start() {
System.out.println("Default start");
}
static void compare(Vehicle v1, Vehicle v2) {
// 比较两个车辆
}
}
4. 方法重载与方法重写
特性 | 方法重载(Overloading) | 方法重写(Overriding) |
---|---|---|
定义 | 同一个类中同名不同参数的方法 | 子类重新实现父类方法 |
条件 | 方法名相同,参数不同(类型、个数、顺序) | 方法签名完全相同,返回类型兼容 |
访问修饰符 | 无限制 | 不能比父类更严格 |
异常 | 无限制 | 不能比父类更宽泛 |
多态类型 | 编译时多态(静态绑定) | 运行时多态(动态绑定) |
5. Java多态实现原理
多态通过动态绑定实现,具体过程:
- 编译器确定对象的静态类型和方法签名
- JVM查找实际对象的实际类型
- 根据实际类型找到方法表
- 调用方法表中对应的方法实现
虚方法表:每个类都有一个虚方法表(vtable),存储该类的所有虚方法(可被重写的方法)地址。子类继承父类的vtable,并替换重写的方法地址。
6. 继承与实现的区别
- 继承(extends):子类与父类是"is-a"关系,获得父类的属性和方法
- 实现(implements):类实现接口,承诺提供特定行为
多重继承:Java不支持类的多重继承,但支持接口的多重继承。可通过接口+内部类实现类似多重继承的效果。
7. Java内部类
类型 | 特点 | 使用场景 |
---|---|---|
成员内部类 | 依赖外部类实例,可访问外部类所有成员 | 强关联的辅助类 |
静态内部类 | 不依赖外部类实例,只能访问外部类静态成员 | 分组相关类 |
局部内部类 | 定义在方法内部,只在方法内可见 | 一次性使用的辅助类 |
匿名内部类 | 没有名字的内部类,创建后立即使用 | 简化接口/抽象类实现 |
8. 匿名内部类
特点:没有名字,在创建时同时实例化
使用限制:
- 只能继承一个类或实现一个接口
- 不能定义构造方法
- 访问外部局部变量必须是final或effectively final
- 不能是静态的
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类");
}
}).start();
9. Java访问修饰符
修饰符 | 类内部 | 同包 | 子类 | 其他包 |
---|---|---|---|---|
public | ✓ | ✓ | ✓ | ✓ |
protected | ✓ | ✓ | ✓ | × |
default(无) | ✓ | ✓ | × | × |
private | ✓ | × | × | × |
10. 构造方法特点
- 名称与类名相同
- 没有返回类型
- 不能被static、final、abstract、native修饰
- 如果没有显式定义,会提供默认无参构造
- 可以重载但不能被继承,也不能被重写
- 总是调用父类构造方法(显式或隐式super())
11. this关键字和super关键字
this:
- 引用当前对象
- 调用当前类构造方法
- 返回当前对象
super:
- 引用父类对象
- 调用父类构造方法
- 调用父类方法
12. 向上转型和向下转型
向上转型:子类对象转为父类类型,自动进行
Animal animal = new Dog(); // 向上转型
向下转型:父类对象转为子类类型,需要显式强制类型转换
Dog dog = (Dog) animal; // 向下转型
出现ClassCastException的原因:试图将父类对象转为子类类型时,实际对象不是子类实例。应使用instanceof进行检查。
三、Java集合框架
1. ArrayList与LinkedList区别
特性 | ArrayList | LinkedList |
---|---|---|
底层实现 | 动态数组 | 双向链表 |
访问方式 | 随机访问 | 顺序访问 |
查找性能 | O(1) | O(n) |
增删性能 | O(n) | O(1) |
内存消耗 | 少 | 多 |
适用场景:
- ArrayList:频繁随机访问,查询操作多
- LinkedList:频繁增删,尤其是首尾操作多
2. Vector与ArrayList区别
特性 | Vector | ArrayList |
---|---|---|
线程安全 | 安全(方法有synchronized) | 不安全 |
性能 | 低 | 高 |
扩容 | 默认2倍 | 默认1.5倍 |
初始版本 | JDK 1.0 | JDK 1.2 |
3. HashMap底层实现原理
JDK 1.7:数组+链表
- 计算key的hashCode
- 对hashCode进行处理得到数组索引
- 如遇哈希冲突,将元素链接到链表尾部
- 链表采用头插法
JDK 1.8优化:数组+链表+红黑树
- 当链表长度大于8且数组长度大于64,链表转为红黑树
- 当红黑树节点数小于6,红黑树转为链表
- 链表采用尾插法,避免JDK 1.7在并发环境下形成环形链表
- 优化了高位运算的hash算法
4. HashMap、Hashtable与ConcurrentHashMap区别
特性 | HashMap | Hashtable | ConcurrentHashMap |
---|---|---|---|
线程安全 | 否 | 是(同步方法) | 是(分段锁/CAS) |
性能 | 高 | 低 | 中(并发高) |
允许null键值 | 是 | 否 | 否 |
初始容量 | 16 | 11 | 16 |
扩容方式 | 2倍 | 2倍+1 | 2倍 |
ConcurrentHashMap实现线程安全:
- JDK 1.7:分段锁(Segment)
- JDK 1.8:CAS + synchronized
5. Comparable与Comparator接口区别
特性 | Comparable | Comparator |
---|---|---|
包位置 | java.lang | java.util |
排序方法 | compareTo | compare |
实现位置 | 待比较类内部 | 独立比较器类 |
灵活性 | 低(一种排序) | 高(多种排序) |
使用示例:
// Comparable
public class Person implements Comparable<Person> {
private String name;
private int age;
@Override
public int compareTo(Person o) {
return this.age - o.age; // 按年龄升序
}
}
// Comparator
Comparator<Person> ageComparator = new Comparator<Person>() {
@Override
public int compare(Person p1, Person p2) {
return p1.getAge() - p2.getAge();
}
};
// Java 8 Lambda
Comparator<Person> nameComparator =
(p1, p2) -> p1.getName().compareTo(p2.getName());
6. 线程安全的集合类
集合类 | 实现方式 | 特点 |
---|---|---|
Vector, Hashtable | 同步方法 | 性能较差,一次仅一个线程访问 |
Collections.synchronizedXXX | 同步代码块 | 性能一般,一次仅一个线程访问 |
ConcurrentHashMap | 分段锁/CAS | 性能好,并发性高 |
CopyOnWriteArrayList | 写时复制 | 适用于读多写少场景 |
CopyOnWriteArraySet | 基于CopyOnWriteArrayList | 适用于读多写少场景 |
ConcurrentSkipListMap | 跳表 | 适用于高并发环境下有序map |
7. HashSet与TreeSet区别
特性 | HashSet | TreeSet |
---|---|---|
底层实现 | HashMap | TreeMap |
有序性 | 无序 | 有序(自然顺序或比较器) |
性能 | O(1) | O(log n) |
是否允许null | 允许一个 | 不允许 |
HashSet保证元素不重复:
- 利用HashMap的key不能重复特性
- 插入元素时,以元素为key,固定对象PRESENT为value
- 判断重复依据equals()和hashCode()方法,须同时重写
8. LinkedHashMap和LinkedHashSet底层实现原理
LinkedHashMap:
- 继承自HashMap,在HashMap基础上增加了双向链表
- 维护插入顺序或访问顺序(可配置)
- 可实现LRU缓存
LinkedHashSet:
- 基于LinkedHashMap实现
- 保证插入顺序的Set
不同点:
- 比HashMap/HashSet多维护了元素顺序
- 迭代性能更好,但插入/删除性能略差
9. CopyOnWriteArrayList原理
核心思想:写时复制,读写分离
实现机制:
- 读操作不加锁,直接读取旧数组
- 写操作加锁,复制一份新数组,在新数组上修改,然后原子替换旧数组
- 适用于读多写少的场景
缺点:
- 内存占用高
- 写操作性能较差
- 数据一致性是最终一致性,不保证实时一致性
10. Collection和Collections区别
Collection:集合框架的根接口,定义了集合的基本操作
Collections:工具类,提供集合操作的静态方法
Collections常用方法:
- 排序:sort, reverse, shuffle
- 查找:binarySearch, min, max
- 批量操作:fill, copy, addAll
- 线程安全包装:synchronizedXXX
- 不可修改包装:unmodifiableXXX
- 单例集合:singletonXXX
四、Java多线程与并发
1. 进程和线程的区别
特性 | 进程 | 线程 |
---|---|---|
定义 | 程序的一次执行 | 进程中的执行单元 |
资源 | 独立的内存空间 | 共享进程的内存空间 |
通信 | 进程间通信(IPC) | 共享内存,简单 |
切换开销 | 大 | 小 |
健壮性 | 一个进程崩溃不影响其他进程 | 一个线程崩溃整个进程崩溃 |
Java线程与操作系统线程:
- JDK 1.2前:绿色线程,由JVM管理
- JDK 1.2后:原生线程,1:1映射到操作系统线程
- JDK 19+:虚拟线程(纤程),M:N映射,由JVM调度到操作系统线程
2. 创建线程的方式
方式 | 实现步骤 | 优缺点 |
---|---|---|
继承Thread类 | 继承Thread,重写run方法 | 简单,但不能继承其他类 |
实现Runnable接口 | 实现接口,实例作为Thread参数 | 可继承其他类,更好的封装 |
实现Callable接口 | 实现call方法,结合FutureTask使用 | 可获取返回值,可抛出异常 |
线程池 | 提交任务到线程池 | 复用线程,控制并发数,推荐使用 |
3. 线程的生命周期
状态 | 描述 | 转换条件 |
---|---|---|
NEW | 线程创建但未启动 | Thread对象创建后 |
RUNNABLE | 可运行状态 | 调用start(),等待CPU分配时间片 |
BLOCKED | 阻塞状态 | 等待获取synchronized锁 |
WAITING | 等待状态 | 调用wait(), join(), LockSupport.park() |
TIMED_WAITING | 超时等待 | 调用sleep(), wait(timeout), join(timeout) |
TERMINATED | 终止状态 | 线程执行完毕或抛出异常 |
4. synchronized关键字原理
原理:基于监视器(Monitor)实现,在字节码中使用monitorenter和monitorexit指令
锁升级过程:
- 无锁 → 偏向锁 → 轻量级锁 → 重量级锁
- 通过对象头中的Mark Word记录锁状态
- 偏向锁:只有一个线程访问
- 轻量级锁:多个线程交替执行
- 重量级锁:多个线程竞争,阻塞等待
对象锁与类锁区别:
- 对象锁:锁实例,不同实例互不影响
- 类锁:锁类,影响所有实例
5. volatile关键字作用
作用:
- 保证可见性:修改立即对其他线程可见
- 禁止指令重排:保证有序性
- 不保证原子性:复合操作仍需加锁
底层实现:
- 内存屏障(Memory Barrier)
- 写操作时,强制将修改刷新到主内存
- 读操作时,强制从主内存读取最新值
- 插入屏障防止指令重排
6. ThreadLocal原理和应用场景
原理:
- 每个Thread维护一个ThreadLocalMap
- 以ThreadLocal对象为key,存储线程私有数据
- 不同线程访问同一ThreadLocal,获取自己线程的数据
应用场景:
- 线程上下文数据传递(用户身份、事务控制)
- 数据库连接、Session管理
- 线程安全的单例模式
防止内存泄漏:
- 显式调用remove()方法
- 使用try-finally代码块确保清理
- 避免使用static修饰ThreadLocal
7. 线程池核心参数
七大核心参数:
- corePoolSize:核心线程数,长期保持
- maximumPoolSize:最大线程数
- keepAliveTime:空闲线程存活时间
- unit:时间单位
- workQueue:任务队列
- threadFactory:线程工厂
- handler:拒绝策略
合理设置:
- CPU密集型:核心数+1
- IO密集型:核心数*2或CPU核心数/(1-阻塞系数)
- 混合型:根据监控和性能测试调整
8. 线程池执行流程
- 核心线程未满,创建新线程执行
- 核心线程已满,任务入队列
- 队列已满,创建临时线程(不超过最大线程数)
- 临时线程已满,触发拒绝策略
拒绝策略:
- AbortPolicy:抛出异常(默认)
- DiscardPolicy:丢弃任务
- DiscardOldestPolicy:丢弃最早任务
- CallerRunsPolicy:调用者线程执行
9. Java常见线程池
线程池 | 特点 | 适用场景 |
---|---|---|
FixedThreadPool | 固定数量线程 | 负载稳定的服务 |
CachedThreadPool | 弹性线程数 | 短暂异步小任务 |
ScheduledThreadPool | 定时执行任务 | 周期性任务 |
SingleThreadExecutor | 单线程执行 | 顺序执行任务 |
10. 线程安全的实现方式
- 互斥同步:synchronized、ReentrantLock
- 非阻塞同步:CAS操作、原子类
- 无同步方案:栈封闭、ThreadLocal、不可变对象
- 并发容器:ConcurrentHashMap、CopyOnWriteArrayList
- 线程安全类库:java.util.concurrent包
11. Lock接口与synchronized区别
特性 | Lock | synchronized |
---|---|---|
实现 | 接口,需显式实现 | JVM层面实现 |
获取/释放 | 显式调用lock/unlock | 自动获取/释放 |
响应中断 | 支持 | 不支持 |
超时尝试 | 支持 | 不支持 |
公平锁 | 支持 | 不支持 |
非阻塞获取 | 支持 | 不支持 |
灵活性 | 高 | 低 |
性能 | JDK 1.6前更好,后续差异不大 | JDK 1.6后优化显著 |
ReentrantLock特性:
- 可重入:同一线程可多次获取锁
- 公平/非公平:控制获取锁的顺序
- 可中断:lockInterruptibly()支持中断
- 超时获取:tryLock(timeout)
- 条件变量:newCondition()
12. 读写锁ReentrantReadWriteLock
特点:
- 读锁共享:多个线程可同时读
- 写锁独占:一个线程写,其他线程阻塞
- 写锁可降级为读锁:写锁->获取读锁->释放写锁
- 读锁不能升级为写锁
适用场景:读多写少的高并发场景,如缓存
13. 死锁
死锁条件(四个必要条件):
- 互斥:资源不能同时被多个线程使用
- 占有并等待:持有资源的同时等待其他资源
- 不可剥夺:资源只能由持有者自愿释放
- 循环等待:形成环形等待链
预防死锁:
- 破坏占有并等待:一次性申请所有资源
- 破坏不可剥夺:等待超时机制
- 破坏循环等待:按顺序申请资源
- 使用tryLock避免无限等待
14. Callable与Runnable区别
特性 | Callable | Runnable |
---|---|---|
返回值 | 有(泛型) | 无 |
异常 | 可抛出 | 不可抛出 |
执行方法 | call() | run() |
使用方式 | 结合Future/FutureTask | Thread构造或线程池 |
获取线程执行结果:
- Future接口的get()方法
- FutureTask同时实现Future和Runnable接口
- CompletableFuture异步回调
15. CompletableFuture实现异步编程
优势:
- 链式调用,避免回调地狱
- 组合多个异步操作
- 异常处理机制
- 支持延迟计算
- 丰富的超时和取消操作
CompletableFuture.supplyAsync(() -> fetchData())
.thenApply(data -> processData(data))
.thenAccept(result -> useResult(result))
.exceptionally(ex -> handleException(ex));
16. 线程间通信方式
通信方式 | 实现机制 | 特点 |
---|---|---|
wait/notify | Object类方法,配合synchronized | 必须先获得锁,易造成死锁 |
park/unpark | LockSupport类方法 | 可在任何地方调用,不需要获取锁 |
Condition | Lock接口方法创建条件变量 | 更精确的线程唤醒,可有多个条件 |
BlockingQueue | 阻塞队列实现生产者消费者 | 高级抽象,使用简单 |
CountDownLatch | 计数器,等待所有线程完成 | 一次性使用 |
CyclicBarrier | 循环屏障,等待所有线程到达 | 可重复使用 |
Semaphore | 信号量,控制并发访问数 | 控制资源并发访问 |
Exchanger | 线程间交换数据 | 适用于两个线程交换数据 |
17. CountDownLatch、CyclicBarrier、Semaphore区别
工具类 | 作用 | 应用场景 |
---|---|---|
CountDownLatch | 一个或多个线程等待其他线程完成操作 | 主线程等待所有子线程执行完毕 |
CyclicBarrier | 多个线程相互等待,到达同一同步点 | 多线程计算,最后合并结果 |
Semaphore | 控制同时访问特定资源的线程数量 | 限流,控制并发访问数 |
18. CAS原理
CAS(Compare And Swap):比较并交换,原子操作
原理:
- 读取旧值A
- 计算新值B
- 比较内存中的值是否仍为A
- 如果是,则更新为B;否则重试或返回失败
问题:
- ABA问题:值从A变成B又变回A,CAS无法感知
- 循环时间长开销大:自旋CAS长时间不成功,CPU消耗大
- 只能保证一个变量的原子操作
解决ABA:
- AtomicStampedReference:添加版本号
- AtomicMarkableReference:添加标记位
五、Java 8及以上新特性
1. Lambda表达式语法
Lambda表达式是函数式编程的核心,语法简洁:
// 基本语法:(参数) -> {表达式}
Runnable r = () -> System.out.println("Hello Lambda!");
// 单参数无需括号
Consumer<String> c = s -> System.out.println(s);
// 多条语句需要花括号
Comparator<Integer> comp = (a, b) -> {
int result = a.compareTo(b);
return result;
};
// 返回值自动推断
Function<String, Integer> f = s -> s.length();
2. 函数式接口
函数式接口:只有一个抽象方法的接口,使用@FunctionalInterface注解标记
内置函数式接口:
- Consumer<T>:接收参数,无返回值
- Supplier<T>:提供数据,无参数
- Function<T, R>:输入T,输出R
- Predicate<T>:输入T,返回boolean
- UnaryOperator<T>:一元操作,类型相同
- BinaryOperator<T>:二元操作,类型相同
3. 方法引用
类型 | 语法 | 示例 |
---|---|---|
静态方法 | 类名::静态方法 | Integer::parseInt |
实例方法 | 实例::方法 | str::length |
对象方法 | 类名::实例方法 | String::compareToIgnoreCase |
构造方法 | 类名::new | ArrayList::new |
4. Stream API核心概念
Stream:元素序列的抽象,支持串行或并行聚合操作
特点:
- 不存储数据
- 不修改源数据
- 延迟执行
- 可消费性:只能遍历一次
与集合区别:
- 集合:数据结构,存储元素
- Stream:计算机制,处理元素
5. Stream操作
中间操作(返回新Stream):
- filter:过滤元素
- map:转换元素
- flatMap:压平嵌套流
- distinct:去重
- sorted:排序
- peek:查看元素
- limit:限制数量
- skip:跳过元素
终端操作(执行计算):
- forEach:遍历元素
- collect:收集结果
- reduce:归约为单值
- count:计数
- min/max:最小/最大值
- anyMatch/allMatch/noneMatch:匹配判断
- findFirst/findAny:查找元素
懒加载特性:中间操作不立即执行,直到遇到终端操作才开始计算
6. 并行流与串行流
区别:
- 串行流:单线程处理
- 并行流:多线程处理(使用ForkJoinPool)
正确使用并行流:
- 数据量大
- 操作耗时长
- 无状态操作
- 避免共享可变状态
- 有限使用limit/findFirst等依赖顺序的操作
// 串行流
list.stream().forEach(System.out::printl