分类 JavaSE基础加强 下的文章

本文包含异常概念、体系分类、处理方式、自定义异常四大部分。

一、什么是异常

异常就是Java程序在编译或运行过程中出现的错误,会导致程序非正常终止。

开发中最常见的异常场景:

  1. 数组索引越界:访问了数组不存在的下标
  2. 算术异常:除数为0
  3. 空指针异常:调用null对象的方法
  4. 文件不存在:读取本地不存在的文件
// 常见运行时异常示例
public class ExceptionTest {
    public static void main(String[] args) {
        int[] arr = {10, 20, 30};
        System.out.println(arr[3]); // 数组索引越界异常
        System.out.println(10 / 0); // 算术异常
        String str = null;
        System.out.println(str.length()); // 空指针异常
    }
}

异常的核心作用

  1. 定位BUG:异常信息会精准打印错误位置和原因,是调试程序的关键
  2. 流程通知:作为方法的特殊返回值,告诉调用者方法执行失败的原因

二、Java异常完整体系

Java中所有异常和错误的根类是 java.lang.Throwable,整体分为两大分支:

1. Error(系统级错误)

属于JVM底层的严重问题(如内存溢出、系统崩溃),也无法通过代码处理。

2. Exception(程序异常,开发核心处理对象)

程序运行/编译中可预见的错误,我们所有异常处理都围绕这个类展开,又细分为运行时异常编译时异常

三、异常的两大分类

1. 运行时异常(RuntimeException)

  • 定义:RuntimeException 及其所有子类
  • 特点:编译阶段不报错,运行时才触发
  • 常见示例:数组越界、空指针、算术异常、类型转换异常

2. 编译时异常(受检异常)

  • 定义:非 RuntimeException 的Exception子类
  • 特点:编译阶段强制报错,必须处理才能运行
  • 常见示例:日期解析异常、文件读取异常、IO流异常
// 编译时异常示例(必须处理,否则编译失败)
import java.text.SimpleDateFormat;
import java.util.Date;

public class CompileException {
    public static void main(String[] args) throws Exception {
        String time = "2024-07-09";
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
        Date date = sdf.parse(time); // 日期解析异常(编译时异常)
    }
}

四、异常的两种核心处理方式

Java提供了抛出异常捕获异常两种处理方案,开发中可根据场景灵活使用。

1. 抛出异常(throws)

  • 作用:方法内部不处理异常,抛给调用者处理
  • 语法:在方法声明后添加 throws 异常类名
  • 简化写法:直接抛出 Exception 接收所有异常
// 抛出异常示例
public class ThrowException {
    public static void main(String[] args) throws Exception {
        // 调用者接收异常,也可继续抛出
        testException();
    }

    // 方法抛出异常,交给上层处理
    public static void testException() throws Exception {
        int num = 10 / 0;
    }
}

2. 捕获异常(try...catch)

  • 作用:直接拦截并处理异常,保证程序不终止
  • 语法:

    try {
        // 可能出现异常的代码
    } catch (异常类型 变量名) {
        // 异常处理逻辑
    }
  • 简化写法:catch (Exception e) 捕获所有异常
// 捕获异常示例
public class TryCatchException {
    public static void main(String[] args) {
        try {
            int num = 10 / 0;
        } catch (Exception e) {
            e.printStackTrace(); // 打印异常堆栈信息
            System.out.println("程序出现异常,已处理!");
        }
        // 异常处理后,程序继续执行
        System.out.println("程序正常结束");
    }
}

五、自定义异常

Java内置的异常类无法覆盖所有业务场景(如年龄非法、手机号格式错误),此时需要自定义异常来管理业务错误。

自定义异常的两种类型

1. 自定义编译时异常

  • 继承:Exception
  • 特点:编译强制处理,提醒严格
// 自定义编译时异常
public class AgeIllegalException extends Exception {
    // 无参构造
    public AgeIllegalException() {}
    // 带异常信息的构造
    public AgeIllegalException(String message) {
        super(message);
    }
}

2. 自定义运行时异常

  • 继承:RuntimeException
  • 特点:编译不报错,运行触发,开发更常用
