十三、Java入门进阶:异常、泛型、集合与 Stream 流

异常、泛型、集合体系、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 什么是泛型?

  • 泛型就是 "类型参数",比如给集合指定 "只能存字符串"。

核心好处:

  1. **类型安全:**编译时就检查类型,避免存错数据;
  2. **不用强转:**取出数据直接用,不用手动转换;
  3. **代码复用:**一套代码支持多种类型。

对比一下就懂了:

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
    }
}
通配符:
  • **?**表示任意类型,配合上下限更灵活
  1. ? extends T:只能是 T 或 T 的子类(上限);
  2. ? 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

  • 底层是红黑树,自动排序;
  • 排序方式:
    1. 自然排序:元素实现Comparable接口;
    2. 自定义排序:创建 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

核心要点总结

  1. 异常 :用 try-catch处理错误,自定义异常适配业务场景,让程序不崩溃;
  2. 泛型:指定类型参数,保证类型安全,避免强转,代码复用;
  3. 集合List 有序可重复、Set无序不可重复、Map 存键值对,根据场景选实现类;
  4. Stream 流:简化集合操作,链式调用更优雅;
  5. 工具类 :可变参数处理多参数,Collections 简化集合操作。
相关推荐
Maggie_ssss_supp1 小时前
Linux-python
开发语言·python
百锦再2 小时前
Java Map常用方法和实现类深度详解
java·开发语言·spring boot·struts·kafka·tomcat·maven
_codemonster2 小时前
JavaWeb开发系列(九)idea配置jdbc
java·ide·intellij-idea
Hx_Ma162 小时前
测试题(六)
java·tomcat·mybatis
人道领域2 小时前
SpringBoot vs SpringMVC:以及SpringBoot的全流程开发(1)
java·spring boot·spring
元亓亓亓2 小时前
LeetCode热题100--41. 缺失的第一个正数--困难
数据结构·算法·leetcode
码云数智-大飞2 小时前
.NET 10 & C# 14 新特性详解:扩展成员 (Extension Members) 全面指南
java·数据库·算法
weixin_477271692 小时前
狗象:与强大的一方建立联系,并控制调用对方的力量。)马王堆帛书《周易》原文及甲骨文还原周朝生活现象《函谷门
算法·图搜索算法
Anastasiozzzz2 小时前
阿亮随手录-SpringBoot启动流程、三级缓存要求、BeanFactory与FactoryBean、AutoWired与Resource、不推荐字段注入
java·spring