学习笔记:异常,泛型,集合(代码示例,企业面试题,企业实际应用场景)

一、异常(Exception)

异常是 Java 处理程序运行时错误的核心机制,企业开发中要求精准捕获、合理抛出、规范处理,避免程序崩溃或隐藏问题。

1. 核心代码示例(分类处理 + 自定义异常)

java 复制代码
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

/**
 * 异常核心示例:
 * 1. 编译时异常(检查型)处理(try-catch/throws)
 * 2. 运行时异常(非检查型)处理
 * 3. 自定义业务异常
 */
// 自定义业务异常(企业中用于标识特定业务错误)
class BusinessException extends RuntimeException {
    // 带错误信息的构造方法
    public BusinessException(String message) {
        super(message);
    }
    // 带错误信息+根因的构造方法(便于排查问题)
    public BusinessException(String message, Throwable cause) {
        super(message, cause);
    }
}

public class ExceptionDemo {
    // 读取文件(抛出编译时异常,由调用方处理)
    public static void readFile(String path) throws IOException {
        if (path == null || !path.endsWith(".txt")) {
            // 抛出运行时异常(参数非法)
            throw new IllegalArgumentException("文件路径非法,必须是txt文件");
        }
        FileInputStream fis = new FileInputStream(path);
        fis.close();
    }

    // 处理业务逻辑(封装底层异常为业务异常)
    public static void doBusiness(String filePath) {
        try {
            readFile(filePath);
            System.out.println("文件读取成功,业务处理完成");
        } catch (IllegalArgumentException e) {
            // 捕获参数异常,包装为业务异常抛出(统一异常类型)
            throw new BusinessException("业务处理失败:参数错误", e);
        } catch (FileNotFoundException e) {
            throw new BusinessException("业务处理失败:文件不存在", e);
        } catch (IOException e) {
            throw new BusinessException("业务处理失败:文件读取异常", e);
        }
    }

    public static void main(String[] args) {
        // 测试异常处理
        try {
            // 测试1:参数错误(运行时异常)
            // doBusiness("test.doc");
            // 测试2:文件不存在(编译时异常)
            doBusiness("none.txt");
        } catch (BusinessException e) {
            // 统一捕获业务异常,打印错误信息和根因
            System.err.println("错误信息:" + e.getMessage());
            // 打印异常栈(排查问题用)
            e.printStackTrace();
        }
    }
}

代码说明

  • 异常分类 :编译时异常(IOException/FileNotFoundException)必须显式处理(try-catch/throws);运行时异常(IllegalArgumentException/NullPointerException)可不用显式处理,但企业中建议主动捕获。
  • 自定义异常 :企业中会为不同业务场景定义专属异常(如UserNotFoundExceptionOrderException),便于统一异常处理和前端错误提示。
  • 异常包装:底层异常(如 IO 异常)包装为业务异常抛出,避免上层感知底层实现细节,符合 "面向业务编程" 思想。

2. 企业面试题

  1. 问:编译时异常和运行时异常的区别?企业中如何处理?答:

    • 编译时异常(Checked Exception):继承Exception(非 RuntimeException),编译期强制处理(try-catch/throws),如IOExceptionSQLException
    • 运行时异常(Unchecked Exception):继承RuntimeException,编译期不强制处理,如NullPointerExceptionIndexOutOfBoundsException
    • 企业处理原则:编译时异常要么捕获处理,要么抛给上层并说明文档;运行时异常优先预防(如判空),关键节点主动捕获,避免程序崩溃。
  2. 问:try-catch-finally 中,finally 一定会执行吗?return 在 finally 前执行会怎样?答:finally几乎 一定会执行,除非 JVM 退出(如System.exit(0));若 try/catch 中有 return,会先执行 return 的表达式计算,再执行 finally,最后返回(若 finally 中有 return,会覆盖原 return 值,企业中禁止这么做)。

  3. 问:throw 和 throws 的区别?答:throw是语句,手动抛出具体异常对象(如throw new BusinessException("错误"));throws是方法声明,声明该方法可能抛出的异常类型,告知调用方需要处理。

  4. 问:企业中为什么要自定义异常?答:统一异常类型,便于全局异常处理(如 Spring 的@ControllerAdvice);区分不同业务错误场景,方便定位问题;可携带业务错误码 / 信息,适配前端错误提示。