// 自定义运行时异常
public class AgeIllegalRuntimeException extends RuntimeException {
    public AgeIllegalRuntimeException() {}
    public AgeIllegalRuntimeException(String message) {
        super(message);
    }
}

自定义异常使用示例

public class CustomExceptionTest {
    public static void main(String[] args) {
        try {
            saveAge(300); // 传入非法年龄
        } catch (AgeIllegalException e) {
            e.printStackTrace();
            System.out.println("年龄校验失败!");
        }
    }

    // 业务方法:校验年龄并抛出自定义异常
    public static void saveAge(int age) throws AgeIllegalException {
        if(age < 1 || age > 200){
            throw new AgeIllegalException("年龄非法,范围必须是1-200");
        }
        System.out.println("年龄保存成功:" + age);
    }
}

六、企业级异常处理实践

  1. 分层抛出,统一捕获
    底层方法不处理异常,全部向上抛出,最外层统一捕获,打印日志并返回友好提示
  2. 异常重试修复
    针对用户输入错误等场景,捕获异常后引导用户重新输入,保证程序不中断
// 异常重试修复示例
import java.util.Scanner;

public class ExceptionRetry {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        while (true) {
            try {
                System.out.print("请输入商品价格:");
                double price = sc.nextDouble();
                System.out.println("价格设置成功:" + price);
                break;
            } catch (Exception e) {
                System.out.println("输入格式错误,请重新输入数字!");
                sc.next(); // 清空错误输入
            }
        }
    }
}

七、总结

  1. 异常根类 Throwable,分 Error(无需处理)和 Exception(开发处理)
  2. 异常分类:运行时异常(编译不报错)、编译时异常(编译强制处理)
  3. 处理方式:throws 抛出(上层处理)、try...catch 捕获(自行处理)
  4. 自定义异常:继承 Exception(编译时)/RuntimeException(运行时)
  5. 开发规范:底层抛异常,上层统一捕获,避免程序崩溃

Java集合HashSet与TreeSet

聚焦Set集合两大核心实现类HashSetTreeSet,梳理核心特性、底层数据结构、实战用法及避坑要点。

一、Set集合基础特性

Set是Java单列集合Collection的子接口,全系列通用三大核心特性

  1. 不重复:集合中无法存储相同元素
  2. 无索引:不支持通过下标获取元素,无法普通for循环遍历
  3. 无独有方法:常用API全部继承自Collection接口

本文仅讲解最常用的两个实现类:HashSet(无序去重)、TreeSet(排序去重)。


二、HashSet 详解

2.1 核心特点

  • 无序:元素添加顺序 ≠ 遍历输出顺序
  • 不重复、无索引(Set通用特性)
  • 增删改查性能优异,是日常开发最常用的Set实现类

2.2 底层原理:哈希表

HashSet的高性能源于哈希表数据结构,JDK版本不同结构有差异:

  • JDK 8之前:数组 + 单向链表
  • JDK 8及以后:数组 + 链表 + 红黑树(优化查询性能)

关键概念:哈希值

  1. 所有Java对象均可调用Object.hashCode()获取int类型哈希码
  2. 特性:同一对象多次调用返回值相同;不同对象可能哈希值相同(哈希碰撞)

元素存储完整流程

  1. 初始化:创建默认长度16、加载因子0.75的数组table
  2. 计算位置:通过元素哈希值 % 数组长度算出存储下标
  3. 空位直接存:下标位置为null,直接存入元素
  4. 碰撞去重:位置非空时,调用equals()比较元素内容

    • 内容相同:判定为重复元素,不存储
    • 内容不同:形成链表,JDK8后新元素挂在链表尾部
  5. 树化优化:链表长度>8 且 数组长度≥64,链表自动转为红黑树
  6. 扩容机制:数组元素达到 16*0.75=12 时,自动扩容为原容量2倍

2.3 自定义对象去重(重点)

HashSet默认仅能对String、Integer等基础类型去重;自定义对象必须重写两个方法才能实现内容去重:

  1. hashCode():保证相同内容对象哈希值一致
  2. equals():判断对象内容是否相同

