一、String
1.1 String
是什么?
public final class String implements java.io.Serializable, Comparable<String>, CharSequence
特点:
-
是 不可变对象(immutable)
-
是 final 类,不能被继承
-
内部使用 字符数组存储内容
-
重写了
equals()
、hashCode()
、toString()
等方法 -
实现了
Comparable
和CharSequence
接口
1.2 不可变特性详解
String s1 = "abc";
s1 = s1 + "d";
System.out.println(s1); // abcd
-
本质上:
s1 + "d"
生成了一个新对象"abcd"
,s1
引用被修改,原"abc"
没变 -
不可变的好处:
-
线程安全
-
可以缓存 hash 值(提高查找效率)
-
可以作为 HashMap 的 key(哈希值不变)
-
1.3 内存结构与字符串常量池
String s1 = "hello";
String s2 = "hello";
System.out.println(s1 == s2); // true
-
"hello"
存在字符串常量池(方法区/元空间) -
相同字面量指向同一个对象,节省内存
String s3 = new String("hello");
System.out.println(s1 == s3); // false
new
会强制在堆上创建新对象,==
比较的是地址,不相等
intern() 方法
String s = new String("abc").intern();
-
将字符串加入常量池并返回池中对象的引用
-
常用于避免重复字符串
1.4 底层实现与源码分析(JDK 8)
private final char value[];
-
每个 String 对象内部有一个
char[]
,一旦创建就不能变 -
JDK 9+ 改为使用
byte[]
+ 编码标识,提升效率
1.5 常用方法分类
>判断类方法
s.equals("abc") // 是否相等
s.equalsIgnoreCase() // 忽略大小写比较
s.contains("abc") // 是否包含子串
s.startsWith("ab") // 是否以 ab 开头
s.endsWith("bc") // 是否以 bc 结尾
>查找/提取类方法
s.indexOf("a") // 第一个 a 出现的位置
s.lastIndexOf("a") // 最后一个 a 的位置
s.charAt(2) // 获取第 3 个字符
s.substring(1, 4) // 截取 [1,4) 子串
>替换/处理类方法
s.replace("a", "b") // 替换
s.trim() // 去掉首尾空格
s.toUpperCase() // 转大写
s.toLowerCase() // 转小写
s.split(",") // 拆分字符串为数组
>转换类方法
String.valueOf(123) // 任意类型转字符串
Integer.parseInt("123") // 字符串转 int
s.toCharArray() // 字符串转字符数组
1.6 字符串拼接与性能分析
String result = "";
for (int i = 0; i < 1000; i++) {
result += i;
}
上面每次 +=
都会创建新对象,性能低下。
推荐使用:StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append(i);
}
String result = sb.toString();
1.7 字符串比较
String s1 = "abc";
String s2 = new String("abc");
System.out.println(s1 == s2); // false(引用不同)
System.out.println(s1.equals(s2)); // true(内容相同)
-
==
比较引用 equals()
比较内容compareTo()
用于排序(按 Unicode)
1.8 String 在集合中做 key?
-
String 是不可变的,hashCode 不变,适合作为
HashMap
的 key。 -
equals()
与hashCode()
都已重写,保证内容相等时哈希一致。
1.9 常见问题
问题 | 简要回答 |
---|---|
String 为什么不可变? | 提高安全性、支持缓存、可作为 Hash key、线程安全 |
== 与 equals() 区别? | == 比地址,equals 比内容 |
字符串拼接性能问题? | 多次拼接用 StringBuilder |
intern() 的作用? | 把字符串加入常量池,返回常量池中地址 |
字符串常量池在哪里? | JDK 8 在方法区(元空间),JDK 7+ 移至堆 |
String 能被继承吗? | 不能,是 final 类 |
String 为什么能作为 Map 的 key? | 不可变、重写了 hashCode 和 equals |
new String("abc") 创建了几个对象? | 常量池中 1 个("abc"),堆中又 1 个(new) |
1.10 小结
特性 | 说明 |
---|---|
不可变 | 修改就生成新对象,安全,支持哈希 |
常量池优化 | 相同字面量只创建一次 |
内部结构 | JDK 8 是 char[],JDK 9 是 byte[] + 编码 |
性能优化建议 | 多次拼接用 StringBuilder |
与其他类区别 | 与 StringBuilder / Buffer 的线程与性能区别 |
二、StringBuilder
2.1 什么是 StringBuilder?
public final class StringBuilder extends AbstractStringBuilder implements java.io.Serializable, CharSequence
特点:
-
可变字符串(mutable)
-
线程不安全(但性能更高)
-
使用字符数组维护可变字符串内容
-
用于大量拼接字符串时的性能优化
2.2 StringBuilder 与 String 的区别
特性 | String | StringBuilder |
---|---|---|
是否可变 | 不可变(immutable) | 可变 |
线程安全 | 安全 | 不安全 |
拼接效率 | 低(频繁创建新对象) | 高(内部数组直接修改) |
底层结构 | char[] | char[] (可扩容) |
用途 | 小量拼接、常量字符串 | 频繁拼接、大量字符串操作 |
2.3 常用构造函数
StringBuilder sb1 = new StringBuilder(); // 默认容量16
StringBuilder sb2 = new StringBuilder("hello"); // 初始化内容
StringBuilder sb3 = new StringBuilder(100); // 指定初始容量
2.4 常用方法
方法 | 作用 |
---|---|
append() | 添加内容到末尾 |
insert(index, str) | 在指定位置插入字符串 |
delete(start, end) | 删除区间内容(左闭右开) |
deleteCharAt(index) | 删除指定位置字符 |
replace(start, end, s) | 替换某一段字符串 |
reverse() | 反转字符串 |
toString() | 转成 String 对象 |
setCharAt(index, ch) | 修改指定位置字符 |
charAt(index) | 读取指定位置字符 |
length() | 当前长度 |
capacity() | 当前容量(可容纳的最大字符数) |
ensureCapacity(int) | 手动增加容量 |
trimToSize() | 减少容量到实际长度 |
2.5 append 示例:高性能拼接
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append(i).append(",");
}
String result = sb.toString();
append()
会修改原数组,不创建新对象,效率远高于String +=
2.6 底层原理:char[] + 自动扩容
// JDK 8 中
char[] value;
int count;
-
初始容量为 16,超过就自动扩容
-
每次扩容为:
newCapacity = oldCapacity * 2 + 2
void expandCapacity(int minimumCapacity) {
int newCapacity = (value.length << 1) + 2;
if (newCapacity < minimumCapacity) {
newCapacity = minimumCapacity;
}
value = Arrays.copyOf(value, newCapacity);
}
2.7 扩容机制详解
默认构造:
StringBuilder sb = new StringBuilder();
-
初始化时创建
char[16]
数组 -
一旦内容超过 16 个字符,就扩容为
16*2+2 = 34
-
后续继续超过,又变为
34*2+2 = 70
,以此类推
2.8 线程安全性对比:StringBuffer
类名 | 可变 | 线程安全 | 性能 | 使用场景 |
---|---|---|---|---|
String | 否 | 是 | 最低 | 字符串不变或少量拼接 |
StringBuilder | 是 | 否 | 最高 | 单线程、大量拼接 |
StringBuffer | 是 | 是 | 中等 | 多线程环境拼接字符串 |
2.9 常见问题
问题 | 解答说明 |
---|---|
StringBuilder 适合什么场景? | 在循环中频繁拼接字符串时使用 |
append 是怎么实现的? | 直接往 char[] 数组写,满了就扩容 |
线程安全吗? | 不安全(非同步),多线程环境用 StringBuffer |
初始容量是多少? | 默认 16,构造函数可指定 |
为什么效率比 String 高? | 不创建新对象,原地修改数组,减少内存和 CPU 开销 |
2.10 实践建议
建议 | 说明 |
---|---|
多次拼接请用 StringBuilder | 尤其是循环中拼接字符串 |
如果知道大致容量,建议指定容量构造函数 | 减少扩容次数,提高效率 |
单线程下推荐使用 StringBuilder | 比 StringBuffer 快很多 |
多线程共享字符串拼接建议用 StringBuffer | 线程安全但稍慢 |
2.11 示例代码
public class Demo {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("abc");
sb.append(123).append(true);
System.out.println(sb); // abc123true
sb.insert(3, "XYZ");
System.out.println(sb); // abcXYZ123true
sb.delete(3, 6);
System.out.println(sb); // abc123true
sb.replace(3, 6, "___");
System.out.println(sb); // abc___true
sb.reverse();
System.out.println(sb); // eurt___cba
System.out.println(sb.capacity()); // 查看当前容量
}
}
三、StringBuffer
3.1 什么是 StringBuffer
public final class StringBuffer extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
特点总结:
-
可变字符串类(mutable)
-
线程安全:方法加了
synchronized
-
底层使用
char[]
存储字符串 -
实现了
CharSequence
接口,可用于for-each
3.2 StringBuffer 与其他类对比
类名 | 可变 | 线程安全 | 适合场景 | 性能 |
---|---|---|---|---|
String | 否 | 是 | 不可变字符串、少量拼接 | 最低 |
StringBuilder | 是 | 否 | 单线程环境拼接字符串 | 最快 |
StringBuffer | 是 | 是 | 多线程环境拼接字符串 | 较快(比 StringBuilder 慢) |
3.3 常用构造方法
StringBuffer sb1 = new StringBuffer(); // 默认容量 16
StringBuffer sb2 = new StringBuffer("hello"); // 初始内容
StringBuffer sb3 = new StringBuffer(100); // 指定容量
3.4 常用方法和功能
添加内容(append)
sb.append("abc"); // 添加字符串
sb.append(123); // 添加数字
sb.append(true); // 添加布尔值
插入内容(insert)
sb.insert(1, "xyz"); // 在位置1插入 "xyz"
删除内容(delete)
sb.delete(3, 6); // 删除从3到6(左闭右开)
sb.deleteCharAt(2); // 删除第2个字符
替换、反转、设置
sb.replace(1, 4, "ZZZ"); // 替换子串
sb.reverse(); // 反转整个字符串
sb.setCharAt(0, 'X'); // 修改第一个字符
长度与容量
sb.length(); // 当前实际长度
sb.capacity(); // 当前 char[] 容量(默认16)
sb.ensureCapacity(50); // 预设容量,避免扩容
转换为 String
String s = sb.toString(); // 获取最终字符串
3.5 线程安全实现原理
所有核心方法都加了 synchronized
,示例源码如下:
public synchronized StringBuffer append(String str) {
super.append(str);
return this;
}
-
多线程环境中,同一个
StringBuffer
实例的操作是互斥的,防止数据错乱。 -
缺点是性能稍慢。
3.6 扩容机制(与 StringBuilder 相同)
-
初始容量是 16。
-
如果容量不足,会扩容为:
newCapacity = oldCapacity * 2 + 2;
底层使用的数组方式(JDK 8):
char[] value;
3.7 性能对比总结
单线程下:
String str = "";
for (int i = 0; i < 10000; i++) {
str += i; // 最慢(频繁创建新对象)
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
sb.append(i); // 推荐,最快
}
StringBuffer sbf = new StringBuffer();
for (int i = 0; i < 10000; i++) {
sbf.append(i); // 稍慢,线程安全
}
3.8 常见问题
问题 | 回答简要说明 |
---|---|
StringBuffer 和 StringBuilder 区别? | Buffer 线程安全,Builder 非线程安全 |
为什么 StringBuffer 慢? | 方法加了 synchronized ,存在锁竞争 |
StringBuffer 初始容量是多少? | 默认是 16 字符长度 |
能否替代 String ? | 不能,String 是不可变类,适合做常量、Map key |
适合的使用场景? | 多线程环境下的大量字符串拼接 |
3.9 示例代码
public class BufferDemo {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("Hello");
sb.append(" World"); // 拼接
sb.insert(5, ","); // 插入
sb.replace(0, 5, "Hi"); // 替换
sb.deleteCharAt(2); // 删除字符
sb.reverse(); // 反转
System.out.println(sb.toString()); // 输出结果
System.out.println("Length: " + sb.length());
System.out.println("Capacity: " + sb.capacity());
}
}
3.10 小结
特性 | 说明 |
---|---|
可变性 | 内容可变,支持追加、插入、替换 |
线程安全性 | 所有方法加了 synchronized ,适合并发场景 |
拼接效率 | 比 String 高,单线程略低于 StringBuilder |
扩容机制 | 自动扩容为 old * 2 + 2 |
推荐场景 | 多线程拼接字符串(如日志缓冲区、消息构建) |
四、List(ArrayList、LinkedList)
4.1 List 接口简介
基本定义
public interface List<E> extends Collection<E>
特点:
-
有序集合(元素按插入顺序排列)
-
允许重复元素
-
可以通过索引(index)访问元素
-
提供大量用于插入、删除、替换的操作方法
4.2 常用方法
方法 | 说明 |
---|---|
add(E e) | 添加元素到末尾 |
add(int index, E e) | 指定位置插入 |
remove(int index) | 删除指定位置元素 |
get(int index) | 获取指定位置元素 |
set(int index, E e) | 替换指定位置的元素 |
indexOf(Object o) | 返回首次出现的索引 |
contains(Object o) | 是否包含某个元素 |
size() | 集合大小 |
clear() | 清空集合 |
4.3 List 的两大核心实现类对比:ArrayList vs LinkedList
特性 | ArrayList | LinkedList |
---|---|---|
底层结构 | 动态数组(Object[]) | 双向链表(Node) |
查询效率 | 高(O(1)) | 低(O(n)) |
插入/删除效率 | 中(尾部快,中间慢,O(n)) | 高(O(1) 位置已知) |
内存使用 | 少(数组连续内存) | 多(存指针结构) |
线程安全 | 否 | 否 |
适合场景 | 多查询、少插入/删除 | 多插入/删除、少查询 |
4.4 ArrayList 详解
构造函数
List<String> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>(100); // 指定初始容量
底层实现
-
内部使用
Object[]
数组存储元素 -
容量不够时自动扩容(扩容机制为原容量的
1.5倍
) -
随机访问元素效率高:
list.get(i)
时间复杂度 O(1)
添加元素
list.add("A"); // 添加到末尾
list.add(1, "B"); // 插入到下标1
删除元素
list.remove(0); // 删除指定下标
list.remove("A"); // 删除首次匹配元素
查询元素
list.get(0); // 获取第一个元素
list.contains("X"); // 是否存在
遍历
for (String s : list) { System.out.println(s); }
4.5 LinkedList 详解
构造函数
List<String> list = new LinkedList<>();
底层实现
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
}
-
每个元素是一个节点,节点包含指向前一个、后一个的引用
-
插入、删除元素时只需改指针,不需要数组拷贝
-
访问指定位置时,需要从头/尾顺序遍历,效率低
特有方法(作为队列和栈)
方法 | 类型 | 说明 |
---|---|---|
addFirst() | 栈/队列 | 从头部添加元素 |
addLast() | 队列 | 从尾部添加元素 |
removeFirst() | 栈/队列 | 移除头部元素 |
removeLast() | 队列 | 移除尾部元素 |
peekFirst() | 队列/栈 | 查看头部元素但不删除 |
4.6 应用场景对比总结
使用场景 | 推荐使用的实现类 |
---|---|
查询操作频繁 | ArrayList (效率高) |
插入/删除操作频繁 | LinkedList (无需位移) |
实现队列(FIFO) | LinkedList |
实现栈(LIFO) | LinkedList |
数据结构较小,无需考虑性能 | 任意都可以 |
4.7 实战示例
import java.util.*;
public class ListDemo {
public static void main(String[] args) {
// ArrayList 示例
List<String> arrayList = new ArrayList<>();
arrayList.add("Java");
arrayList.add("Python");
arrayList.add("C++");
System.out.println("ArrayList: " + arrayList);
// LinkedList 示例(队列)
LinkedList<String> linkedList = new LinkedList<>();
linkedList.addFirst("A");
linkedList.addLast("B");
linkedList.add("C"); // 默认加在尾部
System.out.println("LinkedList: " + linkedList);
linkedList.removeFirst();
System.out.println("After removeFirst: " + linkedList);
}
}
4.8 常见问题
问题 | 简要解答 |
---|---|
ArrayList 和 LinkedList 区别? | 底层结构、增删查性能差异 |
ArrayList 如何扩容? | 原容量的 1.5 倍(newCapacity = old + old >> 1 ) |
LinkedList 插入为什么效率高? | 改链表指针即可,无需数组移动 |
ArrayList 删除中间元素性能差在哪里? | 后续元素要整体前移,O(n) |
是否线程安全? | 默认都不是,需使用 Collections.synchronizedList |
4.9 线程安全的 List
List<String> safeList = Collections.synchronizedList(new ArrayList<>());
或者使用 JDK 1.5 之后的:
List<String> copyOnWriteList = new CopyOnWriteArrayList<>();
适用于读多写少的并发场景。
五、Map(HashMap、TreeMap、LinkedHashMap)
5.1 Map 接口简介
特点:
-
存储的是「键值对 key-value」
-
key
不可重复,value
可重复 -
常用于:缓存、对象映射、参数传递等
-
提供查询、添加、删除、遍历等操作
5.2 常用 Map 实现类对比
实现类 | 底层结构 | 是否有序 | 是否线程安全 | 是否允许 null key/value | 排序方式 |
---|---|---|---|---|---|
HashMap | 数组 + 链表 + 红黑树(JDK8) | 无序 | 否 | 允许 | 无顺序 |
LinkedHashMap | HashMap + 双向链表 | 插入顺序 | 否 | 允许 | 按插入顺序 |
TreeMap | 红黑树(Tree) | 排序 | 否 | 不允许 null key | 按 key 自然顺序 或 Comparator |
5.3 Map 常用方法
方法 | 说明 |
---|---|
put(K key, V value) | 添加或替换键值对 |
get(K key) | 根据 key 获取 value |
remove(K key) | 删除指定 key 的项 |
containsKey(K key) | 是否包含某个 key |
containsValue(V value) | 是否包含某个 value |
keySet() | 获取所有 key 的集合 |
values() | 获取所有 value 的集合 |
entrySet() | 获取所有键值对(Map.Entry)集合 |
size() | 获取元素个数 |
5.4 HashMap 详解
底层结构(JDK 1.8)
Node<K,V>[] table; // 数组
-
每个桶(table[i])是一个链表或红黑树
-
默认初始容量:16
-
负载因子:0.75(容量达到 75% 时触发扩容)
-
触发红黑树条件:
-
链表长度 ≥ 8 且 table.length ≥ 64,才转换为红黑树
-
否则仍使用链表(节省空间)
-
put 原理简述:
hash(key) -> 计算 hash 值
index = hash & (table.length - 1) -> 计算桶位置
插入链表 or 红黑树 or 替换已有 key 的值
示例代码:
Map<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);
System.out.println(map.get("A")); // 输出 1
5.5 LinkedHashMap 详解
特点:
-
保持插入顺序(也可配置为访问顺序)
-
基于
HashMap
+ 双向链表 实现 -
常用于实现 LRU 缓存
结构图示:
HashMap 提供存取性能
双向链表维护元素顺序
LRU 缓存示例
LinkedHashMap<String, String> lruCache = new LinkedHashMap<>(16, 0.75f, true) {
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > 100;
}
};
5.6 TreeMap 详解(有序)
特点:
-
内部基于红黑树实现
-
元素按 key 的自然顺序 或 Comparator 指定顺序排序
-
查询、插入、删除复杂度为 O(log n)
适合场景:
-
要求 key 有序,如:排行榜、时间线
-
需要按区间查询数据
示例代码:
TreeMap<Integer, String> treeMap = new TreeMap<>();
treeMap.put(3, "C");
treeMap.put(1, "A");
treeMap.put(2, "B");
System.out.println(treeMap); // 按 key 升序输出
5.7 三者使用场景对比
场景 | 推荐 Map 实现类 |
---|---|
快速存取、无序 | HashMap |
保留插入顺序 | LinkedHashMap |
要求 key 排序或区间查询 | TreeMap |
实现 LRU 缓存 | LinkedHashMap (访问顺序) |
线程安全要求 | ConcurrentHashMap (推荐) |
5.8 线程安全 Map 替代品
类名 | 特点 |
---|---|
Hashtable | 线程安全,方法加 synchronized ,效率低 |
Collections.synchronizedMap | 包装同步 Map,对每个方法加锁 |
ConcurrentHashMap | 高并发环境下的 Map,分段锁或 CAS,更高性能 |
5.9 遍历方式
// 遍历键值对
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// 遍历 key
for (String key : map.keySet()) {
System.out.println(key);
}
// 遍历 value
for (Integer value : map.values()) {
System.out.println(value);
}
5.10 常见问题
问题 | 解答简要 |
---|---|
HashMap 如何解决哈希冲突? | 链地址法(链表)+ JDK8 后转为红黑树 |
HashMap 扩容机制? | 扩容为原容量的2倍,重新计算 hash 和索引 |
HashMap 的线程不安全性体现? | 并发 put 可能导致死循环、数据丢失等 |
HashMap 和 TreeMap 区别? | HashMap 无序,TreeMap 有序(红黑树) |
LinkedHashMap 如何实现 LRU? | 构造函数设为访问顺序 + 重写 removeEldestEntry |
HashMap 初始容量设多少最合适? | 预计大小 / 0.75(负载因子) |
5.11 小结
实现类 | 底层结构 | 是否有序 | 主要用途 |
---|---|---|---|
HashMap | 数组 + 链表/红黑树 | 无序 | 常规场景,性能好 |
LinkedHashMap | HashMap + 双向链表 | 插入序 | 顺序访问、LRU 缓存等 |
TreeMap | 红黑树 | 排序 | 有序数据存储、区间查询 |
六、Set(HashSet、TreeSet)
6.1 Set 接口简介
特点:
-
元素不可重复(底层通过比较是否相等来判断)
-
不保证元素顺序(除非用
TreeSet
) -
适用于去重、集合运算、判重等场景
6.2 Set 常用实现类对比
实现类 | 底层结构 | 是否有序 | 是否线程安全 | null 是否允许 | 排序依据 |
---|---|---|---|---|---|
HashSet | HashMap 实现 | 无序 | 否 | 允许一个 null | 无序(依赖 hash 值) |
TreeSet | TreeMap(红黑树) | 有序 | 否 | 不允许 null | 按照 Comparable 或 Comparator |
6.3 HashSet 详解
底层原理
-
HashSet
底层使用了一个HashMap
来实现,值存在 Map 的 key 里。 -
本质:
HashSet<E> set = new HashSet<>();
其实是Map<E, Object> map = new HashMap<>();
特点:
-
插入顺序不保证
-
元素唯一性通过
hashCode()
和equals()
决定 -
插入效率高,适合查重、快速去重
示例:
Set<String> set = new HashSet<>();
set.add("A");
set.add("B");
set.add("A"); // 被忽略,不重复
System.out.println(set); // [A, B],无序
重要方法:
方法 | 作用 |
---|---|
add(E e) | 添加元素(若重复返回 false) |
remove(E e) | 删除元素 |
contains(E e) | 判断元素是否存在 |
size() | 获取元素数量 |
clear() | 清空集合 |
isEmpty() | 判断是否为空 |
6.4 TreeSet 详解(排序用)
底层原理:
-
TreeSet 是基于
TreeMap
实现的,内部维护的是一棵红黑树 -
元素自动排序(升序)
特点:
-
有序:默认按自然顺序(Comparable),也可自定义排序器(Comparator)
-
查询/插入/删除时间复杂度为 O(log n)
-
不允许添加 null 元素
示例:
Set<Integer> treeSet = new TreeSet<>();
treeSet.add(5);
treeSet.add(1);
treeSet.add(3);
System.out.println(treeSet); // [1, 3, 5],有序
自定义排序:
Set<String> sortedSet = new TreeSet<>((a, b) -> b.compareTo(a)); // 降序
sortedSet.add("apple");
sortedSet.add("banana");
System.out.println(sortedSet); // [banana, apple]
6.5 Set 相关问题
1)Set 为什么不能存重复元素?
因为底层在 HashSet
中是使用 hashCode()
和 equals()
来判断两个元素是否“相同”。一旦认为相同,新的元素就不会被加入。重点:重写 equals 一定要同时重写 hashCode
2)HashSet 添加自定义对象无效,怎么解决?
class Person {
String name;
int age;
// 忘记重写 equals 和 hashCode 会导致 set 无法正确去重
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && name.equals(person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
3)TreeSet 是否能存 null?
-
不可以!
-
插入 null 会抛出
NullPointerException
(因为无法与其他元素进行比较)
4)Set 如何实现排序功能?
-
使用
TreeSet
(默认升序) -
或者使用
List
+Collections.sort()
来手动排序
6.6 遍历方式(适用于所有 Set)
Set<String> set = new HashSet<>();
set.add("A");
set.add("B");
// 方式1:增强 for 循环
for (String s : set) {
System.out.println(s);
}
// 方式2:迭代器
Iterator<String> iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
6.7 Set vs List 对比
特点 | Set | List |
---|---|---|
是否可重复 | 否 | 是 |
是否有序 | TreeSet 有序,HashSet 无序 | 有序(ArrayList) |
查询性能 | TreeSet 慢于 HashSet | ArrayList 查询快 |
应用场景 | 去重、集合运算 | 排序、位置访问操作 |
6.8 小结
实现类 | 底层结构 | 是否排序 | 是否允许 null | 典型用途 |
---|---|---|---|---|
HashSet | HashMap | 无序 | 允许一个 | 去重、无序集合 |
TreeSet | 红黑树(TreeMap) | 自动排序 | 不允许 | 有序集合、排序需求 |
七、Collection
7.1 Collection 简介
定义:Collection
是 Java 集合框架中最基本的接口,是 List、Set、Queue 的父接口。用于存储一组对象(元素),是单值集合的顶层接口(不同于 Map)。
public interface Collection<E> extends Iterable<E>
常用子接口:
Collection
↑
┌───────┼────────┐
↓ ↓ ↓
List Set Queue
7.2 Collection 的常用方法
方法 | 说明 |
---|---|
boolean add(E e) | 添加元素 |
boolean addAll(Collection c) | 添加集合中的所有元素 |
void clear() | 清空集合 |
boolean contains(Object o) | 是否包含某元素 |
boolean containsAll(Collection c) | 是否包含所有元素 |
boolean isEmpty() | 是否为空集合 |
Iterator<E> iterator() | 返回迭代器 |
boolean remove(Object o) | 删除指定元素 |
boolean removeAll(Collection c) | 删除所有与集合相同的元素 |
boolean retainAll(Collection c) | 取交集(只保留同时存在的) |
int size() | 获取元素个数 |
Object[] toArray() | 转为 Object 数组 |
<T> T[] toArray(T[] a) | 转为指定类型数组 |
7.3 Collection 常见使用示例
Collection<String> coll = new ArrayList<>();
coll.add("apple");
coll.add("banana");
System.out.println(coll.contains("apple")); // true
System.out.println(coll.size()); // 2
for (String item : coll) {
System.out.println(item);
}
coll.remove("banana");
System.out.println(coll); // [apple]
7.4 Collection 接口 VS Arrays
对比项 | Collection 接口 | 数组(Array) |
---|---|---|
大小 | 可变 | 固定大小 |
类型 | 存储对象 | 基本类型或对象 |
方法支持 | 有大量操作方法 | 无操作方法,结构简单 |
性能 | 较低(要平衡功能) | 高(但功能有限) |
7.5 Collection 接口的实现体系
接口/类 | 类型 | 特点/说明 |
---|---|---|
List | 接口 | 有序、可重复元素 |
Set | 接口 | 无序、不可重复元素 |
Queue | 接口 | 队列,先进先出 |
Deque | 接口 | 双端队列 |
ArrayList | List 实现 | 动态数组结构,查询快 |
LinkedList | List & Deque 实现 | 链表结构,增删快 |
HashSet | Set 实现 | 无序、基于哈希表 |
TreeSet | Set 实现 | 有序、基于红黑树 |
PriorityQueue | Queue 实现 | 优先队列,可自定义优先级 |
7.6 增强 for 和 Iterator 遍历
Collection<String> list = new ArrayList<>();
list.add("Java");
list.add("Python");
// 增强 for
for (String lang : list) {
System.out.println(lang);
}
// Iterator
Iterator<String> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
7.7 与泛型结合使用
避免强制类型转换,提高代码安全性与可读性:
Collection<Integer> nums = new ArrayList<>();
nums.add(10);
nums.add(20);
for (int num : nums) {
System.out.println(num);
}
7.8 常见问题
Collection 与 Collections 区别?
名称 | 类型 | 说明 |
---|---|---|
Collection | 接口 | 是集合类的顶层接口,用于存储一组对象 |
Collections | 工具类 | 提供各种集合相关的静态方法,如排序、复制、线程安全包装等 |
为什么 Collection 接口没有 get(int index) 方法?
因为不是所有集合都有顺序(如 Set),因此不提供按下标访问的统一定义。只有 List
(有序)提供此能力。
7.9 小结
特点 | 说明 |
---|---|
接口层级 | Collection 是 List、Set 的父接口 |
主要功能 | 单值集合(增删查遍历等) |
常见实现类 | ArrayList, LinkedList, HashSet 等 |
与 Map 区别 | Collection 只能存单值,Map 是键值对 |
与数组对比 | 集合功能强大,数组结构简单 |
八、Iterator
8.1 什么是 Iterator?
定义:Iterator
是一个迭代器接口,用于遍历集合中的元素。它是 Collection
接口的一个成员方法返回的对象。
public interface Iterator<E> {
boolean hasNext();
E next();
void remove(); // 可选操作
}
8.2 Iterator 能做什么?
方法 | 作用 |
---|---|
hasNext() | 判断是否还有下一个元素 |
next() | 返回下一个元素 |
remove() | 删除当前迭代到的元素(调用 next() 后才能调用) |
8.3 常见使用方式
List<String> list = new ArrayList<>();
list.add("Java");
list.add("Python");
list.add("C++");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String lang = iterator.next();
System.out.println(lang);
}
8.4 与增强 for 的区别
特点 | 增强 for | Iterator |
---|---|---|
语法简洁 | 是 | 否,需显式声明 |
遍历安全性 | 否,不能安全删除元素 | 是,可以安全删除元素 |
适用范围 | Iterable 接口(如 Collection) | 同样适用 |
是否能修改元素 | 否 | 是 可通过 remove() 删除 |
8.5 使用 Iterator 删除元素(正确方式)
不要在增强 for 中删除元素!会报 ConcurrentModificationException
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
int num = it.next();
if (num == 2) {
it.remove(); // 安全删除
}
}
System.out.println(list); // [1, 3]
8.6 fail-fast机制(快速失败)
现象:如果在使用 Iterator
遍历集合时,集合结构被外部修改(非通过 Iterator.remove()
),就会抛出:
java.util.ConcurrentModificationException
示例(错误写法):
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
for (String s : list) {
if (s.equals("b")) {
list.remove(s); // 修改结构,触发 fail-fast
}
}
正确做法:
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if (it.next().equals("b")) {
it.remove(); // 安全删除
}
}
原因分析:ArrayList
、HashSet
等集合中维护了一个 modCount
字段,记录结构修改次数。Iterator
内部也保存了一个期望的 expectedModCount
,两者不一致就抛异常。
8.7 Iterable 接口与 for-each 本质
Collection 实现了 Iterable:
public interface Iterable<T> {
Iterator<T> iterator();
}
所以增强 for 本质上就是调用了 iterator()
:
for (String s : list) {
System.out.println(s);
}
// 相当于:
for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
String s = it.next();
System.out.println(s);
}
8.8 使用场景总结
场景 | 是否适合使用 Iterator |
---|---|
遍历元素 | 是 |
删除元素 | 是(比 for 循环更安全) |
随机访问(按下标) | 否(用 List 的 get 更合适) |
8.9 与 ListIterator 区别(只用于 List)
方法 | Iterator | ListIterator |
---|---|---|
支持向前遍历 | 不支持 | 支持 hasPrevious() ,previous() |
支持添加元素 | 不支持 | 支持 add() |
支持替换元素 | 不支持 | 支持 set() |
适用集合 | Collection | 只能用于 List |
8.10 小结
关键点 | 内容 |
---|---|
核心方法 | hasNext() 、next() 、remove() |
删除方式 | 推荐使用 Iterator.remove() |
fail-fast | 非法修改集合结构会抛异常 |
遍历替代方式 | 增强 for(简单,但不能安全删除元素) |
适配接口 | Collection 、Map 的 keySet()/entrySet() |
九、泛型
9.1 什么是泛型?
泛型(Generic)允许在定义类、接口、方法时使用类型参数,使代码在编译时就进行类型检查,避免强制类型转换。
例子:
List<String> list = new ArrayList<>();
list.add("Java");
String s = list.get(0); // 不需要强制类型转换
没有泛型前:
List list = new ArrayList();
list.add("Java");
String s = (String) list.get(0); // 需要强转,易出错
9.2 泛型的好处
优势 | 说明 |
---|---|
编译期类型检查 | 提前发现错误,避免 ClassCastException |
消除强制类型转换 | 简化代码 |
提高代码复用性 | 写一次泛型类,可适用于多种类型 |
9.3 泛型使用位置
1)泛型类
public class Box<T> {
private T value;
public void set(T value) { this.value = value; }
public T get() { return value; }
}
使用:
Box<String> stringBox = new Box<>();
stringBox.set("Hello");
System.out.println(stringBox.get()); // Hello
2)泛型接口
public interface Converter<F, T> {
T convert(F from);
}
实现类:
public class StringToIntegerConverter implements Converter<String, Integer> {
public Integer convert(String from) {
return Integer.parseInt(from);
}
}
3)泛型方法
public class Util {
public static <T> void printArray(T[] array) {
for (T item : array) {
System.out.println(item);
}
}
}
调用:
String[] arr = {"A", "B", "C"};
Util.printArray(arr);
9.4 常见泛型写法
写法 | 说明 |
---|---|
<T> | 单个类型参数,常见 |
<K, V> | 多个类型参数,如 Map |
<E> | 常用于集合(Element) |
9.5 泛型通配符 ?
1)<?>
表示任意类型
public void printList(List<?> list) {
for (Object o : list) {
System.out.println(o);
}
}
不能往 List<?>
添加任何元素(除了 null
),因为编译器不知道真实类型。
2)<? extends T>
上界通配符:T 及其子类
public void printUpper(List<? extends Number> list) {
// list 可能是 List<Integer>, List<Double>...
}
特点:可读不可写(写入时类型不明确)
3)<? super T>
下界通配符:T 及其父类
public void printLower(List<? super Integer> list) {
list.add(10); // 可以添加 Integer 或其子类
}
特点:可写不可读(读取时只能当 Object)
9.6 泛型擦除(类型擦除)
Java 泛型只在编译期有效,编译后会擦除类型信息,称为 Type Erasure。
List<String> list = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
System.out.println(list.getClass() == list2.getClass()); // true
9.7 泛型的限制
限制 | 说明 |
---|---|
不能使用基本类型(int、double 等) | 用包装类代替,如 Integer |
泛型类中不能创建泛型数组 | new T[] 不允许 |
不能在泛型中使用 instanceof 判断类型 | instanceof T 不合法 |
9.8 类型变量命名规范
符号 | 含义 |
---|---|
T | Type 类型 |
E | Element 元素(集合常用) |
K | Key 键(Map 常用) |
V | Value 值(Map 常用) |
N | Number 数值类型 |
9.9 示例:泛型与集合结合使用
Map<String, List<Integer>> map = new HashMap<>();
List<Integer> scores = new ArrayList<>();
scores.add(90);
map.put("Tom", scores);
System.out.println(map.get("Tom").get(0)); // 90
9.10 泛型 VS Object 的对比
特性 | 使用 Object | 使用泛型 |
---|---|---|
类型检查 | 运行时检查,风险高 | 编译时检查,安全 |
可读性 | 低,需要强转 | 高,清晰直观 |
类型转换 | 必须强转 | 自动完成 |
9.11 小结
概念 | 关键点 |
---|---|
泛型类 | <T> 定义类中的类型 |
泛型方法 | <T> 返回类型 方法名(...) |
通配符 | <?> , <? extends T> , <? super T> |
泛型擦除 | 编译后泛型信息被擦除 |
编译期安全性 | 最大优势是避免 ClassCastException |
十、Arrays 工具类
10.1 Arrays
工具类简介
-
包名:
java.util.Arrays
-
作用:提供数组的排序、搜索、复制、填充、比较、转字符串等静态方法
-
特点:全部是静态方法(
static
),不需要创建对象,直接用类名调用即可。
10.2 常用方法总览(分类汇总)
方法分类 | 常用方法 | 简介 |
---|---|---|
排序 | sort() | 排序数组(默认升序) |
查找 | binarySearch() | 使用二分查找定位元素(排序后使用) |
比较 | equals() | 判断两个数组是否相等(元素逐一比较) |
复制 | copyOf() , copyOfRange() | 拷贝数组 |
填充 | fill() | 用指定值填充整个数组 |
输出 | toString() | 转换为字符串(调试友好) |
并行操作 | parallelSort() | 多线程快速排序(大数组更快) |
10.3 各方法详细讲解
1)sort()
– 数组排序
int[] arr = {3, 1, 5, 2};
Arrays.sort(arr);
System.out.println(Arrays.toString(arr)); // [1, 2, 3, 5]
支持对基本类型和对象数组排序。对象数组需实现 Comparable
或传入 Comparator
。
String[] names = {"Bob", "Alice", "Eve"};
Arrays.sort(names); // 按字母排序
2)binarySearch()
– 二分查找(数组必须已排序)
int[] arr = {1, 2, 3, 5};
int index = Arrays.binarySearch(arr, 3); // 返回索引:2
int indexNotFound = Arrays.binarySearch(arr, 4); // 返回插入点位置的负值 -4(-插入位置 -1)
使用前必须排序,否则结果不可预测。
3)equals()
– 比较两个数组是否完全相等
int[] a = {1, 2, 3};
int[] b = {1, 2, 3};
System.out.println(Arrays.equals(a, b)); // true
对于二维数组(多维),需要用 deepEquals()
:
int[][] x = {{1, 2}, {3, 4}};
int[][] y = {{1, 2}, {3, 4}};
System.out.println(Arrays.deepEquals(x, y)); // true
4)copyOf()
和 copyOfRange()
– 数组复制
int[] arr = {1, 2, 3};
int[] copy = Arrays.copyOf(arr, 5); // 多余位置填 0: [1, 2, 3, 0, 0]
int[] part = Arrays.copyOfRange(arr, 1, 3); // 复制索引1到3(不含3):[2, 3]
5)fill()
– 填充数组
int[] arr = new int[5];
Arrays.fill(arr, 7); // 所有元素变成 7: [7, 7, 7, 7, 7]
也可以指定范围:
Arrays.fill(arr, 1, 4, 99); // 填充索引 1~3(不含4):[7, 99, 99, 99, 7]
6)toString()
– 数组转字符串
int[] arr = {1, 2, 3};
System.out.println(Arrays.toString(arr)); // [1, 2, 3]
二维数组使用 deepToString()
:
int[][] matrix = {{1,2}, {3,4}};
System.out.println(Arrays.deepToString(matrix)); // [[1, 2], [3, 4]]
7)asList()
– 将数组转为 List(固定长度)
String[] array = {"A", "B", "C"};
List<String> list = Arrays.asList(array); // 支持列表操作
list.set(1, "Z"); // 修改元素
System.out.println(list); // [A, Z, C]
// list.add("D"); // 会抛异常:UnsupportedOperationException
返回的 List
是固定长度的,不支持 add/remove 操作。
8)parallelSort()
– 并行排序(Java 8+)
int[] largeArray = new int[1000000];
// 填充数据...
Arrays.parallelSort(largeArray);
它使用多线程对大数组排序,速度比 sort()
更快(当数据量大时),但小数组效果不明显。
10.4 Arrays 工具类的使用建议
场景 | 建议方法 |
---|---|
简单排序 | Arrays.sort() |
大数据排序(Java 8+) | Arrays.parallelSort() |
拷贝数组 | Arrays.copyOf() |
找元素(已排序) | Arrays.binarySearch() |
填充默认值 | Arrays.fill() |
调试输出数组 | Arrays.toString() |
10.5 与集合框架联动
可以通过 Arrays.asList()
将数组转为 List
,适配 Java 集合操作:
String[] arr = {"a", "b", "c"};
List<String> list = Arrays.asList(arr);
Collections.reverse(list); // 反转
System.out.println(list); // [c, b, a]
10.6 常见注意事项
注意点 | 说明 |
---|---|
asList() 返回的是固定长度 List | 不能添加/删除元素 |
copyOf() 目标长度不够会截断,过长会补零 | |
binarySearch() 必须在排序后使用 | |
排序为原地排序(会修改原数组) |
10.7 小结
方法 | 说明 |
---|---|
sort() | 升序排序 |
parallelSort() | 多线程排序(Java 8+) |
binarySearch() | 二分查找(必须排序) |
equals() | 判断是否内容相等 |
copyOf() | 拷贝数组 |
fill() | 填充数组 |
toString() | 转字符串 |
asList() | 数组转 List |
deepToString() | 多维数组转字符串 |
deepEquals() | 多维数组比较 |
十一、Collections 工具类
11.1 Collections
工具类简介
-
包名:
java.util.Collections
-
作用:提供对集合对象进行排序、查找、填充、线程安全化、反转、打乱顺序等操作的静态方法集合。
-
与
Arrays
类似:它是用于集合的工具类,而Arrays
是数组的工具类。
11.2 常用方法总览(分类整理)
功能分类 | 方法举例 |
---|---|
排序 | sort(List) 、reverseOrder() |
查找 | binarySearch(List, key) 、max() 、min() |
修改 | reverse() 、shuffle() 、fill() 、replaceAll() |
同步 | synchronizedList() 、synchronizedMap() 等 |
不可变集合 | unmodifiableList() 、singletonList() 等 |
线程安全集合 | synchronizedXxx() |
其他 | frequency() 、disjoint() 等 |
11.3 各方法详细讲解
1)排序:Collections.sort()
对 List
进行排序,要求元素实现了 Comparable
接口:
List<Integer> list = Arrays.asList(3, 1, 2);
Collections.sort(list);
System.out.println(list); // [1, 2, 3]
自定义排序规则(传入 Comparator
):
Collections.sort(list, (a, b) -> b - a); // 降序
System.out.println(list); // [3, 2, 1]
2)查找:binarySearch()
、max()
、min()
List<String> names = Arrays.asList("Bob", "Alice", "Tom");
Collections.sort(names); // 先排序
int idx = Collections.binarySearch(names, "Tom");
System.out.println(idx); // 查找 Tom 的索引
System.out.println(Collections.max(names)); // Tom(字典序最大)
System.out.println(Collections.min(names)); // Alice(最小)
3)修改内容
reverse()
:反转列表顺序
Collections.reverse(list);
shuffle()
:打乱顺序(常用于洗牌)
Collections.shuffle(list);
fill()
:将所有元素替换为指定值
Collections.fill(list, 99); // 全部改为 99
replaceAll()
:将某值替换为另一个值
Collections.replaceAll(list, 99, 0); // 把所有99替换为0
4)线程安全包装:synchronizedXxx()
将集合包装为线程安全版本:
List<String> safeList = Collections.synchronizedList(new ArrayList<>());
Map<String, String> safeMap = Collections.synchronizedMap(new HashMap<>());
注意:虽然线程安全,但遍历时仍需要外部同步锁:
synchronized (safeList) {
for (String s : safeList) {
// 遍历
}
}
5)不可变集合(只读集合)
List<String> readonly = Collections.unmodifiableList(new ArrayList<>());
readonly.add("X"); // 会抛出 UnsupportedOperationException
可用于 API 返回值,防止外部修改。
6)单元素集合:singletonList()
、singletonMap()
List<String> onlyOne = Collections.singletonList("hello");
Map<String, String> map = Collections.singletonMap("key", "value");
生成只能包含一个元素的集合。
7)工具方法
frequency()
:统计某个元素出现的次数
List<String> list = Arrays.asList("a", "b", "a", "c");
int freq = Collections.frequency(list, "a"); // 2
disjoint()
:判断两个集合是否没有交集
boolean result = Collections.disjoint(list1, list2);
11.4 实战场景
场景 | 方法 |
---|---|
快速排序 List | Collections.sort() |
洗牌算法 | Collections.shuffle() |
线程安全 List | Collections.synchronizedList() |
防止外部修改返回值 | Collections.unmodifiableList() |
查询最大/最小值 | Collections.max() / min() |
查找元素位置 | Collections.binarySearch() (排序后) |
11.5 注意事项
注意点 | 说明 |
---|---|
binarySearch() 必须排序后使用 | 否则结果不准确 |
synchronizedXxx() 不等于绝对线程安全 | 遍历时仍需加锁 |
unmodifiableXxx() 只是包装,不可修改 | 调用 add() 会抛异常 |
singletonList() 集合固定长度不可扩展 | 不支持添加多个元素 |
11.6 与 Arrays 工具类的对比
工具类 | 用于对象 | 常用操作 |
---|---|---|
Arrays | 数组 | 排序、复制、比较、查找等 |
Collections | 集合(List等) | 排序、线程安全、不可变、打乱等 |
11.7 小结
方法名 | 功能简介 |
---|---|
sort(List) | 升序排序 |
reverse(List) | 反转顺序 |
shuffle(List) | 打乱顺序 |
binarySearch(List, x) | 二分查找 |
max(List) / min() | 最大 / 最小值 |
replaceAll() | 替换所有匹配元素 |
frequency() | 某元素出现次数 |
disjoint() | 判断是否无交集 |
synchronizedList() | 转线程安全 List |
unmodifiableList() | 转只读 List |
十二、包装类
12.1 什么是包装类(Wrapper Class)
包装类是 Java 为 每个基本数据类型 提供的 类类型封装,用于将 基本类型转换为对象类型,以便在需要对象的场合中使用。
基本类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
12.2 为什么需要包装类
-
集合不能直接存储基本类型
Java 集合如ArrayList<int>
是不允许的,但ArrayList<Integer>
是可以的。 -
对象方法调用需要对象
基本类型没有方法,但包装类可以调用equals()
、compareTo()
等方法。 -
工具类支持
如:Collections.max(List<Integer>)
只能处理对象。 -
与泛型协同工作
Java 泛型只支持对象类型。
12.3 如何使用包装类
创建包装类对象
Integer a = new Integer(10); // 旧写法,不推荐
Integer b = Integer.valueOf(10); // 推荐
Double d = Double.valueOf(3.14);
Character ch = Character.valueOf('A');
12.4 自动装箱 / 自动拆箱(Java 5 引入)
自动装箱(Auto Boxing)
将基本类型 自动转换为对应的包装类对象。
int x = 5;
Integer obj = x; // 自动装箱:等同于 Integer.valueOf(x)
自动拆箱(Auto Unboxing)
将包装类对象 自动转换为基本类型。
Integer obj = 10;
int y = obj; // 自动拆箱:等同于 obj.intValue()
12.5 常用方法(以 Integer 举例)
Integer x = Integer.valueOf("123"); // 字符串转 Integer
int y = x.intValue(); // 拆箱为 int
String s = Integer.toString(456); // int 转字符串
int z = Integer.parseInt("789"); // 字符串转 int
类似的方法也适用于 Double.parseDouble()
, Boolean.parseBoolean()
等。
12.6 包装类中的常量与缓存
Integer 缓存机制
Integer a = 100;
Integer b = 100;
System.out.println(a == b); // true
Integer c = 200;
Integer d = 200;
System.out.println(c == d); // false
原因:
-
Java 缓存了 -128 到 127 的整数,所以这段范围内的包装类对象不会创建新对象,而是从缓存中取出同一个对象。
-
超出范围就会创建新的对象。
12.7 包装类的不可变性(Immutable)
包装类对象是不可变的,一旦创建,值就不能更改。
Integer x = 10;
x = x + 1; // 实际上创建了一个新的 Integer 对象
12.8 包装类和 ==
/ .equals()
的区别
Integer a = 128;
Integer b = 128;
System.out.println(a == b); // false,比较的是引用
System.out.println(a.equals(b)); // true,比较的是值
结论:
-
比较数值内容,使用
.equals()
-
==
比较的是对象引用地址,只有在 缓存区 -128~127 内才可能是 true
12.9 包装类使用场景总结
场景 | 示例 |
---|---|
集合中使用基本类型 | List<Integer> |
解析字符串为数字 | Integer.parseInt("12") |
比较两个数字大小 | Integer.compare(a, b) |
与泛型协同使用 | Map<String, Double> |
与 Stream/Optional 协作 | Optional<Integer> |
12.10 八个包装类概览
包装类 | 常用静态方法 | 特点 / 注意事项 |
---|---|---|
Integer | valueOf() , parseInt() | 有缓存区 -128~127 |
Double | valueOf() , parseDouble() | 没有缓存 |
Boolean | parseBoolean() , valueOf() | Boolean.TRUE/FALSE 单例 |
Character | isDigit() , isLetter() | 方法用于判断字符类型 |
Long | valueOf() , parseLong() | 也有缓存(默认 -128~127) |
Float | valueOf() , parseFloat() | 没有缓存 |
Short | valueOf() , parseShort() | 有缓存 |
Byte | valueOf() , parseByte() | 有缓存 |
12.11 与基本类型的区别对比
对比项 | 基本类型 int | 包装类 Integer |
---|---|---|
是否是对象 | 否 | 是 |
是否可为空 | 否 | 可以为 null |
占用内存 | 少 | 多(有对象开销) |
支持泛型/集合 | 否 | 是 |
默认值 | 0 | null |
十三、自动装箱与拆箱
13.1 什么是自动装箱与自动拆箱?
这是 Java 从 JDK 1.5(Java 5) 引入的特性,目的是让 基本数据类型与包装类之间的转换更自然、更简洁,从而方便与集合、泛型等机制协作。
名称 | 作用 |
---|---|
自动装箱 | 把 基本类型 ➜ 包装类对象 |
自动拆箱 | 把 包装类对象 ➜ 基本类型 |
13.2 基本使用示例
自动装箱
int num = 10;
Integer obj = num; // 自动装箱,相当于:Integer obj = Integer.valueOf(num);
自动拆箱
Integer obj = 20;
int num = obj; // 自动拆箱,相当于:int num = obj.intValue();
13.3 装箱与拆箱的实际过程
操作代码 | 实际编译后的代码 |
---|---|
Integer x = 1; | Integer x = Integer.valueOf(1); |
int y = x; | int y = x.intValue(); |
自动装箱就是调用包装类的静态方法 valueOf()
;
自动拆箱就是调用包装类实例的方法 xxxValue()
。
13.4 自动装箱 & 拆箱在表达式中的表现
Integer a = 100;
Integer b = 100;
System.out.println(a == b); // true(缓存范围内)
Integer c = 200;
Integer d = 200;
System.out.println(c == d); // false(超出缓存)
System.out.println(c == 200); // true,c 自动拆箱为 int 后比较
- 当
c == 200
:包装类Integer
会拆箱为int
,与int
进行数值比较。
13.5 自动拆箱带来的潜在问题(空指针)
注意!拆箱 null 会抛异常:
Integer obj = null;
int num = obj; // NullPointerException!
原因:尝试执行 obj.intValue()
,而 obj
是 null
,所以抛出异常。
13.6 在集合与泛型中的应用
集合(如 List
)不能存储基本类型,但可以通过自动装箱存储包装类对象:
List<Integer> list = new ArrayList<>();
list.add(1); // 自动装箱为 Integer.valueOf(1)
int x = list.get(0); // 自动拆箱为 int
13.7 自动装箱缓存机制(Integer 缓存池)
Integer 缓存池范围:-128 ~ 127
Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true
Integer x = 128;
Integer y = 128;
System.out.println(x == y); // false
-
Integer.valueOf(int i)
会使用缓存池:-128 ~ 127
范围内直接返回缓存对象。 -
超出范围会创建新对象,因此不是同一个引用。
这也解释了为什么 ==
比较时结果不一致。
13.8 自动装箱的性能问题
装箱会创建对象,在高频操作中会影响性能。
public static void main(String[] args) {
long start = System.nanoTime();
Long sum = 0L; // Long 类型:会频繁装箱拆箱
for (long i = 0; i < 1_000_000; i++) {
sum += i;
}
long end = System.nanoTime();
System.out.println("耗时: " + (end - start) / 1e6 + " ms");
}
优化建议: 使用基本类型:
long sum = 0L; // 更快更轻
13.9 总结对比
特性 | 自动装箱 | 自动拆箱 |
---|---|---|
转换方向 | 基本类型 ➜ 包装类对象 | 包装类对象 ➜ 基本类型 |
原理方法 | valueOf() | xxxValue() (如 intValue) |
应用场景 | 集合、泛型、方法参数 | 运算表达式、返回值 |
可能问题 | 创建过多对象,影响性能 | 空对象拆箱抛出 NullPointerException |
缓存机制 | 有(Integer 等) | 无 |