异常、泛型、集合体系、Stream 流 是从 "只会写 HelloWorld" 到 "能写实用程序" 的关键一步。
目录
[1.1 什么是异常?](#1.1 什么是异常?)
[1.2 异常的核心作用](#1.2 异常的核心作用)
[1.3 自定义异常](#1.3 自定义异常)
[1.4 异常的 3 种处理方案](#1.4 异常的 3 种处理方案)
[示例 1:try-catch(最常用)](#示例 1:try-catch(最常用))
[示例 2:throws(甩锅)](#示例 2:throws(甩锅))
[2.1 什么是泛型?](#2.1 什么是泛型?)
[2.2 泛型类 & 泛型接口](#2.2 泛型类 & 泛型接口)
[2.3 泛型方法 & 通配符](#2.3 泛型方法 & 通配符)
[2.4 泛型与包装类](#2.4 泛型与包装类)
[三、Collection 集合:](#三、Collection 集合:)
[3.1 Collection 体系](#3.1 Collection 体系)
[3.2 Collection 常用方法](#3.2 Collection 常用方法)
[3.3 三种遍历方式](#3.3 三种遍历方式)
[3.4 并发修改异常](#3.4 并发修改异常)
[四、List 集合:](#四、List 集合:)
[4.1 List 核心特点](#4.1 List 核心特点)
[List 特有方法(基于索引)](#List 特有方法(基于索引))
[4.2 ArrayList 底层原理](#4.2 ArrayList 底层原理)
[4.3 LinkedList 底层原理](#4.3 LinkedList 底层原理)
[4.4 ArrayList vs LinkedList](#4.4 ArrayList vs LinkedList)
[五、Set 集合:](#五、Set 集合:)
[5.1 Set 核心特点](#5.1 Set 核心特点)
[5.2 HashSet 底层原理](#5.2 HashSet 底层原理)
[5.3 LinkedHashSet](#5.3 LinkedHashSet)
[5.4 TreeSet](#5.4 TreeSet)
[六、Map 集合:](#六、Map 集合:)
[6.1 Map 核心特点](#6.1 Map 核心特点)
[6.2 Map 常用方法](#6.2 Map 常用方法)
[6.3 三种遍历方式](#6.3 三种遍历方式)
[6.4 Map 常见实现类](#6.4 Map 常见实现类)
[七、Stream 流:](#七、Stream 流:)
[7.1 什么是 Stream 流?](#7.1 什么是 Stream 流?)
[7.2 获取 Stream 流](#7.2 获取 Stream 流)
[7.3 中间方法](#7.3 中间方法)
[7.4 终结方法](#7.4 终结方法)
[综合示例:筛选长度 > 2 的字符串,去重后排序](#综合示例:筛选长度 > 2 的字符串,去重后排序)
[8.1 可变参数](#8.1 可变参数)
[8.2 Collections 工具类](#8.2 Collections 工具类)
一、异常:
1.1 什么是异常?
- 异常就是程序运行时的 "意外状况" (比如除数为 0、数组越界、文件找不到)。Java 把这些 "意外" 封装成Throwable对象,分成两大类:
| 类型 | 说明 | 例子 |
|---|---|---|
| Error | 严重错误,程序无法处理(只能避免) | 内存溢出(OOM)、系统崩溃 |
| Exception | 普通异常,程序可以捕获并处理 | 空指针、数组越界、类型转换错误 |
Exception 又分两种:
- 编译时异常 :写代码时就报错,必须处理(比如IOException);
- 运行时异常 :编译不报错,运行才出问题(比如NullPointerException)。
1.2 异常的核心作用
- **没有异常处理:**程序遇到错误直接崩溃,用户体验差;
- **有异常处理:**捕获错误、给出提示、让程序继续运行,甚至自动修复。
1.3 自定义异常
Java 自带的异常不够用?比如 "年龄输入负数",可以自己造异常:
java
// 步骤1:继承RuntimeException(运行时异常)或Exception(编译时异常)
public class AgeIllegalException extends RuntimeException {
// 无参构造
public AgeIllegalException() {}
// 带错误信息的构造(常用)
public AgeIllegalException(String message) {
super(message); // 把信息传给父类
}
}
// 步骤2:使用自定义异常
public class Test {
public static void setAge(int age) {
if (age < 0 || age > 120) {
// 手动抛出异常
throw new AgeIllegalException("年龄必须在0-120之间!");
}
System.out.println("年龄设置成功:" + age);
}
public static void main(String[] args) {
setAge(-5); // 运行时抛出AgeIllegalException
}
}
1.4 异常的 3 种处理方案
| 方案 | 语法 | 适用场景 |
|---|---|---|
| try-catch | 捕获并处理异常 | 想自己处理错误(比如给用户提示) |
| throws | 声明抛出,甩给调用者 | 当前方法不想处理,让上层处理 |
| throw | 手动抛出异常对象 | 满足特定条件时主动报错 |
示例 1:try-catch(最常用)
java
public class TryCatchDemo {
public static void main(String[] args) {
try {
// 可能出错的代码
int a = 10 / 0;
} catch (ArithmeticException e) {
// 捕获到异常后的处理
System.out.println("除数不能为0!");
e.printStackTrace(); // 打印错误详情(调试用)
} finally {
// 无论是否出错,必执行(比如关闭文件/释放资源)
System.out.println("程序继续运行~");
}
}
}
示例 2:throws(甩锅)
java
import java.text.ParseException;
import java.text.SimpleDateFormat;
public class ThrowsDemo {
// 声明抛出编译时异常,让调用者处理
public static void parseDate(String str) throws ParseException {
new SimpleDateFormat("yyyy-MM-dd").parse(str);
}
public static void main(String[] args) {
try {
parseDate("2024-01-01"); // 调用者必须处理异常
} catch (ParseException e) {
System.out.println("日期格式错了!");
}
}
}
二、泛型:
2.1 什么是泛型?
- 泛型就是 "类型参数",比如给集合指定 "只能存字符串"。
核心好处:
- **类型安全:**编译时就检查类型,避免存错数据;
- **不用强转:**取出数据直接用,不用手动转换;
- **代码复用:**一套代码支持多种类型。
对比一下就懂了:
java
// 不用泛型:可能存错类型,取出还要强转
List list1 = new ArrayList();
list1.add("Java");
list1.add(123); // 能存整数,编译不报错
String s1 = (String) list1.get(0); // 必须强转
// 用泛型:指定只能存String,编译时检查
List<String> list2 = new ArrayList<>();
list2.add("Java");
// list2.add(123); // 编译报错,不能存整数
String s2 = list2.get(0); // 直接用,不用强转
2.2 泛型类 & 泛型接口
泛型类:类名后加**<T>**
(T 是类型参数,随便起名)
java
// 泛型类:万能盒子,能装任意类型
public class Box<T> {
private T content; // 用T表示任意类型
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
// 使用泛型类
public class Test {
public static void main(String[] args) {
Box<String> strBox = new Box<>();
strBox.setContent("Hello");
System.out.println(strBox.getContent()); // Hello
Box<Integer> intBox = new Box<>();
intBox.setContent(100);
System.out.println(intBox.getContent()); // 100
}
}
泛型接口:接口名后加**<T>**
java
// 泛型接口
public interface Generator<T> {
T generate(); // 返回T类型的数据
}
// 实现接口时指定具体类型
public class StringGenerator implements Generator<String> {
@Override
public String generate() {
return "Java";
}
}
2.3 泛型方法 & 通配符
泛型方法:
- 返回值前加**<T>**,独立于类的泛型
java
public class GenericMethodDemo {
// 泛型方法:打印任意类型的数组
public static <T> void printArray(T[] arr) {
for (T t : arr) {
System.out.println(t);
}
}
public static void main(String[] args) {
String[] strs = {"Java", "Python"};
Integer[] ints = {1, 2, 3};
printArray(strs); // 自动推断类型为String
printArray(ints); // 自动推断类型为Integer
}
}
通配符:
- **?**表示任意类型,配合上下限更灵活
- ? extends T:只能是 T 或 T 的子类(上限);
- ? super T:只能是 T 或 T 的父类(下限)。
java
public class WildcardDemo {
// 上限:只能传Number或其子类(Integer、Double)
public static void printNum(List<? extends Number> list) {
for (Number num : list) {
System.out.println(num);
}
}
public static void main(String[] args) {
List<Integer> intList = new ArrayList<>();
intList.add(10);
printNum(intList); // 合法,Integer是Number的子类
}
}
2.4 泛型与包装类
- Java 的基本类型(int、double)不是对象,包装类 就是把基本类型 "包成对象",泛型只能用包装类(++不能用基本类型++):
| 基本类型 | 包装类 | 自动装箱(基本→包装) | 自动拆箱(包装→基本) |
|---|---|---|---|
| int | Integer | Integer i = 10; | int j = i; |
| double | Double | Double d = 3.14; | double f = d; |
| char | Character | Character c = 'a'; | char ch = c; |
java
// 泛型中只能用包装类,不能用int
List<Integer> list = new ArrayList<>();
list.add(10); // 自动装箱:int→Integer
int num = list.get(0); // 自动拆箱:Integer→int
三、Collection 集合:
3.1 Collection 体系
- Collection 是单列集合的根接口,所有单列集合都继承它,主要分两支:
java
Collection
├─ List(有序、可重复、有索引)
│ ├─ ArrayList(底层数组)
│ └─ LinkedList(底层链表)
└─ Set(无序、不可重复、无索引)
├─ HashSet(底层哈希表)
├─ LinkedHashSet(哈希表+链表)
└─ TreeSet(底层红黑树)
3.2 Collection 常用方法
所有单列集合都能用这些方法:
java
public class CollectionDemo {
public static void main(String[] args) {
Collection<String> coll = new ArrayList<>();
coll.add("Java"); // 添加元素
coll.add("Python");
coll.remove("Python"); // 删除元素
System.out.println(coll.contains("Java")); // 判断是否包含:true
System.out.println(coll.size()); // 集合大小:1
coll.clear(); // 清空集合
System.out.println(coll.isEmpty()); // 是否为空:true
}
}
3.3 三种遍历方式
| 遍历方式 | 代码示例 | 核心特点 |
|---|---|---|
| 迭代器 | Iterator<String> it = coll.iterator();while(it.hasNext()) { String s = it.next();} | 可遍历中删除元素(it.remove ()) |
| 增强 for | for (String s : coll) { System.out.println(s);} | 代码简洁,不能删除元素 |
| Lambda | coll.forEach(s -> System.out.println(s)); | JDK8+,最简洁 |
迭代器示例(唯一能遍历中删除的方式)
java
Collection<String> coll = new ArrayList<>();
coll.add("Java");
coll.add("Python");
Iterator<String> it = coll.iterator();
while (it.hasNext()) {
String s = it.next();
if ("Python".equals(s)) {
it.remove(); // 迭代器删除,不会报错
}
}
System.out.println(coll); // [Java]
3.4 并发修改异常
- 如果在增强 for / 迭代器遍历中,用集合的 remove () 删除元素 ,会抛出ConcurrentModificationException:
java
// 错误示例:增强for中用coll.remove()
for (String s : coll) {
if ("Python".equals(s)) {
coll.remove(s); // 抛异常!
}
}
// 解决方法:用迭代器的remove() 或 普通for循环
四、List 集合:
4.1 List 核心特点
- **有序:**存进去的顺序和取出来的顺序一致;
- **可重复:**能存相同的元素;
- **有索引:**可以通过索引(下标)操作元素。
List 特有方法(基于索引)
java
List<String> list = new ArrayList<>();
list.add(0, "Java"); // 索引0添加元素
list.set(0, "Go"); // 修改索引0的元素
list.get(0); // 获取索引0的元素
list.remove(0); // 删除索引0的元素
4.2 ArrayList 底层原理
- 底层是数组(查询快、增删慢);
- 默认初始容量 10,满了后扩容为原来的 1.5 倍;
- 适用场景:频繁查询、少增删(比如查学生列表)。
4.3 LinkedList 底层原理
- 底层是双向链表(增删快、查询慢);
- 特有方法:addFirst()、addLast()、getFirst()(适合做栈 / 队列);
- 适用场景:频繁增删、少查询(比如购物车增删商品)。
4.4 ArrayList vs LinkedList
| 特性 | ArrayList | LinkedList |
|---|---|---|
| 底层结构 | 数组 | 双向链表 |
| 查询效率 | 快(O (1)) | 慢(O (n)) |
| 增删效率 | 慢(O (n)) | 快(O (1)) |
| 内存占用 | 连续空间,有浪费 | 节点存储指针,无浪费 |
| 适用场景 | 查多改少 | 改多查少 |
五、Set 集合:
5.1 Set 核心特点
- **无序:**存进去的顺序和取出来的可能不一样;
- **不可重复:**不能存相同的元素;
- **无索引:**不能用索引操作元素。
5.2 HashSet 底层原理
- 底层是哈希表(JDK8 后:数组 + 链表 + 红黑树);
- 去重依赖:**hashCode() + equals()**方法;
- 自定义对象去重:必须重写这两个方法!
自定义对象去重示例
java
// 学生类:重写hashCode和equals
public class Student {
private String name;
private int age;
// 构造器、get/set省略
// 重写hashCode:根据name和age生成哈希值
@Override
public int hashCode() {
return name.hashCode() + age * 31;
}
// 重写equals:比较name和age是否相同
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Student s = (Student) obj;
return age == s.age && name.equals(s.name);
}
}
// 测试去重
public class Test {
public static void main(String[] args) {
Set<Student> set = new HashSet<>();
set.add(new Student("张三", 18));
set.add(new Student("张三", 18)); // 重复,不会存入
System.out.println(set.size()); // 1
}
}
5.3 LinkedHashSet
- 是HashSet 的子类,底层哈希表 + 双向链表;
- **特点:**有序(存啥顺序取啥顺序)+ 不可重复;
- 适用场景:需要去重又要保留顺序。
5.4 TreeSet
- 底层是红黑树,自动排序;
- 排序方式:
- 自然排序:元素实现Comparable接口;
- 自定义排序:创建 TreeSet 时传入Comparator。
java
// 自定义排序示例:按年龄排序
Set<Student> set = new TreeSet<>((s1, s2) -> {
// 年龄小的在前,年龄相同按姓名
int res = s1.getAge() - s2.getAge();
return res == 0 ? s1.getName().compareTo(s2.getName()) : res;
});
六、Map 集合:
6.1 Map 核心特点
- 存储键值对(Key-Value),比如 "姓名 - 年龄";
- **键唯一:**一个键只能对应一个值(重复键会覆盖旧值);
- **值可重复:**不同键可以对应相同的值;
- **无索引:**不能用索引操作,只能通过键找值。
6.2 Map 常用方法
java
Map<String, Integer> map = new HashMap<>();
map.put("张三", 18); // 添加键值对
map.put("李四", 20);
map.put("张三", 25); // 重复键,覆盖为25
System.out.println(map.get("张三")); // 获取值:25
map.remove("李四"); // 删除键值对
System.out.println(map.containsKey("张三")); // 是否包含键:true
System.out.println(map.size()); // 大小:1
6.3 三种遍历方式
java
Map<String, Integer> map = new HashMap<>();
map.put("张三", 18);
map.put("李四", 20);
// 方式1:键找值
Set<String> keys = map.keySet();
for (String key : keys) {
Integer value = map.get(key);
System.out.println(key + ":" + value);
}
// 方式2:遍历键值对对象
Set<Map.Entry<String, Integer>> entries = map.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
// 方式3:Lambda(最简洁)
map.forEach((key, value) -> System.out.println(key + ":" + value));
6.4 Map 常见实现类
| 实现类 | 特点 | 底层结构 |
|---|---|---|
| HashMap | 无序、键唯一、允许 null 键 / 值 | 哈希表 |
| LinkedHashMap | 有序(存啥顺序取啥顺序)、键唯一 | 哈希表 + 双向链表 |
| TreeMap | 键自动排序、键唯一 | 红黑树 |
七、Stream 流:
7.1 什么是 Stream 流?
- Stream 流是 JDK8 新增的,核心作用是简化集合操作(筛选、排序、去重等),不用写嵌套循环,代码更简洁。
7.2 获取 Stream 流
java
// 1. 集合获取流
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
// 2. 数组获取流
String[] arr = {"Java", "Python"};
Stream<String> stream2 = Arrays.stream(arr);
// 3. 零散数据获取流
Stream<String> stream3 = Stream.of("Java", "Python", "Go");
7.3 中间方法
(可链式调用,返回新流)
| 方法 | 作用 | 示例 |
|---|---|---|
| filter | 筛选元素 | stream.filter(s -> s.length()>2) |
| distinct | 去重 | stream.distinct() |
| sorted | 排序 | stream.sorted() |
| map | 转换元素类型 | stream.map(String::length) |
| limit | 取前 n 个元素 | stream.limit(2) |
| skip | 跳过前 n 个元素 | stream.skip(1) |
7.4 终结方法
(触发计算,返回非流)
| 方法 | 作用 | 示例 |
|---|---|---|
| forEach | 遍历 | stream.forEach(System.out::println) |
| count | 统计个数 | stream.count() |
| collect | 收集到集合 | stream.collect(Collectors.toList()) |
综合示例:筛选长度 > 2 的字符串,去重后排序
java
List<String> list = Arrays.asList("Java", "Python", "Java", "Go", "C++");
List<String> newList = list.stream()
.filter(s -> s.length() > 2) // 筛选长度>2
.distinct() // 去重
.sorted() // 排序
.collect(Collectors.toList()); // 收集到新集合
System.out.println(newList); // [C++, Java, Python]
八、前置知识:
8.1 可变参数
- 允许方法接收任意数量的同类型参数 ,语法Type... args(本质是数组):
java
// 求任意个数整数的和
public static int sum(int... nums) {
int total = 0;
for (int num : nums) {
total += num;
}
return total;
}
public static void main(String[] args) {
System.out.println(sum(1, 2)); // 3
System.out.println(sum(1, 2, 3, 4)); // 10
}
注意: ++可变参数必须是方法最后一个参数,一个方法只能有一个可变参数。++
8.2 Collections 工具类
专门操作集合的工具类,全是静态方法:
java
List<Integer> list = new ArrayList<>();
Collections.addAll(list, 5, 2, 8); // 批量添加
Collections.sort(list); // 排序:[2,5,8]
Collections.shuffle(list); // 打乱顺序
System.out.println(Collections.max(list)); // 最大值:8
核心要点总结
- 异常 :用 try-catch处理错误,自定义异常适配业务场景,让程序不崩溃;
- 泛型:指定类型参数,保证类型安全,避免强转,代码复用;
- 集合 :List 有序可重复、Set无序不可重复、Map 存键值对,根据场景选实现类;
- Stream 流:简化集合操作,链式调用更优雅;
- 工具类 :可变参数处理多参数,Collections 简化集合操作。