实战代码示例

  1. 实体类Student(重写核心方法)

    import java.util.Objects;
    
    public class Student {
     private String name;
     private int age;
     private String address;
     private String phone;
    
     // 无参、全参构造
     public Student() {}
     public Student(String name, int age, String address, String phone) {
         this.name = name;
         this.age = age;
         this.address = address;
         this.phone = phone;
     }
    
     // getter/setter省略
     public String getName() { return name; }
     public void setName(String name) { this.name = name; }
     public int getAge() { return age; }
     public void setAge(int age) { this.age = age; }
     public String getAddress() { return address; }
     public void setAddress(String address) { this.address = address; }
     public String getPhone() { return phone; }
     public void setPhone(String phone) { this.phone = phone; }
    
     // 重写equals:比较对象内容
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
         Student student = (Student) o;
         return age == student.age && Objects.equals(name, student.name)
                 && Objects.equals(address, student.address)
                 && Objects.equals(phone, student.phone);
     }
    
     // 重写hashCode:根据内容生成哈希值
     @Override
     public int hashCode() {
         return Objects.hash(name, age, address, phone);
     }
    
     // 重写toString:格式化输出
     @Override
     public String toString() {
         return "Student{name='" + name + "', age=" + age + "
                         , address='" + address + "', phone='" + phone + "'}";
     }
    }
  2. 测试类:HashSet去重效果

    import java.util.HashSet;
    import java.util.Set;
    
    public class HashSetTest {
     public static void main(String[] args) {
         Set<Student> studentSet = new HashSet<>();
         // 创建两个内容完全相同的对象
         Student s1 = new Student("张三", 18, "北京", "123456");
         Student s2 = new Student("张三", 18, "北京", "123456");
    
         studentSet.add(s1);
         studentSet.add(s2);
    
         // 输出仅1个元素,去重成功
         System.out.println(studentSet);
     }
    }

三、TreeSet 详解

3.1 核心特点

  • 可排序:默认对元素升序排列
  • 不重复、无索引(Set通用特性)
  • 排序基于红黑树实现,兼顾排序与性能

3.2 底层原理:红黑树

TreeSet底层完全基于红黑树(自平衡二叉排序树),特性:

  1. 自动排序,增删改查性能稳定
  2. 去重规则:排序比较结果为0 即判定为重复元素

3.3 默认排序规则

  • 数值类型(Integer/Double):按数值大小升序
  • 字符串类型:按字符ASCII码值升序
  • 自定义对象:无法直接排序,必须手动指定排序规则

3.4 自定义对象排序(两种方案)

TreeSet支持两种排序方式,比较器优先级 > 类自带排序

方案1:实体类实现Comparable接口

实体类重写compareTo()方法,定义默认排序规则

// 教师实体类:实现Comparable接口
public class Teacher implements Comparable<Teacher> {
    private String name;
    private int age;
    private double salary;

    // 无参、全参构造
    public Teacher() {}
    public Teacher(String name, int age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    // getter/setter省略
    public String getName() { return name; }
    public int getAge() { return age; }
    public double getSalary() { return salary; }

    // 核心:定义排序规则(按年龄升序)
    @Override
    public int compareTo(Teacher o) {
        // 正数:当前对象大;负数:当前对象小;0:重复
        return this.age - o.age;
    }

    @Override
    public String toString() {
        return "Teacher{name='" + name + "', age=" + age + ", salary=" + salary + "}";
    }
}

方案2:创建TreeSet时传入Comparator比较器

匿名内部类/Lambda表达式,灵活定义临时排序规则

import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

public class TreeSetTest {
    public static void main(String[] args) {
        // 方式1:使用类默认排序(年龄升序)
        Set<Teacher> teacherSet1 = new TreeSet<>();

        // 方式2:自定义比较器(薪资降序,Lambda简化)
        Set<Teacher> teacherSet2 = new TreeSet<>((o1, o2) -> Double.compare(o2.getSalary(), o1.getSalary()));

        // 添加元素
        teacherSet2.add(new Teacher("老陈", 20, 6232.4));
        teacherSet2.add(new Teacher("dlei", 18, 3999.5));
        teacherSet2.add(new Teacher("老王", 22, 9999.9));

        // 按薪资降序输出
        System.out.println(teacherSet2);
    }
}

四、HashSet vs TreeSet 选型指南

特性HashSetTreeSet
底层结构哈希表(数组+链表+红黑树)红黑树
顺序无序可排序(默认升序)
性能增删查最优(O(1))排序场景最优(O(logn))
去重依据hashCode()+equals()比较器返回值为0
适用场景通用去重、无排序需求元素排序、范围查询

五、学习总结

