Java常用API详解(二):集合类API(ArrayList/HashMap/HashSet)实战,一篇吃透

Java常用API详解(二):集合类API(ArrayList/HashMap/HashSet)实战,一篇吃透

哈喽,各位Java学习者、开发者小伙伴!上一篇我们讲解了Java基础高频API(String、StringBuilder、Object、枚举),都是日常开发的"基础工具"。今天这篇,我们聚焦集合类API------Java开发中处理批量数据的核心工具,也是后端开发、框架源码、面试中的高频考点。

很多新手刚接触集合时,会混淆ArrayList和LinkedList、HashMap和HashSet的用法,不知道什么时候用哪个,甚至写出效率低下、易报错的代码。本文将重点讲解3个最常用的集合类:ArrayList、HashMap、HashSet,从"底层原理→核心API→实战场景→避坑指南"全方位拆解,结合上一篇的API知识串联实战,所有代码可直接复制运行,新手也能轻松上手!

先回顾上一篇的核心知识点:我们用枚举替代魔法值,用String、StringBuilder处理字符串,用Object类的重写提升代码可读性。今天的集合API,会频繁结合这些知识点,实现更完整的企业级业务逻辑。

前置铺垫:为什么需要集合?(数组的局限性)

在没有集合之前,我们存储批量数据只能用数组,但数组有两个致命局限,而集合完美解决了这些问题,这也是集合成为开发必备工具的核心原因:

    1. 数组长度固定:一旦创建,无法动态扩容,添加元素超过长度会报错;
    1. 数组操作繁琐:没有内置的增删改查方法,比如删除元素需要手动移动数组元素,代码冗余。

而Java集合(位于java.util包下),本质是"动态容器",支持动态扩容,提供了丰富的增删改查API,无需关心底层实现,专注业务逻辑即可。Java集合体系核心分为两大分支:Collection(存储单个元素)Map(存储键值对),本文重点讲解最常用的3个实现类:ArrayList(Collection分支)、HashMap(Map分支)、HashSet(Collection分支)。

一、ArrayList API(最常用的List集合,开发首选)

ArrayList是List接口的最常用实现类,底层基于动态数组实现 ,核心特点:有序、可重复、支持索引访问,查询速度快(直接通过索引定位),尾部增删快,中间增删慢(需移动元素),非线程安全,是日常开发中处理批量数据的首选集合。

适用场景:频繁查询、尾部增删,少量中间增删(如用户列表、订单列表展示)。

1.1 核心初始化方式(3种,重点掌握前2种)

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

public class ArrayListTest {
    public static void main(String[] args) {
        // 1. 无参构造(最常用,初始容量为10,扩容时默认1.5倍扩容)
        List<String> list1 = new ArrayList<>();

        // 2. 指定初始容量(适合提前知道元素数量,减少扩容次数,提升性能)
        List<Integer> list2 = new ArrayList<>(20);

        // 3. 基于已有集合/数组初始化(偶尔用到)
        List<String> list3 = new ArrayList&lt;&gt;(Arrays.asList("Java", "API", "集合"));
    }
}

补充:JDK 8中,ArrayList无参构造的初始容量为0,首次添加元素时才扩容为10,后续每次扩容为原容量的1.5倍,通过Arrays.copyOf()复制数组实现扩容。

1.2 高频核心API(按使用频率排序,必记)

所有API结合代码示例,可直接复制运行,重点掌握"增删改查"四大核心操作:

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

