Java集合框架与泛型

时游大约 6 分钟

一、集合框架总览

Java 集合框架主要分为两大类:

  • 单列集合Collection 体系
    • List:有序、可重复
    • Set:不重复,有的无序,有的有序
    • Queue/Deque:队列、双端队列
  • 双列集合Map 体系
    • 键值对(Key-Value)结构,键唯一

二、List:有序可重复

2.1 ArrayList

  • 底层是动态数组
  • 随机访问快(get(index)),中间插入/删除较慢(需要移动元素)。
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add(1, "X"); // 指定位置插入
System.out.println(list.get(0)); // A

常见方法:

  • add(E e)add(int index, E e)
  • get(int index)set(int index, E e)
  • remove(int index)remove(Object o)
  • size()isEmpty()contains(Object o)

2.2 LinkedList

  • 底层是双向链表
  • 任意位置插入/删除相对快,随机访问较慢。
  • 同时实现了 ListDeque 接口,可用作双端队列。
LinkedList<Integer> list = new LinkedList<>();
list.addFirst(1);
list.addLast(2);
Integer first = list.getFirst();
Integer last = list.getLast();

2.3 遍历方式

List<String> list = Arrays.asList("A", "B", "C");

// 1. 普通 for
for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}

// 2. 增强 for
for (String s : list) {
    System.out.println(s);
}

// 3. 迭代器
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    System.out.println(it.next());
}
package basic;

import java.util.ArrayList;
import java.util.LinkedList;

public class List {
    public static void main(String[] args) {
        // ArrayList:动态数组
        ArrayList<Integer> list = new ArrayList<>();
        // 添加元素
        list.add(1);
        list.add(2);
        list.set(1, 3);  // 只能修改已经存在的元素,不可以越界
        // 访问元素
        System.out.println(list.get(0));
        // 删除元素:依据索引删除
        list.remove(1);
        // 数组长度
        System.out.println(list.size());
        // 判空
        System.out.println(list.isEmpty());
        // 判断是否存在
        System.out.println(list.contains(1));

        // LinkedList:可向首尾插入数据
        LinkedList<Integer> linkedList = new LinkedList<>();
        linkedList.addFirst(1);
        linkedList.addFirst(2); // 会往第一项推入
        linkedList.addLast(3);
        linkedList.addLast(5);
        linkedList.add(6); // 末尾添加
        linkedList.removeFirst(); // 移除首项
        linkedList.removeLast(); // 移除末尾
        int first = linkedList.getFirst(); // 查询首项,末尾项getLast也同理
        System.out.println(first);  // 1
        System.out.println(linkedList);  // [1, 3, 5]
    }
}

三、Set:不重复的集合

3.1 HashSet

  • 底层基于 HashMap 实现,元素无序、不重复。
  • 元素去重依赖:
    • hashCode() 计算哈希桶
    • equals() 判断内容是否相等
Set<String> set = new HashSet<>();
set.add("A");
set.add("B");
set.add("A"); // 不会重复添加
System.out.println(set.size()); // 2

3.2 LinkedHashSet & TreeSet

  • LinkedHashSet
    • 底层是 Hash 表 + 双向链表。
    • 保留元素插入顺序。
  • TreeSet
    • 底层是红黑树。
    • 元素按照自然顺序或指定比较器排序。
    • 元素必须实现 Comparable 接口,或在构造时传入 Comparator
Set<Integer> set = new TreeSet<>();
set.add(5);
set.add(1);
set.add(3);
System.out.println(set); // [1, 3, 5]

3.3 自定义对象放入 Set

如果要使用 HashSet 存放自定义对象,需要重写 hashCodeequals

public class User {
    private String name;
    private int age;

    // 构造、getter/setter 省略

    @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 == user.age &&
                Objects.equals(name, user.name);
    }

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

import java.util.*;

