Java基础速成:集合、String与异常处理核心要点

Java基础速成:集合、String与异常处理核心要点

引言

在Java技术栈的面试与实际开发中,基础知识的扎实程度往往决定了一个开发者的上限。无论是构建高并发的后端服务,还是编写健壮的中间件组件,集合框架字符串处理以及异常处理机制都是不可或缺的基石。

很多开发者能够熟练使用ArrayListHashMap,却在使用过程中无意间埋下了性能隐患或Bug;对String的不可变性一知半解,导致在拼接字符串时效率低下;对异常处理机制理解不透彻,使得系统在出错时难以定位问题。

本文将跳出简单的API罗列,从底层原理、源码实现、性能优化实战陷阱四个维度,深度剖析这三大核心模块,助你构建完整的Java知识体系。


一、 集合框架:数据容器的艺术

Java集合框架主要分为CollectionMap两大体系。面试中,重点在于理解不同容器的底层数据结构及其在多线程环境下的表现。

1.1 List体系:ArrayList与LinkedList的抉择

核心原理:
* ArrayList:基于动态数组实现。它允许通过下标快速随机访问,但插入和删除元素可能涉及数组的移动(复制),效率较低。其扩容机制是重点:默认初始容量为10,当空间不足时,按照1.5倍扩容(int newCapacity = oldCapacity + (oldCapacity >> 1)),并调用Arrays.copyOf进行数据搬运。
* LinkedList:基于双向链表实现。插入删除只需修改指针,时间复杂度为O(1)(已知节点),但随机访问需要遍历链表,时间复杂度为O(n)。

实战误区:
很多开发者认为“删除操作多就用LinkedList”。实际上,由于现代CPU缓存机制的存在,数组连续内存空间的访问速度远快于链表的非连续内存访问。除非涉及大量的头插/头删操作,否则绝大多数业务场景下ArrayList性能更优。

1.2 Map体系:HashMap的底层演进

HashMap是面试的“王者”考点。

JDK 1.8 之前: 数组 + 链表。Hash冲突时,节点形成链表。
JDK 1.8 之后: 数组 + 链表 + 红黑树。

核心原理深度解析:
1. 初始化:默认初始容量16,负载因子0.75。当元素个数 > 容量 * 负载因子时,触发扩容。
2. 哈希计算:为了减少哈希冲突,HashMap对Key的hashCode进行了扰动处理:(h = key.hashCode()) ^ (h >>> 16)。高16位异或低16位,使得低位特征中掺杂了高位特征,从而在取模运算(n-1 & hash)时分布更均匀。
3. 树化阈值:当链表长度超过8且数组长度超过64时,链表会转化为红黑树,将查询复杂度从O(n)降低到O(log n)。

1.3 实战代码示例:自定义Key的陷阱

在实际项目中,常用对象作为Map的Key。如果重写equals但不重写hashCode,会导致内存泄漏或查询失败。

import java.util.HashMap;
import java.util.Objects;

/**
 * 演示HashMap中作为Key的对象必须重写equals和hashCode方法
 */
class User {
    private String name;
    private int id;

    public User(String name, int id) {
        this.name = name;
        this.id = id;
    }

    // 必须重写equals,否则比较的是内存地址
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return id == user.id && Objects.equals(name, user.name);
    }

    // 必须重写hashCode,否则相同的对象可能计算出不同的哈希槽位
    @Override
    public int hashCode() {
        return Objects.hash(name, id);
    }
}

public class CollectionDemo {
    public static void main(String[] args) {
        HashMap<User, String> map = new HashMap<>();

        User u1 = new User("Alice", 101);
        map.put(u1, "Admin");

        // 即使是new出来的新对象,因为重写了equals和hashCode,依然能取到值
        User u2 = new User("Alice", 101);
        System.out.println("获取用户角色: " + map.get(u2)); // 输出: Admin

        // 如果不重写hashCode,u2的hash值与u1不同,将返回null
        // 如果不重写equals,即使hash碰撞,equals比较也会失败
    }
}

二、 String:不可变性的设计哲学

String是Java中使用频率最高的类,也是面试中最容易忽视深度的部分。

2.1 不可变性

String被final修饰,且内部存储字符的数组(JDK 8是char[],JDK 9是byte[])也是final的。这意味着String对象一旦创建,其值不可更改。

为什么设计成不可变?
1. 字符串常量池优化:Java将字符串字面量存储在常量池中。如果String可变,改变一个引用指向的字符串,会导致所有指向该字符串的引用都发生变化,这将是灾难性的。
2. 线程安全:不可变对象

posted @ 2026-03-03 12:00  寒人病酒  阅读(6)  评论(0)    收藏  举报