public class ArrayListApiTest {
    public static void main(String[] args) {
        // 初始化ArrayList(存储用户名称,结合上一篇的String API)
        List<String> userNames = new ArrayList<>();

        // 1. 增:添加元素(核心API)
        userNames.add("张三"); // 尾部添加元素(最常用)
        userNames.add("李四");
        userNames.add(1, "王五"); // 指定索引插入(后续元素自动后移)
        System.out.println("添加后:" + userNames); // 输出:[张三, 王五, 李四]

        // 2. 查:获取元素/判断元素(核心API)
        String name = userNames.get(0); // 根据索引获取元素(索引从0开始)
        System.out.println("索引0的元素:" + name); // 输出:张三
        boolean hasLi = userNames.contains("李四"); // 判断是否包含指定元素
        System.out.println("是否包含李四:" + hasLi); // 输出:true
        int size = userNames.size(); // 获取集合元素个数
        System.out.println("集合大小:" + size); // 输出:3
        boolean isEmpty = userNames.isEmpty(); // 判断集合是否为空
        System.out.println("集合是否为空:" + isEmpty); // 输出:false

        // 3. 改:修改指定索引的元素
        String oldName = userNames.set(2, "赵六"); // 修改索引2的元素,返回被替换的旧元素
        System.out.println("被替换的旧元素:" + oldName); // 输出:李四
        System.out.println("修改后:" + userNames); // 输出:[张三, 王五, 赵六]

        // 4. 删:删除元素(3种常用方式)
        userNames.remove(1); // 方式1:根据索引删除,返回被删除的元素
        System.out.println("删除索引1后:" + userNames); // 输出:[张三, 赵六]
        userNames.remove("张三"); // 方式2:根据元素删除(删除第一个匹配的)
        System.out.println("删除张三后:" + userNames); // 输出:[赵六]
        // 方式3:按条件删除(JDK 8+,结合Lambda,实战常用)
        userNames.add("孙七");
        userNames.add("周八");
        userNames.removeIf(s -> s.length() == 2); // 删除长度为2的元素
        System.out.println("按条件删除后:" + userNames); // 输出:[]

        // 5. 清空集合
        userNames.clear();
        System.out.println("清空后:" + userNames); // 输出:[]

        // 6. 遍历集合(3种常用方式,重点掌握前2种)
        List<String> newList = new ArrayList<>(Arrays.asList("A", "B", "C"));
        // 方式1:增强for循环(最常用,简洁)
        for (String s : newList) {
            System.out.print(s + " "); // 输出:A B C
        }
        System.out.println();
        // 方式2:普通for循环(需要索引时用)
        for (int i = 0; i < newList.size(); i++) {
            System.out.print(newList.get(i) + " "); // 输出:A B C
        }
        System.out.println();
        // 方式3:迭代器(遍历过程中删除元素推荐用)
        Iterator<String> iterator = newList.iterator();
        while (iterator.hasNext()) {
            String s = iterator.next();
            if ("B".equals(s)) {
                iterator.remove(); // 迭代器删除,避免ConcurrentModificationException
            }
        }
        System.out.println("迭代器删除后:" + newList); // 输出:[A, C]
    }
}

1.3 新手常踩坑点(重点避坑)

  • 坑点1:索引越界异常(IndexOutOfBoundsException)→ 原因:索引小于0或大于等于集合大小(size()-1);解决:获取索引前先判断索引范围,或用增强for循环遍历。

  • 坑点2:遍历集合时用for循环删除元素 → 会导致索引错乱,抛ConcurrentModificationException;解决:用迭代器(Iterator)删除,或JDK 8+的removeIf()方法。

  • 坑点3:频繁在ArrayList中间增删元素 → 效率极低(需移动大量元素);解决:频繁中间增删,改用LinkedList(后续会简单介绍)。

  • 坑点4:ArrayList存储基本类型 → 报错;原因:集合只能存储引用类型,基本类型需用包装类(如int→Integer、long→Long)。

补充:ArrayList vs LinkedList(快速区分,避免用错)

很多新手会混淆这两个List实现类,用表格快速区分,按需选择:

特性 ArrayList LinkedList
底层实现 动态数组 双向链表
查询速度 快(O(1),直接索引访问) 慢(O(n),需遍历链表)
增删速度(中间) 慢(需移动元素) 快(仅修改节点指针)
适用场景 频繁查询、尾部增删 频繁中间增删、作为队列/栈使用

注意:日常开发中,ArrayList的使用频率远高于LinkedList,除非有频繁中间增删的场景,否则优先用ArrayList。

二、HashMap API(最常用的Map集合,键值对存储首选)