  1. HashSet:Java开发首选Set集合,核心是哈希表,自定义对象必须重写hashCode()equals()实现去重;
  2. TreeSet:专注排序场景,核心是红黑树,自定义对象需通过ComparableComparator指定排序规则;
  3. 通用原则:仅去重选HashSet,需要排序选TreeSet

Java基础:Collection与List集合

聚焦单列集合Collection及其子体系List,核心特性、常用API、遍历方式、底层原理与实战用法

一、集合基础概述

数组是Java基础容器,但长度固定,无法适配动态数据存储;而集合是Java提供的动态容器,长度可变,支持灵活的增删改查,是开发中最常用的数据存储工具。

Java集合分为两大核心体系:

  • 单列集合(Collection):每个元素仅存储一个值
  • 双列集合(Map):每个元素为键值对(key-value)

二、Collection 单列集合

2.1 集合体系分支

Collection 是所有单列集合的顶层接口,两大核心子接口:

  1. List 系列有序、可重复、有索引
    实现类:ArrayList、LinkedList
  2. Set 系列无序、不重复、无索引
    实现类:HashSet、TreeSet

2.2 基础特性演示

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class CollectionTest {
    public static void main(String[] args) {
        // List:有序、可重复、有索引
        List<String> list = new ArrayList<>();
        list.add("Java");
        list.add("Java");
        list.add("C++");
        System.out.println(list); // [Java, Java, C++]

        // Set:无序、不重复、无索引
        Set<String> set = new HashSet<>();
        set.add("Java");
        set.add("Java");
        set.add("C++");
        System.out.println(set); // [Java, C++]
    }
}

2.3 Collection 通用API

作为顶层接口,Collection 定义的方法所有单列集合通用,核心方法如下:

方法名功能说明
boolean add(E e)添加元素
void clear()清空集合
boolean remove(E e)删除指定元素
boolean contains(Object obj)判断是否包含指定元素
boolean isEmpty()判断集合是否为空
int size()获取元素个数
Object[] toArray()集合转数组

API 实战代码

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;

public class CollectionApiTest {
    public static void main(String[] args) {
        Collection<String> coll = new ArrayList<>();
        // 1. 添加元素
        coll.add("张三");
        coll.add("李四");
        coll.add("王五");
        System.out.println(coll); // [张三, 李四, 王五]

        // 2. 元素个数
        System.out.println(coll.size()); // 3

        // 3. 删除元素
        coll.remove("李四");
        System.out.println(coll); // [张三, 王五]

        // 4. 判断包含
        System.out.println(coll.contains("张三")); // true

        // 5. 集合转数组
        Object[] arr = coll.toArray();
        System.out.println(Arrays.toString(arr)); // [张三, 王五]
        
        // 6. 清空集合
        coll.clear();
        System.out.println(coll.isEmpty()); // true
    }
}

2.4 Collection 三种遍历方式

遍历是集合最常用操作,Java 提供3种标准遍历方案,仅遍历不修改时均可使用

方式1:迭代器遍历(集合专属)

迭代器 Iterator 是集合遍历的原生方式,核心方法:

  • hasNext():判断是否有下一个元素
  • next():获取当前元素并后移指针
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class IteratorTest {
    public static void main(String[] args) {
        Collection<String> coll = new ArrayList<>();
        coll.add("张无忌");
        coll.add("玄冥二老");
        coll.add("宋青书");

        // 获取迭代器
        Iterator<String> it = coll.iterator();
        // 循环遍历
        while (it.hasNext()) {
            String name = it.next();
            System.out.println(name);
        }
    }
}

⚠️ 注意:越界调用 next() 会抛出 NoSuchElementException 异常。

方式2:增强for循环(简化迭代器)

语法简洁,本质是迭代器的语法糖,支持集合+数组:

import java.util.ArrayList;
import java.util.Collection;

public class ForEachTest {
    public static void main(String[] args) {
        Collection<String> coll = new ArrayList<>();
        coll.add("张无忌");
        coll.add("玄冥二老");
        
        // 增强for遍历
        for (String name : coll) {
            System.out.println(name);
        }
    }
}

方式3:Lambda 表达式遍历(JDK8+)

极简写法,基于函数式编程:

import java.util.ArrayList;
import java.util.Collection;

public class LambdaTest {
    public static void main(String[] args) {
        Collection<String> coll = new ArrayList<>();
        coll.add("张无忌");
        coll.add("玄冥二老");
        
        // Lambda 遍历
        coll.forEach(System.out::println);
    }
}

2.5 并发修改异常解决方案

异常场景

遍历集合时,直接增删元素会触发 ConcurrentModificationException 并发修改异常。

核心解决方案(3种)

