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
- 底层是双向链表。
- 任意位置插入/删除相对快,随机访问较慢。
- 同时实现了
List和Deque接口,可用作双端队列。
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 存放自定义对象,需要重写 hashCode 和 equals:
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作为键)。 - 通过
key的hashCode + 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
七、集合使用小建议
- 优先选择接口类型声明变量:
List、Set、Map,而不是具体类。 ArrayList适合读多写少、随机访问多的场景。LinkedList适合频繁在头尾插入/删除的场景。- 需要去重用
Set,同时注意重写自定义类的equals和hashCode。 - 键值映射基本都用
HashMap,需要顺序就换成LinkedHashMap,需要排序用TreeMap。
Loading...
