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

一、异常(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 为首选),掌握底层原理和高效遍历方式。
相关推荐
小安同学iter1 小时前
天机学堂day05
java·开发语言·spring boot·分布式·后端·spring cloud·微服务
无限进步_2 小时前
C语言宏的魔法:探索offsetof与位交换的奇妙世界
c语言·开发语言·windows·后端·算法·visual studio
白露与泡影2 小时前
springboot中File默认路径
java·spring boot·后端
汝生淮南吾在北2 小时前
SpringBoot+Vue游戏攻略网站
前端·vue.js·spring boot·后端·游戏·毕业设计·毕设
IMPYLH2 小时前
Lua 的 type 函数
开发语言·笔记·后端·junit·lua
ConardLi3 小时前
分析了 100 万亿 Token 后,得出的几个关于 AI 的真相
前端·人工智能·后端
老华带你飞3 小时前
英语学习|基于Java英语学习系统(源码+数据库+文档)
java·开发语言·数据库·vue.js·spring boot·后端·学习
源代码•宸3 小时前
100 Go Mistakes(#4 过度使用getter和setter、#5 接口污染)
开发语言·经验分享·后端·golang
腾讯云云开发3 小时前
【你可能不知道的开发技巧】一行代码完成小程序的CloudBase鉴权登录
前端·后端·微信小程序