Java基础知识面试题
1.重载和重写的区别
重载(Overload):
同一个类中,方法名相同,参数列表不同(个数、类型、顺序)。
class Calculator {
// 方法1:两个int相加
public int add(int a, int b) {
return a + b;
}
// 方法2:三个int相加(参数个数不同 → 重载)
public int add(int a, int b, int c) {
return a + b + c;
}
// 方法3:两个double相加(参数类型不同 → 重载)
public double add(double a, double b) {
return a + b;
}
}
重写(Override):
子类继承父类后,方法名、参数列表、返回类型相同。不能比父类更严格,不能比父类被重写方法声明更宽泛的异常。
class Animal {
public void eat() {
System.out.println("动物吃东西");
}
}
class Dog extends Animal {
@Override // 重写父类方法
public void eat() {
System.out.println("狗吃骨头");
}
}
对比项 | 重载(Overload) | 重写(Override) |
---|---|---|
定义 | 同一个类中,方法名相同,参数列表不同(个数、类型、顺序)。 | 子类继承父类后,方法名、参数列表、返回类型相同(JDK5+允许返回子类类型)。 |
作用范围 | 同一个类 或父子类(继承关系不影响)。 | 子类与父类(必须有继承或接口实现关系)。 |
返回值 | 可以不同。 | 必须相同(或为父类方法返回值的子类型)。 |
访问权限 | 可以任意修改(public /protected /private )。 |
不能比父类更严格(如父类public ,子类不能private )。 |
异常抛出 | 可以抛出任意异常。 | 不能抛出比父类更宽泛的检查异常(如父类抛IOException ,子类不能抛Exception )。 |
目的 | 提供同名方法的不同实现方式(功能扩展)。 | 修改或增强父类方法的行为(多态性)。 |
2.String 和 StringBuffer、StringBuilder的区别是什么?
String:
内部使用 final char[],修改时会创建新的对象(如 str+=”a“ 会生成新的String)。
String s1 = "Hello";
s1 += " World"; // 创建新对象,原"Hello"成为垃圾
System.out.println(s1); // "Hello World"
StringBuilder:
内部使用壳扩容的 char[],修改时直接操作原对象。
无同步锁,线程不安全,但性能更高(推荐单线程使用)。
StringBuilder builder = new StringBuilder();
builder.append("Hello").append(" World"); // 单线程推荐
System.out.println(builder); // "Hello World"
StringBuffer:
内部使用壳扩容的 char[],修改时直接操作原对象。
所有方法使用 synchronized 修饰,保证多线程安全,但性能较低。
StringBuffer buffer = new StringBuffer();
buffer.append("Hello").append(" World"); // 原地修改
System.out.println(buffer); // "Hello World"
对比项 | String | StringBuffer | StringBuilder |
---|---|---|---|
可变性 | 不可变(final char[]) | 可变(动态扩容 char[]) | 可变(动态扩容 char[]) |
线程安全 | 天然线程安全(不可变) | 线程安全(方法加 synchronized ) |
非线程安全(无同步锁) |
性能 | 最低(频繁操作会生成新对象) | 中等(同步锁开销) | 最高(无锁,适合单线程) |
适用场景 | 字符串常量或少量拼接 | 多线程环境下的字符串操作 | 单线程环境下的字符串高效操作 |
内存开销 | 高(每次修改生成新对象) | 低(原地修改) | 低(原地修改) |
3.==与equals的区别
==:
比较对象的引用地址是否相同。适用于基本类型(int、double等)和引用类型(对象)。
基本数据类型(如:int,char):直接比较值是否相等。
int a = 10;
int b = 10;
System.out.println(a == b); // true(值相同)
引用数据类型(如String,Object):比较内存地址是否相同。
String s1 = new String("Java");
String s2 = new String("Java");
System.out.println(s1 == s2); // false(地址不同)
equals():
比较对象的内容是否逻辑相等。 仅适用于引用类型(对象)。
默认行为(Obejct类实现):与==相同,比较地址。
重写后的行为:(如String、Integer):比较对象内容。
String s1 = new String("Java");
String s2 = new String("Java");
System.out.println(s1.equals(s2)); // true(内容相同)
对比项 | == |
equals() |
---|---|---|
作用 | 比较对象的引用地址(内存地址是否相同)。 | 比较对象的内容是否逻辑相等(可重写)。 |
适用类型 | 基本类型(int , double 等)和引用类型(对象)。 |
仅适用于引用类型(对象)。 |
默认行为 | 基本类型:比较值;引用类型:比较地址。 | Object 类的默认实现等同于 == (比较地址),但可被重写(如 String 、Integer 等已重写)。 |
重写影响 | 无法重写。 | 可重写以自定义相等逻辑(如 String 比较字符序列)。 |
4.抽象类和接口的区别是什么?
抽象类:
抽象类是一种特殊的类,它不能被实例化(即不能直接创建对象),主要用于作为其他类的基类(父类)。
abstract class Animal {
abstract void eat(); // 抽象方法(子类必须实现)
void sleep() { // 具体方法(子类可直接继承)
System.out.println("睡觉");
}
}
abstract class Vehicle {
protected int speed; // 普通变量
public Vehicle(int speed) { // 构造方法
this.speed = speed;
}
}
接口:
接口(Interface)是一种完全抽象的类,它定义了一组方法(行为规范),但不提供具体实现。任何实现该接口的类都必须提供这些方法的具体体现。
(Java8+)
interface Swimmable {
void swim(); // 抽象方法
default void dive() { // 默认方法(子类可选重写)
System.out.println("下潜");
}
static void info() { // 静态方法(直接调用)
System.out.println("水生生物");
}
}
interface Flyable {
int MAX_HEIGHT = 10000; // 默认是 public static final
// 不能有构造方法!
}
对比项 | 抽象类(Abstract Class) | 接口(Interface) |
---|---|---|
定义关键字 | abstract class |
interface |
方法实现 | 可以包含具体方法和抽象方法(abstract )。 |
Java 8 前:只能有抽象方法; Java 8+:可包含 default 和 static 方法。 |
成员变量 | 可以是普通变量、常量、静态变量。 | 默认是 public static final (常量)。 |
构造方法 | 有构造方法(但不能实例化,用于子类初始化)。 | 无构造方法。 |
继承/实现 | 单继承(一个类只能继承一个抽象类)。 | 多实现(一个类可实现多个接口)。 |
设计目的 | 代码复用(提供部分通用实现)。 | 定义行为规范(多态、解耦)。 |
适用场景 | 多个相关类共享公共逻辑(如模板方法模式)。 | 定义跨类别的能力(如 Comparable 、Runnable )。 |
5.String类的常用方法
1.字符串长度&判空
方法 | 说明 |
---|---|
int length() |
返回字符串长度(字符数)。 |
boolean isEmpty() |
判断字符串是否为空(length() == 0 )。 |
boolean isBlank() (Java 11+) |
判断字符串是否为空或仅含空白字符(如 " " )。 |
String str = "Hello";
System.out.println(str.length()); // 5
System.out.println("".isEmpty()); // true
System.out.println(" ".isBlank()); // true
2.字符串比较
方法 | 说明 |
---|---|
boolean equals(Object obj) |
比较字符串内容是否相同(区分大小写)。 |
boolean equalsIgnoreCase(String str) |
忽略大小写比较字符串内容。 |
int compareTo(String str) |
按字典顺序比较字符串(返回 0 表示相等)。 |
int compareToIgnoreCase(String str) |
忽略大小写的字典顺序比较。 |
String s1 = "Java";
String s2 = "java";
System.out.println(s1.equals(s2)); // false
System.out.println(s1.equalsIgnoreCase(s2)); // true
System.out.println(s1.compareTo(s2)); // -32('J' - 'j')
3.字符串查找
boolean contains(CharSequence s) |
判断是否包含子串。 |
---|---|
int indexOf(String str) |
返回子串首次出现的索引(-1 表示未找到)。 |
int lastIndexOf(String str) |
返回子串最后一次出现的索引。 |
boolean startsWith(String prefix) |
判断是否以指定字符串开头。 |
boolean endsWith(String suffix) |
判断是否以指定字符串结尾。 |
String str = "Hello, Java!";
System.out.println(str.contains("Java")); // true
System.out.println(str.indexOf("Java")); // 7
System.out.println(str.startsWith("Hello")); // true
4.字符串截取&分割
方法 | 说明 |
---|---|
String substring(int beginIndex) |
从 beginIndex 开始截取子串。 |
String substring(int begin, int end) |
截取 [begin, end) 的子串。 |
String[] split(String regex) |
按正则表达式分割字符串。 |
String str = "2023-10-01";
String[] parts = str.split("-"); // ["2023", "10", "01"]
System.out.println(str.substring(5)); // "10-01"
System.out.println(str.substring(0, 4)); // "2023"
5.字符串拼接&格式化
方法 | 说明 |
---|---|
String concat(String str) |
字符串拼接(等同于 + )。 |
static String join(CharSequence delimiter, CharSequence... elements) |
用分隔符拼接多个字符串。 |
String format(String format, Object... args) |
格式化字符串(类似 printf )。 |
String s1 = "Hello";
String s2 = "Java";
System.out.println(s1.concat(s2)); // "HelloJava"
System.out.println(String.join(", ", "A", "B", "C")); // "A, B, C"
System.out.println(String.format("PI=%.2f", 3.14159)); // "PI=3.14"
6.其他常用方法
法 | 说明 |
---|---|
char charAt(int index) |
返回指定索引的字符。 |
char[] toCharArray() |
转为 char[] 数组。 |
static String valueOf(Object obj) |
将对象转为字符串(如 int → String )。 |
String str = "Java";
System.out.println(str.charAt(1)); // 'a'
char[] chars = str.toCharArray(); // ['J', 'a', 'v', 'a']
String numStr = String.valueOf(123); // "123"
6.Collection 和 Collections 有什么区别?
Collection(接口):
定义集合的基本行为,如增删改查:
主要子接口:
List(有序可重复,如 ArrayList、LinkedList)。
Set(无序唯一:如 HashSet、TreeSet)。
Queue(队列:LinkedList、PriorityQueue)。
public interface Collection<E> extends Iterable<E> {
boolean add(E e); // 添加元素
boolean remove(Object o); // 删除元素
int size(); // 返回元素个数
// 其他方法...
}
Collection<String> coll = new ArrayList<>();
coll.add("A");
coll.add("B");
System.out.println(coll.size()); // 2
Collections(工具类):
提供操作集合的静态方法,如排序、反转、线程安全化:
常用方法:
sort():排序(需元素实现Comparable)。
binarySearch():二分查找。
sychronizedList(): 讲集合转为线程安全版本。
List<Integer> list = Arrays.asList(3, 1, 2);
Collections.sort(list); // 排序 → [1, 2, 3]
Collections.reverse(list); // 反转 → [3, 2, 1]
Collections.shuffle(list); // 随机打乱
List<Integer> numbers = new ArrayList<>(Arrays.asList(5, 2, 8));
Collections.sort(numbers); // [2, 5, 8]
Collections.fill(numbers, 0); // [0, 0, 0]
对比项 | Collection (接口) |
Collections (工具类) |
---|---|---|
类型 | Java 集合框架的根接口(List 、Set 、Queue 的父接口)。 |
一个工具类(提供静态方法操作或返回集合)。 |
作用 | 定义集合的基本操作(如 add() 、remove() 、size() )。 |
提供集合的通用算法(排序、查找、同步化等)。 |
方法 | 实例方法(需通过集合对象调用,如 list.add() )。 |
静态方法(直接通过类调用,如 Collections.sort() )。 |
常见子接口/类 | List (有序)、Set (无序唯一)、Queue (队列)。 |
无子类,仅包含静态方法。 |
7.List、Set、Map 之间的区别是什么?
List(列表):
有序(按插入顺序或索引访问)。允许重复元素,可存储多个null。
ArrayList: 基于动态数组,随机访问快(0(1)),但插入/删除慢(0(n))。
LinkedList:基于双向联表,插入/删除快(0(1)),但随机访问慢(0(n))。
List<String> list = new ArrayList<>();
list.add("A"); // [A]
list.add("B"); // [A, B]
list.add("A"); // [A, B, A](允许重复)
Set(集合):
无序(HashSet)或按规则排序(TreeSet)。元素唯一(依赖equals()和hashCode())。
HashSet: 基于哈希表,查找0(1),无序。
TreeSet:基于红黑树,查找0(log n), 按自然顺序或 Comparator 排序。
Set<String> set = new HashSet<>();
set.add("A"); // [A]
set.add("B"); // [A, B](具体顺序可能不同)
set.add("A"); // [A, B](去重)
Map(键值对):
key唯一,Value可重复。 HashMap无序,TreeMap 按Key排序。
HashMap:基于哈希表,查找 0(1)。
TreeMap:基于红黑树,查找0(log n), 按Key 排序。
Map<String, Integer> map = new HashMap<>();
map.put("Apple", 1); // {Apple=1}
map.put("Banana", 2); // {Apple=1, Banana=2}
map.put("Apple", 3); // {Apple=3, Banana=2}(Key 重复会覆盖)
对比项 | List(列表) | Set(集合) | Map(映射) |
---|---|---|---|
接口/实现 | List (接口) (ArrayList , LinkedList ) |
Set (接口) (HashSet , TreeSet ) |
Map (接口) (HashMap , TreeMap ) |
存储结构 | 有序、可重复(按插入顺序或索引访问)。 | 无序、唯一(不允许重复元素)。 | 键值对(Key-Value),Key 唯一,Value 可重复。 |
允许 null | 允许多个 null 值。 |
最多允许一个 null 值(TreeSet 不允许)。 |
HashMap :允许一个 null Key 和多个 null Value。 TreeMap :Key 不能为 null 。 |
查找性能 | ArrayList :O(1) (索引访问) LinkedList :O(n) (遍历)。 |
HashSet :O(1) (平均) TreeSet :O(log n) 。 |
HashMap :O(1) (平均) TreeMap :O(log n) 。 |
线程安全 | 默认不安全,可用 Collections.synchronizedList 或 CopyOnWriteArrayList 。 |
默认不安全,可用 Collections.synchronizedSet 或 CopyOnWriteArraySet 。 |
默认不安全,可用 Collections.synchronizedMap 或 ConcurrentHashMap 。 |
适用场景 | 需要保留顺序或允许重复(如购物车商品列表)。 | 需要去重或快速查找(如用户 ID 集合)。 | 需要键值映射(如缓存、字典)。 |
8.HashMap 和 Hashtable 有什么区别
HashMap:
非线程安全,多线程操作可能导致数据不一致。
替代方案:使用 ConcurrentHashMap(分段锁,性能较高)。
HashMap<String, Integer> hashMap = new HashMap<>();
hashMap.put("A", 1);
hashMap.put(null, 2); // 允许 null Key
hashMap.put("B", null); // 允许 null Value
System.out.println(hashMap); // {null=2, A=1, B=null}
Hashtable:
线程安全,但所有方法加 synchronized,并发性能差。
Hashtable<String, Integer> hashtable = new Hashtable<>();
hashtable.put("A", 1);
// hashtable.put(null, 2); // 运行时报 NullPointerException
// hashtable.put("B", null); // 运行时报 NullPointerException
System.out.println(hashtable); // {A=1}
对比项 | HashMap (JDK 1.2+) | Hashtable (JDK 1.0) |
---|---|---|
线程安全 | 非线程安全(需手动同步,如 ConcurrentHashMap )。 |
线程安全(方法用 synchronized 修饰,性能较低)。 |
允许 null | 允许 一个 null Key 和 多个 null Value。 |
不允许 null Key 或 Value(会抛 NullPointerException )。 |
继承体系 | 继承 AbstractMap ,实现 Map 接口。 |
继承 Dictionary (已过时),实现 Map 接口。 |
性能 | 更高(无同步锁,适合单线程)。 | 较低(同步锁开销,多线程竞争时性能差)。 |
迭代器 | 使用 Iterator (快速失败,fail-fast )。 |
使用 Enumeration (较老的迭代方式)。 |
扩容机制 | 默认容量 16,扩容为 2 倍。 | 默认容量 11,扩容为 2 倍 + 1。 |
推荐使用场景 | 单线程 或 高并发(用 ConcurrentHashMap )。 |
遗留代码(现代开发基本被 ConcurrentHashMap 替代)。 |
9.说一下 HashMap的实现原理
HashMap 是 Java 中最常用的哈希表实现,基于 数组+联表+红黑树 的混合结构,核心目标是实现高效的0(1)时间复杂度(平均情况下)的键值对存取。
1.核心数据结构
数组(桶数组): 默认初始容量为16,存储链表的头节点或红黑树的根节点。数组的每个位置称为一个桶(Bucket),通过哈希函数确定键值对的存储位置。
链表(解决哈希冲突):当多个键的哈希值映射到同一桶时,以链表形式存储(拉链法)。
红黑树(优化长链表):当链表长度超过 8 且桶数组长度 >= 64 时,链表转为红黑树,将查询时间从0(n) 优化为 0(logn)。
2.关键机制
哈希计算: 计算键的 hashCode()(int 类型)。 通过扰动函数 ((h = key.hashCode())^(h>>>16)) 混合高低位,减少哈希冲突。 取模运算 (n-1) & hash 确定桶下标(n 为数组长度,要求是 2的幂)。
// 计算键 "A" 的桶下标
String key = "A";
int hash = key.hashCode() ^ (key.hashCode() >>> 16); // 扰动
int index = (16 - 1) & hash; // 等价于 hash % 16
插入流程:
1.计算键的哈希值和桶下标。
2.若桶为空,直接存入新节点。
3.若桶不为空:
链表:遍历链表,若找到相同键则更新值,否则添加到链表尾部。
红黑树:调用树的插入方法。
4.若链表长度 ≥ 8 且数组长度 ≥ 64,链表转红黑树。
扩容(Rehash):
触发条件:元素数量超过 容量x负载因子(默认负载因子 0.75)。
扩容条件:数组大小扩大为 2倍,重新计算所有键值对位置。
10.ArrayList 与 LinkedList 的区别
ArrayList:
基于动态数组,内存连续,支持快速随机访问。
扩容时需拷贝原数组到新数组(Arrays.copyOf) 。
LinkedList:
基于双向链表,内存不连续,每个节点存储前驱(prev)和后继(next)指针。
无需扩容,插入/删除只需修改相邻节点的指针。
// ArrayList 示例
List<String> arrayList = new ArrayList<>();
arrayList.add("A"); // O(1)
arrayList.get(0); // O(1)
arrayList.add(0, "B"); // O(n)
// LinkedList 示例
List<String> linkedList = new LinkedList<>();
linkedList.add("A"); // O(1)
linkedList.get(0); // O(n)
linkedList.addFirst("B"); // O(1)(直接操作头节点)
对比项 | ArrayList | LinkedList |
---|---|---|
底层数据结构 | 动态数组(可自动扩容) | 双向链表(节点存储前后指针) |
内存占用 | 更小(仅存储数据,无额外指针) | 更大(每个元素需存储前后节点的指针) |
随机访问性能 | O(1)(通过索引直接定位) | O(n)(需从头遍历链表) |
插入/删除性能 | O(n)(需移动后续元素) | O(1)(仅修改指针,但需先定位到操作位置) |
适用场景 | 高频随机访问(如按索引查询) | 高频插入/删除(如队列、栈) |
扩容机制 | 初始容量为10,默认扩容 1.5 倍(int newCapacity = oldCapacity + (oldCapacity >> 1) ) |
无需扩容(动态增减节点) |
线程安全 | 非线程安全(可用 Collections.synchronizedList ) |
非线程安全(同上) |
实现接口 | List |
List + Deque (可作队列或栈使用) |
11.Java集合容器有哪些?
一、Collection接口
1**.List(有序、可重复)**
实现类 | 特点 |
---|---|
ArrayList | 基于动态数组,查询快(O(1) ),增删慢(需移动元素,O(n) )。 |
LinkedList | 基于双向链表,增删快(O(1) ),查询慢(需遍历,O(n) )。支持队列操作。 |
2.Set(无序、唯一)
实现类 | 特点 |
---|---|
HashSet | 基于 HashMap 实现,无序,查询/插入 O(1) ,允许一个 null 值。 |
LinkedHashSet | 继承 HashSet ,内部用链表维护插入顺序。 |
TreeSet | 基于红黑树实现,元素按自然顺序或 Comparator 排序,操作 O(log n) 。 |
3.Queue(队列)
实现类 | 特点 |
---|---|
LinkedList | 可作双向队列(Deque )。 |
PriorityQueue | 基于堆实现的优先级队列,元素按优先级排序。 |
ArrayDeque | 基于数组的双端队列,高性能(比 LinkedList 更节省内存)。 |
二、Map接口
实现类 | 特点 |
---|---|
HashMap | 基于哈希表(数组+链表+红黑树),线程不安全,允许 null 键/值。 |
LinkedHashMap | 继承 HashMap ,用链表维护插入顺序或访问顺序(适合实现 LRU 缓存)。 |
TreeMap | 基于红黑树,键按自然顺序或 Comparator 排序,操作 O(log n) 。 |
ConcurrentHashMap | 线程安全的 HashMap (分段锁/CAS),高并发场景首选。 |
List<String> list = new ArrayList<>(); // 常用列表
Set<Integer> set = new HashSet<>(); // 快速去重
Map<String, Integer> map = new HashMap<>(); // 键值存储
Queue<String> queue = new ArrayDeque<>(); // 高效队列
12.哪些集合是线程安全的?
Java提供了多种线程安全的集合类,主要分为两类:
1.早期线程安全集合(性能较低,如Vector、Hashtable)
集合类 | 特点 | 问题 |
---|---|---|
Vector | 线程安全的 ArrayList (方法用 synchronized 修饰)。 |
全表锁,并发性能差。 |
Hashtable | 线程安全的 HashMap (方法用 synchronized 修饰)。 |
全表锁,并发性能差。 |
Stack | 线程安全的栈(继承自 Vector )。 |
已废弃,推荐用 Deque 替代。 |
注意:这些类因性能问题,现代开发中已被 JUC(
java.util.concurrent
)包中的集合取代。
2.现代高并发集合(JUC包中的高效实现,如 ConcurrentHashMap、CopyOnWriteArrayList)
集合类 | 特点 | 适用场景 |
---|---|---|
ConcurrentHashMap | 分段锁(JDK 7)或 CAS + 红黑树(JDK 8+),高并发下性能优异。 | 高并发键值存储(如缓存)。 |
CopyOnWriteArrayList | 写时复制(写操作加锁并复制新数组),读操作无锁。 | 读多写少(如监听器列表)。 |
CopyOnWriteArraySet | 基于 CopyOnWriteArrayList 实现的线程安全 Set 。 |
读多写少的去重场景。 |
ConcurrentLinkedQueue | 基于链表的无界线程安全队列(CAS 实现)。 | 高并发队列(如任务队列)。 |
ConcurrentSkipListMap | 基于跳表实现的线程安全 TreeMap (支持排序)。 |
高并发有序键值存储。 |
ConcurrentSkipListSet | 基于 ConcurrentSkipListMap 实现的线程安全 TreeSet 。 |
高并发有序去重集合。 |
BlockingQueue 实现类 | 阻塞队列(如 ArrayBlockingQueue 、LinkedBlockingQueue )。 |
生产者-消费者模型。 |
13.创建线程有哪几种方式
1.继承Thread类
直接继承Thread类并重写 run() 方法。
简单但灵活性差(Java不支持多继承)。
class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程运行中(继承Thread)");
}
}
// 启动线程
MyThread thread = new MyThread();
thread.start(); // 调用start()启动新线程(非run())
2.实现 Runnable 接口
实现 Runnable 接口,重写 run() 方法。
更灵活(可继承其他类或是实现其他接口)。
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程运行中(实现Runnable)");
}
}
// 启动线程
Thread thread = new Thread(new MyRunnable());
thread.start();
3.实现 Callable 接口 + FutureTask
可返回结果(Callable 的 call() 有返回值)。
支持异常抛出(Runnable 的 run() 不能抛异常)。
import java.util.concurrent.*;
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Callable返回结果";
}
}
// 启动线程
FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
Thread thread = new Thread(futureTask);
thread.start();
// 获取结果(会阻塞直到线程完成)
String result = futureTask.get();
System.out.println(result);
4.使用线程池
避免频繁创建/销毁线程的开销,提高系统资源利用率。
通过 ExecutorService 管理线程生命周期。
import java.util.concurrent.*;
public class ThreadPoolDemo {
public static void main(String[] args) {
// 创建线程池(固定大小)
ExecutorService executor = Executors.newFixedThreadPool(3);
// 提交任务(支持Runnable或Callable)
executor.execute(() -> System.out.println("线程池执行Runnable任务"));
Future<String> future = executor.submit(() -> "线程池执行Callable任务");
// 获取Callable结果
try {
System.out.println(future.get());
} catch (Exception e) {
e.printStackTrace();
}
// 关闭线程池
executor.shutdown();
}
}
方式 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
继承 Thread |
简单直接 | 无法继承其他类 | 简单任务 |
实现 Runnable |
灵活性高(可继承其他类) | 无返回值 | 多线程资源共享 |
实现 Callable |
可返回结果、支持异常 | 需配合 FutureTask 使用 |
需要返回结果的异步任务 |
线程池 | 资源复用、管理便捷 | 需配置参数(如核心线程数) | 高并发、长期运行任务 |
14.线程有哪些状态
Java线程的生命周期分为 6 种状态,可通过 Thread.getState() 查看。
1.NEW(新建)
线程被创建但尚未启动(未调用 start())。
触发条件:新建线程。
2.RUNNABLE(可运行)
线程正在JVM种执行或等待系统资源(如CPU)。
触发条件:调用start()后进入 RUNNABLE。 被阻塞的线程(如I/O)恢复后也回到RUNNABLE。
3.BLOCKED(阻塞)
线程等待获取监视器锁(sychronized)时被阻塞。
触发条件:竞争 sychronized 锁失败(锁被其他线程持有)。
4.WAITING(无限等待)
线程主动进入等待,除非被唤醒,否则一直等待。
触发条件:调用Object.wait()、Thread.join() 或 LockSupport 。park()。
5.TIMED_WAITING(超时等待)
线程在指定时间内等待,超时后自行唤醒。
触发条件:Thread.sleep(long)、Object.wait(long)、Thread.join(long)等带超时参数的方法。
6.TERMINATED(终止)
线程执行完毕(run() 方法结束)或异常退出。
触发条件:thread.start(); // 执行完 run() 后状态为 TERMINATED 。
15.sleep() 和 wait() 有什么区别?
1.锁的行为不同
sleep():睡眠期间不释放锁,其他线程无法进入同步块。
wait():释放锁,其他线程可获取锁并执行。
2.调用前提不同
wait() 必须在 sychronized 代码块中调用,否则抛 IllegalMonitorStateException。
sleep() 可以在任何地方调用。
3.设计目的不同
sleep():用于延迟执行(如定时任务)。
wait():用于线程间协作(如生产者-消费者模型)。
sleep() 实例
public class SleepDemo {
public static void main(String[] args) {
new Thread(() -> {
synchronized (SleepDemo.class) {
System.out.println("线程A: 持有锁并睡眠2秒");
try {
Thread.sleep(2000); // 不释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程A: 唤醒后继续执行");
}
}).start();
new Thread(() -> {
synchronized (SleepDemo.class) {
System.out.println("线程B: 获取到锁"); // 必须等待线程A释放锁
}
}).start();
}
}
执行结果:
线程A: 持有锁并睡眠2秒
(等待2秒)
线程A: 唤醒后继续执行
线程B: 获取到锁
wait()示例:
public class WaitDemo {
public static void main(String[] args) {
Object lock = new Object();
new Thread(() -> {
synchronized (lock) {
System.out.println("线程A: 持有锁并等待");
try {
lock.wait(); // 释放锁,线程B可获取锁
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程A: 被唤醒后重新获得锁");
}
}).start();
new Thread(() -> {
synchronized (lock) {
System.out.println("线程B: 获取到锁并唤醒线程A");
lock.notify(); // 唤醒线程A(但线程B仍持有锁,直到同步块结束)
try {
Thread.sleep(1000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程B: 释放锁");
}
}).start();
}
}
执行结果:
线程A: 持有锁并等待
线程B: 获取到锁并唤醒线程A
线程B: 释放锁
线程A: 被唤醒后重新获得锁
对比项 | Thread.sleep() |
Object.wait() |
---|---|---|
所属类 | Thread 类的静态方法 |
Object 类的实例方法 |
锁的释放 | 不释放锁(即使占用 synchronized 锁) |
释放锁(调用前必须持有 synchronized 锁) |
使用场景 | 单纯让线程暂停指定时间 | 线程间通信(需配合 notify() /notifyAll() ) |
唤醒条件 | 时间到期后自动唤醒 | 必须由其他线程显式唤醒(或超时,若调用 wait(long timeout) ) |
异常处理 | 需捕获 InterruptedException |
需捕获 InterruptedException |
状态变化 | 进入 TIMED_WAITING 状态 |
进入 WAITING 或 TIMED_WAITING 状态 |
16.notify() 和 notifyAll() 有什么区别?
1.唤醒机制
notify():从等待池(WAITING 状态的线程)中随机选择一个线程唤醒,其他线程仍保持等待。
notifyAll():唤醒等待池中所有线程,这些线程需要重新竞争锁。
2.锁竞争流程
notify():被选中的线程从 WAITING 变为 BLOCKED,尝试获取锁。其他未被唤醒的线程仍在 WAITING 状态。
notifyAll():所有等待线程从 WAITING 变为 BLOCKED,一起竞争锁。最终只有一个线程能获取锁,其余线程继续阻塞。
对比项 | notify() |
notifyAll() |
---|---|---|
唤醒范围 | 随机唤醒 一个 正在等待(wait() )的线程。 |
唤醒 所有 正在等待(wait() )的线程。 |
锁竞争 | 被唤醒的线程直接竞争锁,其他线程继续等待。 | 所有被唤醒的线程一起竞争锁。 |
使用场景 | 明确知道只需唤醒一个线程(如单生产者-单消费者)。 | 需要唤醒所有线程(如资源释放后所有消费者竞争)。 |
性能影响 | 更高效(减少不必要的线程竞争)。 | 可能引起“惊群效应”(大量线程竞争资源)。 |
公平性 | 不保证公平性(依赖 JVM 实现)。 | 不保证公平性,但所有线程有机会竞争。 |
17.说一说几种常见的线程池及适用场景?
Java 通过 Executors 工具类提供了几种标准线程池实现,均基于 ThreadPoolExecutor 封装。
FixedThreadPool(固定大小线程池)
特点:
核心线程数 = 最大线程数 (corePoolSize = maximumPoolSize),线程数固定。
使用无界队列(LinkedBlockingQueue),任务堆积可能导致OOM。
适用场景:
适用于负载稳定的场景(如已知并发量可控的Web服务)。
缺点:
无界队列可能导致内存溢出(如提交速度远高于提交速度)。
创建方式:
ExecutorService executor = Executors.newFixedThreadPool(5); // 固定5个线程
CachedThreadPool(可缓存线程池)
特点:
核心线程数为0,最大线程数为 Integer.MAX_VALUE(理论上无限创建线程)。
使用 SynchronousQueue(不存储任务,直接移交线程执行)。
空闲线程超时60秒后回收。
适用场景:
适用于短时异步任务且任务量波动大的场景(如突发流量请求)。
缺点:
线程数无上限,可能耗尽系统资源(如CPU或内存)。
创建方式:
ExecutorService executor = Executors.newCachedThreadPool();
3.SingleThreadExecutor(单线程线程池)
特点:
核心线程数 = 最大线程数 = 1 (保证任务按提交顺序串执行)。
使用无界队列(LinkedBlockingQueue),任务堆积可能导致OOM。
适用场景:
需要任务顺序执行的场景 (如日志顺序写入、单线程任务调度)。
缺点:
无界队列风险同 FixedThreadPool。
创建方式:
ExecutorService executor = Executors.newSingleThreadExecutor();
4.ScheduledThreadPool(定时任务线程池)
特点:
支持延迟执行和周期性执行任务。
使用 DelayedWorkQueue(按任务触发时间排序)。
适用场景:
定时任务(如每隔10秒统计一次数据)。
延迟任务(如5分钟后执行回调)。
创建方式:
ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
18.创建线程池的核心参数有哪些?
1. 核心参数列表
参数名 | 作用 | 推荐设置规则 |
---|---|---|
corePoolSize |
核心线程数(长期存活的线程)。 | CPU密集型:CPU核心数 + 1 IO密集型:CPU核心数 × 2 |
maximumPoolSize |
最大线程数(临时线程 = maximumPoolSize - corePoolSize )。 |
建议 corePoolSize × 2 ~3 (根据任务阻塞时间调整) |
keepAliveTime |
临时线程空闲存活时间(超时后销毁)。 | 默认 60秒 (IO密集型可缩短,CPU密集型可延长) |
unit |
keepAliveTime 的时间单位(秒、毫秒等)。 |
通常用 TimeUnit.SECONDS |
workQueue |
任务队列(存储未执行的Runnable任务)。 | 根据场景选择: - ArrayBlockingQueue (有界) - LinkedBlockingQueue (无界) |
threadFactory |
线程工厂(自定义线程命名、优先级等)。 | 建议自定义命名(如MyThreadFactory ),便于排查问题 |
handler |
拒绝策略(当队列和线程池全满时的处理方式)。 | 默认AbortPolicy (抛异常),生产建议用CallerRunsPolicy 或自定义日志记录 |
2.参数详解
(1)核心线程 vs 最大线程
核心线程:长期存活,即使空闲也不销毁(除非allowCoreThreadTimeOut=true
)。
临时线程:当任务数 > corePoolSize
且队列满时创建,空闲超时后销毁。
(2)任务队列选择
队列类型 | 特点 | 适用场景 |
---|---|---|
ArrayBlockingQueue |
有界队列,固定大小。 | 需要控制任务积压,避免OOM。 |
LinkedBlockingQueue |
无界队列(默认Integer.MAX_VALUE )。 |
任务量不可预测,但可能引发OOM。 |
SynchronousQueue |
不存储任务,直接移交线程。 | 高并发且快速响应(如CachedThreadPool )。 |
PriorityBlockingQueue |
优先级队列。 | 任务需要按优先级执行。 |
(3)拒绝策略(RejectedExecutionHandler)**
策略 | 行为 |
---|---|
AbortPolicy (默认) |
直接抛出 RejectedExecutionException 。 |
CallerRunsPolicy |
由提交任务的线程自己执行任务(降低提交速度)。 |
DiscardPolicy |
静默丢弃被拒绝的任务(不通知)。 |
DiscardOldestPolicy |
丢弃队列中最旧的任务,然后重试提交。 |
19.线程池中submit() 和 execute() 方法有什么区别?
在 Java 线程池中,submit() 和 execute() 是提交任务的两种核心方法,二者在返回值、异常处理和使用场景上有显著差异。
使用示例:
execute() 方法
特点:异常需通过 Thread.setUncaughtExceptionHandler 捕获,否则会丢失。无法获取任务执行结果。
ExecutorService executor = Executors.newFixedThreadPool(2);
executor.execute(() -> {
System.out.println("任务执行中");
// 异常会直接抛出到线程未捕获异常处理器
if (true) throw new RuntimeException("execute()异常");
});
submit() 方法
特点:通过Future.get() 获得返回值或异常。
支持Callable(有返回值)和 Runnable(返回null)。
Future<String> future = executor.submit(() -> {
System.out.println("Callable任务执行中");
return "success";
});
// 获取结果(会阻塞)
try {
String result = future.get(); // 捕获ExecutionException(封装任务异常)
System.out.println(result);
} catch (ExecutionException e) {
System.out.println("捕获到任务异常: " + e.getCause());
}
对比项 | execute() |
submit() |
---|---|---|
返回值 | 无返回值(void ) |
返回 Future 对象,可获取任务结果或异常。 |
异常处理 | 异常直接抛出(需自行捕获) | 异常封装在 Future 中(需调用 get() 时捕获)。 |
适用任务类型 | 仅支持 Runnable |
支持 Runnable 和 Callable 。 |
方法来源 | 定义在 Executor 接口。 |
定义在 ExecutorService 子接口。 |
20.在Java程序中怎么保证多线程的运行安全?
多线程安全问题的本质是共享资源的并发访问冲突。Java提供了多种机制来保证线程安全。
1.不可变对象(Immutable Objects)
原理:对象创建后状态不可修改,天然线程安全。
适用场景:配置信息,常量数据。
// final 修饰类和字段,不提供setter方法
public final class ImmutableConfig {
private final String serverUrl;
public ImmutableConfig(String url) { this.serverUrl = url; }
public String getServerUrl() { return serverUrl; }
}
2.线程封闭(Thread Confinement)
原理:将对象限制在单个线程内访问,避免共享。
实现方式:
局部变量(栈封闭):每个线程都有自己的栈帧。
ThreadLocal:为每个线程保留独立副本。
private static ThreadLocal<SimpleDateFormat> dateFormat =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
3.同步锁(Synchronization)
(1) synchronized 关键字
// 同步方法(锁当前对象)
public synchronized void increment() { counter++; }
// 同步代码块(锁指定对象)
public void update() {
synchronized (lockObject) {
// 临界区代码
}
}
(2) ReentrantLock(显示锁)
private final ReentrantLock lock = new ReentrantLock();
public void doTask() {
lock.lock();
try {
// 临界区代码
} finally {
lock.unlock(); // 必须手动释放
}
}
4.并发容器(Thread-Safe Collections)
原理:基于 CAS 或分段锁实现的高效线程安全容器。
常用类:
ConcurrentHashMap:替代 HashMap
CopyOnWriteArrayList:替代 ArrayList(读多写少场景)
BlockingQueue:生产者-消费者模型(如 ArrayBlockingQueue)
Map<String, String> concurrentMap = new ConcurrentHashMap<>();
List<String> copyOnWriteList = new CopyOnWriteArrayList<>();
5.原子类(Atomic Classes)
原理:通过CAS(Compare-And-Swap)实现无锁线程安全操作。
适用场景:计数器、状态标志。
private AtomicInteger counter = new AtomicInteger(0);
public void safeIncrement() {
counter.incrementAndGet(); // 原子操作
}
6.线程池(ThreadPool)
原理:统一管理线程生命周期,避免频繁创建/销毁线程。
ExecutorService executor = new ThreadPoolExecutor(
4, // corePoolSize
8, // maximumPoolSize
30, TimeUnit.SECONDS, // keepAliveTime
new ArrayBlockingQueue<>(100), // workQueue
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
21.什么是死锁?
1.死锁的定义
死锁是指两个或多个线程在执行过程中,因争夺资源而陷入互相等待的状态,导致所有线程都无法继续执行。此时系统处于僵持状态,除非外部干预,否则程序无法恢复。
2.死锁的四个必要条件
必须同时满足以下条件才会发生死锁:
1.互斥条件:资源一次只能被一个线程占用(如锁)。
2.占有并等待:线程持有至少一个资源,同时等待获取其他被占用的资源。
3.不可抢占:已分配给线程的资源不能被其他线程强行夺取,必须由线程自行释放。
4.循环等待:存在一个线程的循环等待链,每个线程都在等待下一个线程锁占用的资源。
22.怎么防止死锁
1.固定锁顺序(破坏循环等待)
原理:所有线程按全局统一的顺序获取锁。
适用场景:多锁嵌套场景。
优点:简单有效,适用于已知所有锁的场景。
// 定义全局锁顺序规则:lock1 → lock2
public void transfer(Account from, Account to, int amount) {
Object firstLock = from.getId() < to.getId() ? from : to;
Object secondLock = from.getId() < to.getId() ? to : from;
synchronized (firstLock) {
synchronized (secondLock) {
from.withdraw(amount);
to.deposit(amount);
}
}
}
2.超时机制(破坏不可抢占)
原理:通过 tryLock设置超时,放弃等待并释放已有锁。
适用场景:高并发竞争环境。
优点:避免无限等待,系统可自恢复。
private final Lock lock1 = new ReentrantLock();
private final Lock lock2 = new ReentrantLock();
public void doTask() throws InterruptedException {
while (true) {
if (lock1.tryLock(1, TimeUnit.SECONDS)) {
try {
if (lock2.tryLock(1, TimeUnit.SECONDS)) {
try {
// 成功获取两把锁
break;
} finally {
lock2.unlock();
}
}
} finally {
lock1.unlock();
}
}
// 随机休眠避免活锁
Thread.sleep((long) (Math.random() * 100));
}
}
3.一次性申请所有资源(破坏占有并等待)
原理:通过更高层次的锁(如门面锁)保证原子性获取所有资源。
适用场景:需要多个资源的操作。
缺点:可能降低并发度。
public class ResourceManager {
private final Object globalLock = new Object();
public void useResources(A a, B b) {
synchronized (globalLock) {
// 安全使用资源a和b
}
}
}
4.使用无锁数据结构(破坏互斥条件)
原理:通过CAS(Compare-And-Swap)实现无锁编程。
使用场景:计算器、状态标志等简单操作。
优点:高性能,无死锁风险。
private AtomicInteger counter = new AtomicInteger(0);
public void safeIncrement() {
counter.incrementAndGet(); // 原子操作,无需锁
}
5.减少锁粒度(降低死锁概率)
原理:缩小同步代码块范围,缩短锁持有时间。
public void process() {
readFile(); // 无锁操作
synchronized (this) {
updateDB(); // 最小化锁范围
}
}
6.设计模式规避
银行家算法:预分配资源前检查系统安全性(适合资源调度系统)。。
哲学家就餐问题解决方案: 限制最多 n-1 人同时拿筷子。仅当左右筷子都可用时才拿起。
// 使用Semaphore限制并发拿筷子的哲学家数量
Semaphore table = new Semaphore(4); // 5个哲学家最多4人同时拿
public void takeForks() throws InterruptedException {
table.acquire();
synchronized (leftFork) {
synchronized (rightFork) {
eat();
}
}
table.release();
}
23.synchronized 和 lock 的区别
synchronized:
特点:
自动加锁和释放锁(进入代码块加锁,退出时释放锁)。
不可中断(线程必须等待锁释放)。
public class SynchronizedExample {
private int count = 0;
public synchronized void increment() { // 方法同步
count++;
}
public void doSomething() {
synchronized (this) { // 代码块同步
count--;
}
}
}
Lock:
特点:
必须手动释放锁(否则可能导致死锁)。
支持尝试获取锁(tryLock())
可中断(lockInterruptibly)
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
private final Lock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock(); // 手动加锁
try {
count++;
} finally {
lock.unlock(); // 必须手动释放锁
}
}
public void tryLockExample() {
if (lock.tryLock()) { // 尝试获取锁(非阻塞)
try {
count--;
} finally {
lock.unlock();
}
} else {
System.out.println("获取锁失败");
}
}
}
对比项 | synchronized (Java 内置锁) | Lock (如 ReentrantLock ) |
---|---|---|
锁的获取方式 | 自动获取和释放(进入同步代码块获取,退出时释放) | 手动获取和释放(必须显式调用 lock() 和 unlock() ) |
锁的可中断性 | 不可中断(线程阻塞后只能等待) | 可中断(lockInterruptibly() 可响应中断) |
公平锁支持 | 非公平锁(默认) | 可配置公平锁(new ReentrantLock(true) ) |
条件变量 | 仅 wait() /notify() (依赖 Object 监视器) |
支持多个条件变量(Condition 接口,更灵活) |
性能 | JVM 优化,适用于低竞争场景 | 高竞争场景下性能更好(如 ReentrantLock 的 CAS 机制) |
锁的尝试获取 | 不支持(必须阻塞等待) | 支持(tryLock() 可尝试获取锁,避免死锁) |
适用场景 | 简单的同步控制(如单方法同步) | 复杂同步需求(如超时、可中断、公平锁等) |
24.java的八大基本数据类型,分别占多少字节?
数据类型 | 关键字 | 字节数 | 取值范围 | 默认值 | 包装类 |
---|---|---|---|---|---|
字节型 | byte |
1字节 | -128 ~ 127 | 0 | Byte |
短整型 | short |
2字节 | -32,768 ~ 32,767 | 0 | Short |
整型 | int |
4字节 | -2³¹ ~ 2³¹-1 (-2,147,483,648 ~ 2,147,483,647) | 0 | Integer |
长整型 | long |
8字节 | -2⁶³ ~ 2⁶³-1 | 0L | Long |
单精度浮点型 | float |
4字节 | ±3.4E+38 (约6-7位有效数字) | 0.0f | Float |
双精度浮点型 | double |
8字节 | ±1.7E+308 (约15位有效数字) | 0.0d | Double |
字符型 | char |
2字节 | ‘\u0000’ ~ ‘\uffff’ (0 ~ 65,535) | ‘\u0000’ | Character |
布尔型 | boolean |
未明确定义(通常按1字节处理) | true / false |
false | Boolean |
关键细节:
1.boolean的特殊性
JVM规范未严格定义 boolean 的大小,不同JVM实现可能不同:
编译时:通常用int(4字节)或 byte(1字节)表示
数组时:通常每个元素占1字节。
实际开发中按1字节估算即可。
2.浮点数经度问题
float f = 0.1f + 0.2f; // 结果可能是 0.30000004
double d = 0.1 + 0.2; // 结果可能是 0.30000000000000004
3.类型转换规则
小类型 -> 大类型:自动转换(如 byte -> int )
大类型 -> 小类型: 需强制转换(可能丢失精度)
int i = 100;
byte b = (byte)i; // 必须显式强制转换
25.JDK8有哪些新特性?
1.语言核心增强
1.Lambda表达式
作用:简化匿名内部类,实现函数式编程。
// 旧写法
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello");
}
}).start();
// Lambda 写法
new Thread(() -> System.out.println("Hello")).start();
2.函数式接口(Functional Interface)
定义:只有一个抽象方法的接口
注解:@FunctionalInterface
内置四大核心接口:
接口 | 方法 | 用途 |
---|---|---|
Consumer<T> |
void accept(T) |
消费型(接收参数处理) |
Supplier<T> |
T get() |
供给型(生成数据) |
Function<T,R> |
R apply(T) |
函数型(转换数据) |
Predicate<T> |
boolean test(T) |
断言型(条件判断) |
3.方法引用(Method Reference)
// 1. 静态方法引用
Function<String, Integer> parser = Integer::parseInt;
// 2. 实例方法引用
Consumer<String> printer = System.out::println;
// 3. 对象方法引用
Function<String, Integer> length = String::length;
// 4. 构造器引用
Supplier<List<String>> listSupplier = ArrayList::new;
2.集合框架增强
1.Stream API
List<String> names = Arrays.asList("Tom", "Jerry", "Bob");
// 中间操作
names.stream()
.filter(name -> name.length() > 3)
.map(String::toUpperCase)
.sorted()
// 终止操作
.forEach(System.out::println);
2.默认方法(Default Methods)
接口中可定义实现方法:
interface Vehicle {
default void print() {
System.out.println("我是一辆车");
}
}
3.新日期时间API(java.time)
类 | 用途 |
---|---|
LocalDate |
日期(年月日) |
LocalTime |
时间(时分秒) |
LocalDateTime |
日期+时间 |
Instant |
时间戳(机器时间) |
Duration /Period |
时间间隔 |
LocalDate today = LocalDate.now();
LocalDate birthday = LocalDate.of(1990, Month.JANUARY, 1);
long days = ChronoUnit.DAYS.between(birthday, today);
3.JVM与工具增强
原空间(Metaspace)
取代永久代(PermGen):
使用本地内存,避免 OutOfMemoryError:PerGen space
默认无大小限制 (可通过 -XX: MetaspaceSize 调整)
4.其他重要特性
1.Optional 类
解决 NPE 问题:
Optional<String> opt = Optional.ofNullable(getName());
String name = opt.orElse("default");
2.重复注解
同意注解多次使用:
@Target(ElementType.TYPE)
@Repeatable(Filters.class)
public @interface Filter {
String value();
}
@Filter("filter1")
@Filter("filter2")
public class MyClass {}
3**.类型注解**
注解可用在任何类型前:
List<@NonNull String> list = new ArrayList<>();
26.Stream流中的常用方法
Stream API 是 JDK8 引入的函数式数据处理工具,提供了丰富的链式操作方法,主要分为中间操作和终端操作两大类。
一、中间操作(Intermediate Operations)
中间操作放回新的 Stream,支持链式调用(惰性求值,遇到终止操作才执行)。
1.过滤与去重
方法 | 说明 | 示例 |
---|---|---|
filter(Predicate) |
过滤元素 | stream.filter(s -> s.length() > 3) |
distinct() |
去重(依赖equals() ) |
stream.distinct() |
limit(long) |
限制元素数量 | stream.limit(5) |
skip(long) |
跳过前N个元素 | stream.skip(2) |
2.映射转换
方法 | 说明 | 示例 |
---|---|---|
map(Function) |
元素转换 | stream.map(String::toUpperCase) |
flatMap(Function) |
扁平化转换(合并流) | stream.flatMap(list -> list.stream()) |
mapToInt/Double/Long() |
转为数值流 | stream.mapToInt(Integer::parseInt) |
3.排序与窥视
方法 | 说明 | 示例 |
---|---|---|
sorted() |
自然排序 | stream.sorted() |
sorted(Comparator) |
自定义排序 | stream.sorted(Comparator.reverseOrder()) |
peek(Consumer) |
窥视元素(调试用) | stream.peek(System.out::println) |
二、终止操作(Terminal Operations)
终止操作会触发实际计算,返回非 Stream 结果。
1.遍历与匹配
方法 | 说明 | 示例 |
---|---|---|
forEach(Consumer) |
遍历元素 | stream.forEach(System.out::println) |
allMatch(Predicate) |
所有元素匹配 | stream.allMatch(s -> s.startsWith("A")) |
anyMatch(Predicate) |
任一元素匹配 | stream.anyMatch(s -> s.contains("java")) |
noneMatch(Predicate) |
无元素匹配 | stream.noneMatch(s -> s.isEmpty()) |
findFirst() |
返回第一个元素 | stream.findFirst() |
findAny() |
返回任意元素(并行流适用) | stream.findAny() |
2.归约与收集
方法 | 说明 | 示例 |
---|---|---|
reduce(BinaryOperator) |
归约计算 | stream.reduce((a, b) -> a + b) |
collect(Collector) |
转换为集合/其他结构 | stream.collect(Collectors.toList()) |
count() |
统计元素数量 | stream.count() |
min(Comparator) |
返回最小值 | stream.min(Integer::compare) |
max(Comparator) |
返回最大值 | stream.max(String::length) |
三、并行流(Parallel Stream)
通过 parallel() 或直接创建并行流,利用多核加速处理。
List<String> result = list.parallelStream()
.filter(s -> s.length() > 3)
.map(String::toUpperCase)
.collect(Collectors.toList());
注意事项:
1.确保操作是线程安全的。
2.数据量小可能反而更慢(线程切换开销)
3.避免在并行流中使用有状态操作(如sorted())
27.什么是自动装箱和自动拆箱?
1.基本概念
术语 | 定义 | 示例 |
---|---|---|
自动装箱 | 将基本数据类型自动转换为对应的包装类对象 | Integer i = 10; 等价于 Integer i = Integer.valueOf(10); |
自动拆箱 | 将包装类对象自动转换为对应的基本数据类型 | int num = i; 等价于 int num = i.intValue(); |
2.发生场景*
(1) 自动装箱场景
// 赋值操作
Integer a = 100; // 基本类型 → 包装类
// 方法参数传递
List<Integer> list = new ArrayList<>();
list.add(1); // 基本类型 → 包装类
// 三元运算符
boolean flag = true;
Integer result = flag ? 1 : 0; // 两边都会装箱
(2)自动拆箱场景
// 赋值操作
Integer b = Integer.valueOf(200);
int num = b; // 包装类 → 基本类型
// 运算操作
Integer x = 10, y = 20;
int sum = x + y; // 先拆箱再计算
// 比较操作
if (x > 5) { // 拆箱后比较
System.out.println("大于5");
}
3.注意事项
(1) 性能问题
频繁装箱/拆箱会导致额外对象创建(如循环内操作)
// 低效写法(每次循环都装箱)
Long sum = 0L;
for (long i = 0; i < 10000; i++) {
sum += i; // 隐含拆箱和重新装箱
}
(2) NPE风险
拆箱时包装类为null会抛NPE:
Integer nullInt = null;
int num = nullInt; // 抛出 NullPointerException
(3) 缓存机制
部分包装类缓存常用值(如 Integer缓存 -128~127)
Integer a = 127, b = 127;
System.out.println(a == b); // true (使用缓存对象)
Integer c = 128, d = 128;
System.out.println(c == d); // false (新建对象)
28.java中创建对象的方式有哪些?
1.使用new关键字(最常用)
特点:
直接触发类加载(如果类未加载)
调用构造函数初始化对象
对象分配在堆内存中
// 调用构造方法创建对象
Person person = new Person("张三", 25);
2.反射机制(Class.newInstance() 或 Constructor.newInstance())
特点:
动态创建对象(框架常用,如 Spring IOC)
可突破 private 构造方法的限制(通过setAccessible(true))
性能比 new 关键字差
// 方式1:Class.newInstance()(JDK9已废弃)
Person p1 = Person.class.newInstance();
// 方式2:Constructor.newInstance()(推荐)
Constructor<Person> constructor = Person.class.getConstructor(String.class, int.class);
Person p2 = constructor.newInstance("李四", 30);
3.克隆(Cloneable) 接口
特点:
浅拷贝(对象内部的引用类型成员仍指向原对象)
需实现 Cloneable 接口(否则抛 CloneNotSupportedException)
不调用构造方法
class Person implements Cloneable {
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
Person original = new Person("王五", 35);
Person cloned = (Person) original.clone();
4.反序列化(ObjectInputStream)
特点:
对象必须实现 Serializable 接口
不调用构造方法
常用于网络传输或持久化存储
// 序列化对象到文件
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.bin"));
oos.writeObject(new Person("赵六", 40));
oos.close();
// 反序列化创建对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.bin"));
Person deserialized = (Person) ois.readObject();
5.工厂模式/静态工作方法
特点:
封装对象创建逻辑
可返回子类对象(隐藏实现细节)
例如:Integer.valueOf()就是静态工厂方法
// 工厂类
class PersonFactory {
public static Person createPerson(String name, int age) {
return new Person(name, age);
}
}
// 使用工厂方法创建对象
Person p = PersonFactory.createPerson("田七", 45);
方式 | 是否调用构造方法 | 适用场景 | 性能 |
---|---|---|---|
new 关键字 |
✅ | 常规对象创建 | ⚡️ 最快 |
反射 | ✅ | 动态加载(如框架) | ⏳ 较慢 |
克隆 | ❌ | 复制已有对象 | ⚡️ 快 |
反序列化 | ❌ | 网络传输/持久化 | ⏳ 慢 |
工厂方法 | ✅ | 复杂对象创建 | ⚡️ 快 |
29.ArrayList的扩容机制
1.核心扩容原理
ArrayList 基于动态数据实现,当容量不足时会自动扩容,关键参数:
默认初始容量:10(无参构造时)
扩容公式:新容量 = 旧容量 + 旧容量 >> 1(及1.5倍)
最大容量:Integer.MAX_VALUE - 8 (部分JVM需要数组头信息)
2.扩容流程
步骤说明:
1.检查容量:当调用add() 方法时,先检查当前元素数量 size + 1 是否超过数组长度 elementData.length
2.触发扩容:如果容量不足,调用 grow() 方法扩容。
3.数据迁移:使用 Arrays.copyOf() 创建新数组并复制原数据
示例:
ArrayList<Integer> list = new ArrayList<>(); // 初始容量=10
for (int i = 0; i < 15; i++) {
list.add(i);
// 第11次add时触发扩容:10 -> 15
// 第16次add时再次扩容:15 -> 22
}
3.性能优化建议
1.预分配容量:如果已知数据量,构造时指定初始容量避免多次扩容。
2.避免频繁扩容:批量添加数据时使用 addAll() 比 循环 add() 更高效。
3.空间换时间:对大集合考虑使用 LinkedList 或 手动管理数组
30.说说常见的设计模式
设计模式是软件设计中针对常见问题的可重用解决方案。
一、创建型模式(5种)
1.单例模式(Singleton)
作用:确保一个类只有一个实例,并提供全局访问点
应用场景:配置管理、线程池、数据库连接池
2.工厂方法模式(Factory Method)
作用:定义创建对象的接口,让子类决定实例化哪个类
应用场景:日志记录器、数据库访问
3.抽象工厂模式(Abstract Factory)
作用:创建相关或依赖对象的家族,而不需指定具体类
特点:比工厂方法模式更高层次的抽象
4.建造者模式(Builder)
作用:将一个复杂对象的构建与其表示分离
应用场景:创建复杂对象(如XML解析器)
5.原型模式(Prototype)
作用:通过复制现有实例来创建新对象
关键方法:clone()
二、解构型模式(7种)
6.适配器模式(Adapter)
作用:将不兼容接口转换为可兼容接口
类型:
类适配器(继承)
对象适配器(组合)
7.桥接模式(Bridge)
作用:将抽象与实现分离,使它们可以独立变化
8.组合模式(Composite)
作用:将对象组合成树形结构以表示“部分-整体”层次结构
9.装饰器模式(Decorator)
作用:动态地给对象添加额外职责
10.外观模式(Facade)
作用:为子系统提供统一的高层接口
11.享元模式(Flyweight)
作用:通过共享技术有效支持大量细粒度对象
关键:区分内部状态和外部状态
12.代理模式(Proxy)
类型:远程代理、虚拟代理、保护代理、智能引用代理。
三、行为型模式(11种)
13.责任链模式(Chain of Responsibility)
作用:让多个对象都有机会处理请求
14.命令模式(Command)
应用场景:事务处理、任务队列
15.解释器模式(Interpreter)
作用:定义语言的文法表示、并解释执行
16.迭代器模式(Iterator)
作用:定义语言的文法表示,并解释执行
17.中介者模式(Mediator)
作用:用一个中介对象封装一系列对象交互
18.备忘录模式(Memento)
作用:捕获对象内部状态并在之后恢复
19.观察者模式(Observer)
应用场景:事件处理系统
20.状态模式(State)
作用:允许对象在内部状态改变时改变行为
21.策略模式(Strategy)
作用:定义算法族,使它们可以互相替换。
22.模板方式模式(Template Method)
作用:定义算法骨架,将某些步骤延迟到子类
23.访问者模式(Visitor)
作用:在不修改类的前提下为类添加新操作