public class SetDemo {
    public static void main(String[] args) {
        // Set:元素无序、不重复、会自动去重
        Set<String> hashSet = new HashSet<>();
        hashSet.add("apple"); // 添加时会使用hashCode计算哈希码,由哈希码确定插入位置。如果哈希值相同,会使用equals()检查是否相等,若相等则不会被插入。
        hashSet.add("banana");
        hashSet.add("orange");
        hashSet.add("apple"); // 不会被添加
        hashSet.add(null); // 允许null
        System.out.println(hashSet);  // [banana, orange, apple],未按照插入的顺序

        // LinkedHashSet:继承自HashSet,区别是LinkedHashSet有序
        Set<String> linkedHashSet = new LinkedHashSet<>();
        linkedHashSet.add("apple");
        linkedHashSet.add("banana");
        linkedHashSet.add("orange");
        linkedHashSet.add("apple");
        linkedHashSet.add(null);
        System.out.println(linkedHashSet); // [apple, banana, orange, null],按插入的顺序排列

        // TreeSet:红黑树,会按照自然规律或指定顺序排序,不允许null
        Set<String> treeSet = new TreeSet<>();
        treeSet.add("banana");
        treeSet.add("orange");
        treeSet.add("apple");
        treeSet.add("022");
        treeSet.add("012");
        System.out.println(treeSet); // [012, 022, apple, banana, orange],依据先数字后字母a、b、c、d...排序


        // 三种Set删除元素方式
        hashSet.remove("apple"); // 删除单个元素,成功返回true
        hashSet.clear(); // 清空

        // 并、交、差、子集判定
        Set<String> hashSet1 = new HashSet<>(Arrays.asList("apple", "banana"));
        Set<String> hashSet2 = new HashSet<>(Arrays.asList("apple", "orange"));

        // 并
        // hashSet1.addAll(hashSet2);
        // System.out.println("并集:" + hashSet1); // [apple, banana, orange]
        // 交
        // hashSet1.retainAll(hashSet2);
        // System.out.println(hashSet1); // [apple]
        // 差
        // hashSet1.removeAll(hashSet2);
        // System.out.println(hashSet1); // [banana]
        // 子集判定
        boolean isChild = hashSet1.contains(hashSet2);  // 判断hashSet1中是否有hashSet2的所有元素
        System.out.println(isChild);
    }
}

四、Map:键值对映射

4.1 HashMap

  • 最常用的 Map 实现。
  • 键和值都可以是 null(但不推荐大量使用 null 作为键)。
  • 通过 keyhashCode + equals 实现快速查找。
Map<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);
map.put("A", 3); // 覆盖旧值

Integer value = map.get("A"); // 3

常用方法:

  • put(K key, V value)get(Object key)remove(Object key)
  • containsKey(Object key)containsValue(Object value)
  • keySet()values()entrySet()

4.2 LinkedHashMap & TreeMap

  • LinkedHashMap
    • 保持插入顺序。
    • 可用于实现简单的 LRU 缓存(通过访问顺序)。
  • s
    • 基于红黑树,键有序。
    • 键需要实现 Comparable,或在构造时提供 Comparator
Map<Integer, String> map = new TreeMap<>();
map.put(3, "C");
map.put(1, "A");
map.put(2, "B");
System.out.println(map); // {1=A, 2=B, 3=C}

4.3 遍历 Map

Map<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);

// 1. 遍历键值对
for (Map.Entry<String, Integer> entry : map.entrySet()) {
    System.out.println(entry.getKey() + " -> " + entry.getValue());
}

// 2. 只遍历 key
for (String key : map.keySet()) {
    System.out.println(key);
}

// 3. 只遍历 value
for (Integer value : map.values()) {
    System.out.println(value);
}
package basic;

import java.util.Map;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.TreeMap;