HashMap是Map接口的最常用实现类,底层基于哈希表(数组+链表/红黑树)实现 ,核心特点:无序、键唯一、值可重复,查询、增删速度快(O(1)),非线程安全,支持存储null键(仅1个)和null值(多个),是开发中存储键值对数据的首选集合。

适用场景:存储键值对数据(如用户ID→用户信息、配置项key→配置值、字典映射)。

2.1 核心初始化方式(2种,重点掌握)

java 复制代码
import java.util.HashMap;
import java.util.Map;

public class HashMapTest {
    public static void main(String[] args) {
        // 1. 无参构造(最常用,初始容量16,负载因子0.75)
        Map<Integer, String> userMap = new HashMap<>();

        // 2. 指定初始容量(适合提前知道键值对数量,减少扩容次数)
        Map<String, Integer> scoreMap = new HashMap<>(30);
    }
}

补充:负载因子0.75表示,当HashMap中元素个数超过"容量×0.75"时,会自动扩容为原容量的2倍(JDK 8);初始容量和负载因子的设置,会影响HashMap的性能,默认值(16和0.75)是时间和空间的平衡选择。

2.2 高频核心API(按使用频率排序,必记)

结合上一篇的User类、枚举API,实现实战场景(存储用户信息),代码可直接复用:

java 复制代码
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

// 自定义User类(重写equals()和hashCode(),避免HashMap键重复问题)
class User {
    private Integer id;
    private String name;
    private Integer age;

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

    // 重写equals()和hashCode()(HashMap键唯一的核心保障)
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return age.equals(user.age) && id.equals(user.id) && name.equals(user.name);
    }

    @Override
    public int hashCode() {
        return java.util.Objects.hash(id, name, age);
    }

    // 重写toString(),便于调试
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    // getter/setter省略
}

public class HashMapApiTest {
    public static void main(String[] args) {
        // 初始化HashMap:key=用户ID(Integer),value=用户对象(User)
        Map<Integer, User> userMap = new HashMap<>();

        // 1. 增:添加键值对(核心API)
        userMap.put(1, new User(1, "张三", 25));
        userMap.put(2, new User(2, "李四", 23));
        userMap.put(3, new User(3, "王五", 24));
        // 注意:key重复时,会覆盖原有的value
        userMap.put(1, new User(1, "张三(更新)", 26));
        System.out.println("添加后:" + userMap);

        // 2. 查:获取值/判断键(核心API)
        User user = userMap.get(2); // 根据key获取value
        System.out.println("key=2的用户:" + user); // 输出:User{id=2, name='李四', age=23}
        boolean hasKey = userMap.containsKey(3); // 判断是否包含指定key
        System.out.println("是否包含key=3:" + hasKey); // 输出:true
        boolean hasValue = userMap.containsValue(new User(2, "李四", 23)); // 判断是否包含指定value
        System.out.println("是否包含该用户:" + hasValue); // 输出:true
        int size = userMap.size(); // 获取键值对个数
        System.out.println("键值对个数:" + size); // 输出:3

        // 3. 改:修改value(本质是put(),key存在则覆盖)
        userMap.put(3, new User(3, "王五(更新)", 25));
        System.out.println("修改后:" + userMap);

        // 4. 删:删除键值对(根据key删除)
        User removedUser = userMap.remove(2); // 删除指定key,返回被删除的value
        System.out.println("被删除的用户:" + removedUser); // 输出:User{id=2, name='李四', age=23}
        System.out.println("删除后:" + userMap);

        // 5. 遍历HashMap(3种常用方式,重点掌握前2种)
        // 方式1:遍历所有key,再根据key获取value(最常用)
        Set<Integer> keys = userMap.keySet();
        for (Integer key : keys) {
            User u = userMap.get(key);
            System.out.println("key=" + key + ",value=" + u);
        }

        // 方式2:遍历所有键值对(entrySet(),高效)
        for (Map.Entry<Integer, User> entry : userMap.entrySet()) {
            Integer key = entry.getKey();
            User value = entry.getValue();
            System.out.println("key=" + key + ",value=" + value);
        }

        // 方式3:遍历所有value(不关心key时用)
        for (User u : userMap.values()) {
            System.out.println("value=" + u);
        }

        // 6. 清空集合
        userMap.clear();
        System.out.println("清空后:" + userMap); // 输出:{}
    }
}