3. 企业实际应用场景

  1. 全局异常处理 :Spring Boot 项目中用@ControllerAdvice+@ExceptionHandler统一捕获所有异常,返回标准化错误响应(错误码 + 错误信息)。
  2. 业务流程校验 :下单时若库存不足,抛出StockInsufficientException;登录时若用户名密码错误,抛出AuthFailedException
  3. 资源操作容错 :数据库操作(SQLException)、文件操作(IOException)捕获后,记录日志并转换为业务异常,避免暴露底层敏感信息。
  4. 参数校验 :接口入参校验失败时,抛出IllegalArgumentException或自定义ParamValidException,配合校验框架(如 Hibernate Validator)使用。

二、泛型(Generic)

泛型是 JDK 5 + 特性,核心作用是 "类型参数化",避免类型强转,提升代码复用性和类型安全性,是集合、框架的基础。

1. 核心代码示例(泛型类 / 方法 / 接口 + 通配符)

typescript 复制代码
import java.util.ArrayList;
import java.util.List;

/**
 * 泛型核心示例:
 * 1. 泛型类
 * 2. 泛型方法
 * 3. 泛型接口
 * 4. 泛型通配符(? extends/super)
 */
// 1. 泛型类:通用数据包装类
class DataWrapper<T> {
    private T data;

    public DataWrapper(T data) {
        this.data = data;
    }

    public T getData() {
        return data;
    }

    // 泛型方法:独立于类的泛型
    public <E> void printData(E extraData) {
        System.out.println("核心数据:" + data + ",额外数据:" + extraData);
    }
}

// 2. 泛型接口
interface Converter<F, T> {
    T convert(F from);
}

// 泛型接口实现:String转Integer
class StringToIntConverter implements Converter<String, Integer> {
    @Override
    public Integer convert(String from) {
        return Integer.parseInt(from);
    }
}

// 3. 泛型通配符示例
class GenericUtil {
    // 上界通配符:只能读取,不能添加(除了null)
    public static void printList(List<? extends Number> list) {
        for (Number num : list) {
            System.out.print(num + " ");
        }
        System.out.println();
        // list.add(10); // 编译错误:无法确定具体类型
    }

    // 下界通配符:只能添加,读取时只能转Object
    public static void addIntToList(List<? super Integer> list) {
        list.add(10);
        list.add(20);
        // Integer num = list.get(0); // 编译错误:只能转Object
    }
}

public class GenericDemo {
    public static void main(String[] args) {
        // 泛型类使用
        DataWrapper<String> strWrapper = new DataWrapper<>("黑马程序员");
        System.out.println(strWrapper.getData()); // 无需强转
        strWrapper.printData(123); // 泛型方法:E自动推断为Integer

        // 泛型接口使用
        Converter<String, Integer> converter = new StringToIntConverter();
        Integer num = converter.convert("100");
        System.out.println("字符串转数字:" + num);

        // 泛型通配符使用
        List<Integer> intList = new ArrayList<>();
        intList.add(1);
        intList.add(2);
        GenericUtil.printList(intList); // 上界通配符:Number的子类都可传入

        List<Object> objList = new ArrayList<>();
        GenericUtil.addIntToList(objList); // 下界通配符:Integer的父类都可传入
        System.out.println("添加后列表:" + objList);
    }
}

代码说明

  • 泛型类DataWrapper<T> 可包装任意类型数据,避免为 String/Integer 等分别写包装类,提升复用性。

  • 泛型方法<E> void printData(E extraData) 独立于类的泛型,调用时自动推断类型,灵活度更高。

  • 通配符

    • ? extends Number(上界):只能读取,适合 "生产者" 场景(如遍历输出);
    • ? super Integer(下界):只能添加,适合 "消费者" 场景(如添加元素)。
  • 类型安全 :泛型在编译期检查类型,避免运行时ClassCastException(如集合中存 String 却取 Integer)。