public class MapDemo {
    public static void main(String[] args) {
        // HashMap:无序
        Map<String, Integer> hasMap = new HashMap<>();
        hasMap.put("apple", 1); // 插入数据
        hasMap.put("orange", 2);
        hasMap.put("banana", 3);
        hasMap.put("pineapple", 2);
        System.out.println(hasMap.get("apple"));  // get():查询数据
        hasMap.remove("banana"); // 删除元素
        boolean hasKey = hasMap.containsKey("banana");  // 判定是否有这个key
        System.out.println(hasKey);
        boolean hasValue = hasMap.containsValue(2); // 判定是否有这个值
        System.out.println(hasValue);
        System.out.println(hasMap.keySet()); // 返回所有key组成的列表
        System.out.println(hasMap.values()); // 返回所有值组成的列表
        System.out.println(hasMap.entrySet()); // 组合成键值对返回:[orange=2, apple=1, pineapple=2]

        // LinkedHashMap:有序,其余用法同HashMap
        Map<String, Integer> linkedHashMap = new LinkedHashMap<>();
        linkedHashMap.put("apple", 1);
        linkedHashMap.put("orange", 2);
        linkedHashMap.put("banana", 3);
        System.out.println(linkedHashMap.get("apple")); // 1

        // TreeMap:红黑树,键有序,其余用法同HashMap
        Map<String, Integer> treeMap = new TreeMap<>();
        treeMap.put("apple", 1);
        treeMap.put("orange", 2);
        System.out.println(treeMap.get("apple"));

        // 遍历Map
        // 1.遍历键值对
        for (Map.Entry<String, Integer> entry : treeMap.entrySet()) {
            System.out.println(entry.getKey() + " : " + entry.getValue());
        }

        // 遍历key
        for (String key : treeMap.keySet()) {
            System.out.println(key + " : " + treeMap.get(key));
        }

        // 遍历value
        for (Integer value : treeMap.values()) {
            System.out.println(value);
        }
    }
}

五、泛型(Generics)基础

5.1 为什么需要泛型?

没有泛型时代:

List list = new ArrayList();
list.add("ABC");
String s = (String) list.get(0); // 强制类型转换,容易出错

使用泛型:

List<String> list = new ArrayList<>();
list.add("ABC");
String s = list.get(0); // 不需要强转,编译期类型检查

优点:

  • 编译期进行类型检查,减少运行时错误。
  • 避免频繁强制类型转换,代码更简洁。

5.2 泛型类

public class Box<T> {
    private T value;

    public void set(T value) {
        this.value = value;
    }

    public T get() {
        return value;
    }
}

Box<String> box = new Box<>();
box.set("Hello");
String v = box.get();

5.3 泛型方法

public class Util {
    public static <T> T getFirst(List<T> list) {
        if (list == null || list.isEmpty()) {
            return null;
        }
        return list.get(0);
    }
}

List<Integer> numbers = Arrays.asList(1, 2, 3);
Integer first = Util.getFirst(numbers);

注意:泛型方法的 <T> 声明在返回值类型前。


六、通配符与边界(? / extends / super)

6.1 无界通配符 ?

public void printList(List<?> list) {
    for (Object o : list) {
        System.out.println(o);
    }
    // list.add("x"); // 编译错误,无法安全写入
}
  • List<?> 表示元素类型未知。
  • 可以安全读取为 Object,但不能往里添加任何非 null 的元素

6.2 上界通配符 ? extends

  • List<? extends Number>:元素类型是 Number 或其子类。
  • 典型场景:读取为父类,不能安全写入。
public double sum(List<? extends Number> list) {
    double result = 0;
    for (Number n : list) {
        result += n.doubleValue();
    }
    return result;
}

6.3 下界通配符 ? super

  • List<? super Integer>:元素类型是 Integer 或其父类(如 Number, Object)。
  • 典型场景:向集合中安全写入某个类型的对象。
public void addNumbers(List<? super Integer> list) {
    list.add(1);
    list.add(2);
}

记忆口诀:
PECS(Producer Extends, Consumer Super)

  • 生产者(只读)用 extends
  • 消费者(写入)用 super

七、集合使用小建议

  • 优先选择接口类型声明变量:ListSetMap,而不是具体类。
  • ArrayList 适合读多写少、随机访问多的场景。
  • LinkedList 适合频繁在头尾插入/删除的场景。
  • 需要去重用 Set,同时注意重写自定义类的 equalshashCode
  • 键值映射基本都用 HashMap,需要顺序就换成 LinkedHashMap,需要排序用 TreeMap
上次编辑于:
贡献者: 15327360835
Loading...