2.3 新手常踩坑点(重中之重,面试高频)

  • 坑点1:HashMap的key重复问题 → 原因:未重写equals()hashCode(),导致相同内容的对象被当作不同key;解决:自定义对象作为key时,必须重写这两个方法(如上面的User类),确保"相同内容的对象,hashCode相同、equals返回true"。

  • 坑点2:HashMap是有序的 → 错误!HashMap是无序的(存储顺序≠插入顺序);解决:需要有序的键值对,改用LinkedHashMap。

  • 坑点3:HashMap线程安全 → 错误!HashMap非线程安全,多线程环境下修改会导致数据错乱;解决:多线程场景用ConcurrentHashMap(后续讲解),或用Collections.synchronizedMap(userMap)包装。

  • 坑点4:用null作为key时,多次put会覆盖 → 原因:HashMap只允许1个null key,重复put会覆盖原value。

三、HashSet API(去重首选,无序集合)

HashSet是Set接口的最常用实现类,底层基于HashMap实现 (用key存储元素,value为默认常量),核心特点:无序、元素唯一、无索引,不支持通过索引访问,查询、增删速度快(O(1)),非线程安全,允许存储1个null元素,是开发中"去重"的首选集合。

适用场景:元素去重(如用户标签去重、订单编号去重)、无需保证顺序的场景。

3.1 核心初始化方式(2种)

java 复制代码
import java.util.HashSet;
import java.util.Set;

public class HashSetTest {
    public static void main(String[] args) {
        // 1. 无参构造(最常用)
        Set<String> tagSet = new HashSet<>();

        // 2. 基于已有集合初始化(去重常用)
        Set<String> oldSet = new HashSet<>();
        oldSet.add("Java");
        oldSet.add("Java"); // 重复元素,自动忽略
        Set<String> newSet = new HashSet<>(oldSet);
    }
}

3.2 高频核心API(用法简洁,必记)

HashSet无索引,因此没有get()、set()等与索引相关的方法,核心API围绕"增删改查"(无改,改本质是删了再增):

java 复制代码
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class HashSetApiTest {
    public static void main(String[] args) {
        // 初始化HashSet(存储用户标签,实现去重)
        Set<String> tagSet = new HashSet<>();

        // 1. 增:添加元素(核心API,重复元素自动忽略)
        tagSet.add("Java");
        tagSet.add("后端");
        tagSet.add("API");
        tagSet.add("Java"); // 重复元素,不会添加
        System.out.println("添加后:" + tagSet); // 输出:[Java, 后端, API](顺序不固定)

        // 2. 查:判断元素是否存在(核心API)
        boolean hasTag = tagSet.contains("后端");
        System.out.println("是否包含后端标签:" + hasTag); // 输出:true
        int size = tagSet.size();
        System.out.println("标签个数:" + size); // 输出:3
        boolean isEmpty = tagSet.isEmpty();
        System.out.println("是否为空:" + isEmpty); // 输出:false

        // 3. 删:删除元素
        boolean isRemoved = tagSet.remove("API"); // 删除指定元素,返回是否删除成功
        System.out.println("是否删除API标签:" + isRemoved); // 输出:true
        System.out.println("删除后:" + tagSet); // 输出:[Java, 后端]

        // 4. 遍历HashSet(2种常用方式,无普通for循环,因为无索引)
        // 方式1:增强for循环(最常用)
        for (String tag : tagSet) {
            System.out.print(tag + " "); // 输出:Java 后端(顺序不固定)
        }
        System.out.println();

        // 方式2:迭代器(遍历过程中删除元素推荐用)
        Iterator<String> iterator = tagSet.iterator();
        while (iterator.hasNext()) {
            String tag = iterator.next();
            if ("Java".equals(tag)) {
                iterator.remove();
            }
        }
        System.out.println("迭代器删除后:" + tagSet); // 输出:[后端]

        // 5. 清空集合
        tagSet.clear();
        System.out.println("清空后:" + tagSet); // 输出:[]
    }
}