2. 企业面试题

  1. 问:泛型的作用?为什么要使用泛型?答:

    • 类型安全:编译期检查类型,避免运行时类型转换异常;
    • 代码复用:一套代码适配多种类型(如 List/List);
    • 消除强制类型转换:提升代码可读性和简洁性。
  2. 问:泛型擦除是什么?运行时能获取泛型类型吗?答:泛型擦除是 JVM 的机制:编译期保留泛型检查,运行时泛型信息被擦除(如List<String>变为List);默认无法获取,但可通过反射(如ParameterizedType)获取泛型类型(企业中框架常用,如 MyBatis)。

  3. 问:泛型通配符?? extends T? super T的区别?答:

    • ?:无界通配符,可接收任意类型,只能读取(转 Object),不能添加;
    • ? extends T:上界通配符,只能接收 T 或 T 的子类,适合读取;
    • ? super T:下界通配符,只能接收 T 或 T 的父类,适合添加。
  4. 问:泛型类和泛型方法的区别?答:泛型类的泛型参数在类级别,所有方法共享;泛型方法的泛型参数在方法级别,独立于类,可在非泛型类中定义,调用时更灵活。

3. 企业实际应用场景

  1. 集合框架 :Java 集合(List/Map/Set)全部基于泛型,如Map<String, User>List<Order>,避免类型混乱。
  2. 通用工具类 :企业封装通用工具(如缓存工具、转换工具),用泛型适配多种类型,如CacheUtil<K, V>ConvertUtil<F, T>
  3. 框架开发 :Spring/MyBatis 等框架大量使用泛型,如Mapper<User>RestTemplate.getForObject(String url, Class<T> responseType)
  4. 自定义容器 :业务中封装分页结果PageResult<T>,适配用户列表、订单列表等不同分页场景。

三、集合(Collection)

集合是 Java 存储和操作一组数据的容器,企业中 90% 以上的业务都会用到,核心分为Collection(单列)和Map(双列)两大体系。

1. 核心代码示例(常用集合 + 核心操作)

csharp 复制代码
import java.util.*;

/**
 * 集合核心示例:
 * 1. List(ArrayList/LinkedList)
 * 2. Set(HashSet/TreeSet)
 * 3. Map(HashMap/TreeMap)
 * 4. 集合遍历/排序/筛选
 */
public class CollectionDemo {
    public static void main(String[] args) {
        // 1. List:有序、可重复、索引访问
        List<String> arrayList = new ArrayList<>();
        arrayList.add("Java");
        arrayList.add("Python");
        arrayList.add("Java"); // 允许重复
        // 遍历
        for (String s : arrayList) {
            System.out.print(s + " ");
        }
        System.out.println();
        // 排序
        Collections.sort(arrayList);
        System.out.println("排序后:" + arrayList);

        // LinkedList:适合频繁增删(链表结构)
        List<Integer> linkedList = new LinkedList<>();
        linkedList.addFirst(1);
        linkedList.addLast(3);
        linkedList.add(1, 2); // 索引插入
        System.out.println("LinkedList:" + linkedList);

        // 2. Set:无序、不可重复
        Set<String> hashSet = new HashSet<>();
        hashSet.add("A");
        hashSet.add("B");
        hashSet.add("A"); // 重复元素不会添加
        System.out.println("HashSet:" + hashSet); // 无序

        // TreeSet:有序(自然排序/自定义排序)
        Set<Integer> treeSet = new TreeSet<>();
        treeSet.add(3);
        treeSet.add(1);
        treeSet.add(2);
        System.out.println("TreeSet(自然排序):" + treeSet); // [1,2,3]

        // 3. Map:键值对、键唯一
        Map<String, Integer> hashMap = new HashMap<>();
        hashMap.put("语文", 90);
        hashMap.put("数学", 95);
        hashMap.put("语文", 92); // 覆盖已有键
        // 遍历Map(企业推荐方式)
        for (Map.Entry<String, Integer> entry : hashMap.entrySet()) {
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }
        // 获取值
        Integer mathScore = hashMap.getOrDefault("数学", 0);
        System.out.println("数学成绩:" + mathScore);

        // TreeMap:按键排序
        Map<String, Integer> treeMap = new TreeMap<>(hashMap);
        System.out.println("TreeMap(按键排序):" + treeMap);

        // 4. 集合筛选(JDK 8+ Stream)
        List<Integer> numList = Arrays.asList(1, 2, 3, 4, 5, 6);
        List<Integer> evenList = numList.stream()
                .filter(num -> num % 2 == 0) // 筛选偶数
                .collect(Collectors.toList());
        System.out.println("偶数列表:" + evenList);
    }
}