  1. 索引for循环:删除后 i-- 修正索引
  2. 倒序索引遍历:无需修正索引
  3. 迭代器自带 remove() 方法(推荐)

⚠️ 禁止:增强for、Lambda 遍历中增删元素

实战代码

import java.util.ArrayList;
import java.util.Iterator;

public class ModifyTest {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("Java入门");
        list.add("宁夏枸杞");
        list.add("黑枸杞");

        // 方案1:迭代器删除(无异常)
        Iterator<String> it = list.iterator();
        while (it.hasNext()) {
            String s = it.next();
            if (s.contains("枸杞")) {
                it.remove(); // 迭代器原生删除
            }
        }
        System.out.println(list); // [Java入门]
    }
}

三、List 集合(Collection 子接口)

List 继承 Collection,独有索引特性,是开发中使用频率最高的集合。

3.1 List 核心特性

  • 有序:元素存储顺序与取出顺序一致
  • 可重复:允许存储重复元素
  • 有索引:支持通过索引精准操作元素

3.2 List 独有索引API

List 新增索引相关方法,Collection 无此功能:

方法名功能说明
void add(int index, E e)指定索引插入元素
E remove(int index)删除指定索引元素,返回被删元素
E set(int index, E e)修改指定索引元素,返回原元素
E get(int index)获取指定索引元素

API 实战

import java.util.ArrayList;
import java.util.List;

public class ListApiTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("张三");
        list.add("李四");

        // 索引插入
        list.add(1, "赵敏");
        System.out.println(list); // [张三, 赵敏, 李四]

        // 索引删除
        list.remove(2);
        System.out.println(list); // [张三, 赵敏]

        // 索引修改+获取
        list.set(0, "金毛狮王");
        System.out.println(list.get(0)); // 金毛狮王
    }
}

3.3 List 四种遍历方式

在 Collection 三种遍历基础上,新增普通for索引遍历

  1. 普通for(索引)
  2. 迭代器
  3. 增强for
  4. Lambda
// 普通for索引遍历(List专属)
for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}

3.4 List 两大实现类对比

1. ArrayList(数组实现)

  • 底层:动态数组
  • 特性:查询快、增删慢(首尾增删除外)
  • 场景:高频查询、少量增删(开发首选)

2. LinkedList(双链表实现)

  • 底层:双向链表
  • 特性:查询慢、首尾增删极快
  • 场景:高频首尾操作、模拟队列/栈

3.5 LinkedList 实战:队列 & 栈

利用 LinkedList 首尾操作特性,快速实现经典数据结构:

  1. 队列:先进先出(FIFO)
  2. :后进先出(LIFO)
import java.util.LinkedList;

public class LinkedListTest {
    public static void main(String[] args) {
        // 1. 模拟队列
        LinkedList<String> queue = new LinkedList<>();
        queue.addLast("A"); // 入队
        queue.addLast("B");
        System.out.println(queue.removeFirst()); // 出队 A

        // 2. 模拟栈
        LinkedList<String> stack = new LinkedList<>();
        stack.push("1"); // 压栈
        stack.push("2");
        System.out.println(stack.pop()); // 弹栈 2
    }
}

四、总结

  1. Collection:单列集合顶层接口,通用增删查+3种遍历方式
  2. List:有序可重复有索引,独有索引API,4种遍历方式
  3. 实现类选型

    • 查多改少 → ArrayList
    • 首尾增删 → LinkedList
  4. 避坑指南:遍历增删用迭代器,禁止增强for/Lambda直接修改集合