3.3 核心注意点(去重的关键)

    1. HashSet去重的原理:依赖元素的equals()hashCode()方法,和HashMap的key去重原理一致------先比较hashCode,若不同则直接存入;若相同,再用equals()判断,若为false则存入,若为true则忽略(视为重复元素)。
    1. 自定义对象存入HashSet,需重写equals()hashCode(),否则无法实现去重(和HashMap的key要求一致)。
    1. HashSet与ArrayList的区别:ArrayList有序、可重复、有索引;HashSet无序、不可重复、无索引,核心用途是去重。

补充:若需要"去重且保证插入顺序",改用LinkedHashSet(底层基于LinkedHashMap实现),API与HashSet完全一致,仅多了"有序"特性。

四、实战综合案例(整合三篇API,企业级场景)

结合上一篇的String、StringBuilder、枚举API,以及本篇的集合API,写一个"用户管理系统"简化版,实现用户的增删改查、标签去重、角色关联,整合所有知识点,代码可直接复用:

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

// 1. 枚举:用户角色(避免魔法值,衔接上一篇)
enum UserRole {
    ADMIN("管理员", 1),
    USER("普通用户", 2),
    GUEST("游客", 3);

    private final String desc;
    private final int code;

    UserRole(String desc, int code) {
        this.desc = desc;
        this.code = code;
    }

    // 自定义API:根据编码获取枚举
    public static UserRole getByCode(int code) {
        for (UserRole role : UserRole.values()) {
            if (role.code == code) {
                return role;
            }
        }
        return null;
    }

    // getter方法
    public String getDesc() {
        return desc;
    }

    public int getCode() {
        return code;
    }
}

// 2. 自定义User类(重写equals()、hashCode()、toString())
class User {
    private Integer id;
    private String name;
    private Integer age;
    private UserRole role;
    private Set<String> tags; // 用户标签(用HashSet去重)

    public User(Integer id, String name, Integer age, UserRole role) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.role = role;
        this.tags = new HashSet<>(); // 初始化标签集合(自动去重)
    }

    // 新增:添加用户标签(去重)
    public void addTag(String tag) {
        // 结合String API,去除标签前后空格,避免无效标签
        if (tag != null && !tag.trim().isEmpty()) {
            this.tags.add(tag.trim());
        }
    }

    // 重写equals()和hashCode()(用于HashMap、HashSet去重)
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return age.equals(user.age) && id.equals(user.id) && name.equals(user.name) && role == user.role;
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name, age, role);
    }

    // 重写toString(),结合StringBuilder拼接
    @Override
    public String toString() {
        return new StringBuilder()
                .append("User{")
                .append("id=").append(id)
                .append(", name='").append(name).append('\'')
                .append(", age=").append(age)
                .append(", role=").append(role.getDesc())
                .append(", tags=").append(tags)
                .append('}')
                .toString();
    }

    // getter/setter省略
}