代码说明

  • List 选型ArrayList(数组结构)适合随机访问、读多写少;LinkedList(链表结构)适合频繁增删(如队列 / 栈)。
  • Set 选型HashSet(哈希表)查询快(O (1)),无序;TreeSet(红黑树)有序,查询 O (logn)。
  • Map 选型HashMap(哈希表)企业首选,查询 / 插入快;TreeMap按键排序,适合需要有序的场景。
  • Stream 流:JDK 8 + 特性,企业中替代传统循环,实现集合的筛选、映射、聚合,代码更简洁。

2. 企业面试题

  1. 问:ArrayList 和 LinkedList 的区别?企业中如何选择?答:

    • 底层结构:ArrayList 是动态数组,LinkedList 是双向链表;
    • 访问效率:ArrayList 随机访问快(O (1)),LinkedList 慢(O (n));
    • 增删效率:ArrayList 尾部增删快,中间增删慢(需移动元素);LinkedList 任意位置增删快(O (1));
    • 内存:ArrayList 有扩容冗余,LinkedList 每个节点存前后指针,内存占用高;
    • 选型:读多写少用 ArrayList,频繁增删(如队列)用 LinkedList。
  2. 问:HashMap 的底层原理?JDK 1.7 和 1.8 的区别?答:

    • JDK 1.7:数组 + 链表,哈希冲突用链表解决,头插法(多线程下易成环);
    • JDK 1.8:数组 + 链表 + 红黑树,链表长度≥8 且数组长度≥64 时转红黑树,尾插法,解决成环问题;
    • 核心:通过 hashCode 计算索引,equals 判断键是否相同,负载因子 0.75(平衡空间和时间)。
  3. 问:HashSet 和 HashMap 的关系?HashSet 如何保证元素不重复?答:HashSet 底层是 HashMap(用 HashMap 的 key 存元素,value 存固定对象);添加元素时,先计算 hashCode,再用 equals 判断,若已存在则不添加,保证唯一性。

  4. 问:集合的遍历方式有哪些?哪种效率最高?答:

    • List:普通 for(索引)、增强 for、迭代器(Iterator)、Stream;
    • Map:keySet+get、entrySet、forEach(JDK 8+);
    • 效率:ArrayList 用普通 for 最快,LinkedList 用迭代器 / 增强 for 最快;Map 用 entrySet 遍历最快(避免多次 get)。

3. 企业实际应用场景

  1. 数据存储 :接口返回列表(如用户列表、订单列表)用List;去重数据(如商品标签、用户 ID)用HashSet;键值对数据(如用户 ID→用户信息)用HashMap
  2. 缓存实现 :本地缓存(如加载字典数据)用HashMap,有序缓存用TreeMap
  3. 数据处理:批量数据筛选(如筛选未支付订单)、聚合(如统计各部门人数)用 Stream 流。
  4. 并发场景 :高并发下用ConcurrentHashMap(线程安全)替代HashMap,用CopyOnWriteArrayList替代ArrayList

总结

  1. 异常:核心是区分编译时 / 运行时异常,企业中自定义业务异常 + 全局统一处理,避免程序崩溃和信息泄露。
  2. 泛型:解决类型安全和代码复用问题,是集合、框架的基础,重点掌握泛型类 / 方法和通配符的使用。
  3. 集合:企业开发高频使用,核心是根据业务场景选型(ArrayList/HashMap 为首选),掌握底层原理和高效遍历方式。
相关推荐
序安InToo14 分钟前
第6课|注释与代码风格
后端·操作系统·嵌入式
xyy12314 分钟前
C#: Newtonsoft.Json 到 System.Text.Json 迁移避坑指南
后端
洋洋技术笔记16 分钟前
Spring Boot Web MVC配置详解
spring boot·后端
JxWang0517 分钟前
VS Code 配置 Markdown 环境
后端
navms20 分钟前
搞懂线程池,先把 Worker 机制啃明白
后端
JxWang0520 分钟前
离线数仓的优化及重构
后端
Nyarlathotep011321 分钟前
gin01:初探gin的启动
后端·go
JxWang0522 分钟前
安卓手机配置通用多屏协同及自动化脚本
后端
JxWang0523 分钟前
Windows Terminal 配置 oh-my-posh
后端
SimonKing39 分钟前
OpenCode AI编程助手如何添加Skills,优化项目!
java·后端·程序员