// 3. 业务逻辑:用户管理(整合所有API)
public class UserManager {
    public static void main(String[] args) {
        // 用HashMap存储用户:key=用户ID,value=用户对象(高效查询)
        Map<Integer, User> userMap = new HashMap<>();

        // 1. 添加用户(结合枚举API、HashSet标签去重)
        User admin = new User(1, "张三", 25, UserRole.getByCode(1));
        admin.addTag("Java");
        admin.addTag("后端");
        admin.addTag("Java"); // 重复标签,自动去重
        userMap.put(1, admin);

        User user1 = new User(2, "李四", 23, UserRole.getByCode(2));
        user1.addTag("前端");
        user1.addTag("Vue");
        userMap.put(2, user1);

        System.out.println("所有用户:");
        for (User user : userMap.values()) {
            System.out.println(user);
        }

        // 2. 查询用户(根据ID查询,结合HashMap API)
        User queryUser = userMap.get(1);
        System.out.println("\n查询ID=1的用户:" + queryUser);

        // 3. 修改用户(更新姓名,结合HashMap API)
        queryUser.setName("张三(管理员)");
        userMap.put(1, queryUser); // 覆盖原用户
        System.out.println("\n修改后ID=1的用户:" + userMap.get(1));

        // 4. 删除用户(根据ID删除)
        userMap.remove(2);
        System.out.println("\n删除ID=2后,剩余用户:" + userMap.values());

        // 5. 统计用户数量(结合HashMap API)
        System.out.println("\n当前用户数量:" + userMap.size());

        // 6. 提取所有用户标签(去重,结合HashSet API)
        Set<String> allTags = new HashSet<>();
        for (User user : userMap.values()) {
            allTags.addAll(user.getTags()); // 批量添加标签,自动去重
        }
        System.out.println("\n所有用户标签(去重后):" + allTags);
    }
}

运行结果(参考):

所有用户: User{id=1, name='张三', age=25, role=管理员, tags=[Java, 后端]} User{id=2, name='李四', age=23, role=普通用户, tags=[前端, Vue]} 查询ID=1的用户:User{id=1, name='张三', age=25, role=管理员, tags=[Java, 后端]} 修改后ID=1的用户:User{id=1, name='张三(管理员)', age=25, role=管理员, tags=[Java, 后端]} 删除ID=2后,剩余用户:[User{id=1, name='张三(管理员)', age=25, role=管理员, tags=[Java, 后端]}] 当前用户数量:1 所有用户标签(去重后):[Java, 后端]

五、总结与后续预告

本文作为Java常用API系列的第二篇,重点讲解了3个最常用的集合类API,都是日常开发中"每天都会用到"的核心工具,总结如下:

    1. ArrayList:有序、可重复、有索引,底层数组,查询快,首选用于批量数据存储、频繁查询
    1. HashMap:无序、键唯一、值可重复,底层哈希表,首选用于键值对存储、高效查询
    1. HashSet:无序、元素唯一、无索引,底层HashMap,首选用于元素去重
    1. 核心共性:都非线程安全,自定义对象作为key/元素时,必须重写equals()hashCode()

新手建议:把本文的代码全部复制运行一遍,重点练习"集合的增删改查"和"去重逻辑",尤其是HashMap和HashSet的去重原理,这是面试高频考点;同时注意规避文中提到的坑点(如索引越界、线程安全、去重失败)。

后续预告:Java常用API(三)将讲解「工具类API」(Collections、Arrays)和「日期时间API」(LocalDateTime等),这些API能进一步提升开发效率,解决日常开发中的高频问题,敬请关注!

原创不易,点赞+收藏+关注,后续持续更新Java核心干货,欢迎在评论区交流集合API使用中的问题~

相关推荐
XMYX-02 小时前
19 - Go 并发限制:限流与控制并发数
开发语言·golang
z4424753262 小时前
CSS如何实现文本溢出显示省略号_掌握text-overflow使用方法
jvm·数据库·python
大能嘚吧嘚2 小时前
python3.13.x 创建虚拟环境
python
m0_515098422 小时前
如何处理.NET中的Oracle Number溢出_OracleDecimal与C# decimal数据类型对应
jvm·数据库·python
2401_887724502 小时前
Less如何优化CSS文件大小_利用压缩配置去除冗余样式
jvm·数据库·python
吕源林2 小时前
Python中PyTorch如何处理NaN损失值_添加梯度裁剪与检查输入数据
jvm·数据库·python
kronos.荒2 小时前
动态规划——整数拆分(python)
python·算法·动态规划
卵男(章鱼)2 小时前
汽车网络通讯分析与仿真工具的系统工程:Vector CANoe与ZLG ZCANPRO深度剖析
开发语言·汽车·php
Absurd5872 小时前
SQL如何利用JOIN查询进行数据报表汇总_聚合函数与分组连接方法
jvm·数据库·python