大家好!今天我们来学习《Java 程序设计》中的第 11 章 ------ 泛型与集合。这一章内容非常重要,是 Java 编程的基础也是核心,掌握好泛型和集合框架能让我们的代码更简洁、更高效、更易维护。话不多说,让我们开始吧!

11.1 泛型介绍
在 Java 5 之前,集合中可以存储任意类型的对象,当我们从集合中取出对象时,需要进行强制类型转换,这不仅麻烦,还可能在运行时出现ClassCastException。泛型(Generic)的出现解决了这个问题,它允许我们在定义类、接口和方法时指定类型参数,从而实现类型安全。
11.1.1 泛型类型
泛型类型是指在定义类或接口时使用类型参数,在使用该类或接口时再指定具体的类型。
示例代码:泛型类的定义与使用
// 定义泛型类
class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
public class GenericTypeDemo {
public static void main(String[] args) {
// 使用泛型类,指定类型为String
Box<String> stringBox = new Box<>();
stringBox.setContent("Hello, Generic!");
String str = stringBox.getContent(); // 无需类型转换
System.out.println(str);
// 使用泛型类,指定类型为Integer
Box<Integer> integerBox = new Box<>();
integerBox.setContent(123);
int num = integerBox.getContent(); // 自动拆箱
System.out.println(num);
// 如果尝试放入其他类型,编译时就会报错
// stringBox.setContent(456); // 编译错误
}
}
运行结果:
11.1.2 泛型方法
泛型方法是指在方法声明中定义类型参数的方法,它可以在普通类中定义,也可以在泛型类中定义。
示例代码:泛型方法的定义与使用
public class GenericMethodDemo {
// 定义泛型方法
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.print(element + " ");
}
System.out.println();
}
// 定义有返回值的泛型方法
public static <T> T getLastElement(T[] array) {
if (array == null || array.length == 0) {
return null;
}
return array[array.length - 1];
}
public static void main(String[] args) {
Integer[] intArray = {1, 2, 3, 4, 5};
String[] strArray = {"Hello", "World", "Java"};
System.out.println("打印整数数组:");
printArray(intArray);
System.out.println("打印字符串数组:");
printArray(strArray);
System.out.println("整数数组最后一个元素:" + getLastElement(intArray));
System.out.println("字符串数组最后一个元素:" + getLastElement(strArray));
}
}
运行结果:
11.1.3 通配符(?)的使用
通配符?
用于表示未知类型,它可以在声明变量、方法参数和返回值时使用。
示例代码:通配符的使用
import java.util.ArrayList;
import java.util.List;
public class WildcardDemo {
// 使用通配符?表示可以接受任何类型的List
public static void printList(List<?> list) {
for (Object element : list) {
System.out.print(element + " ");
}
System.out.println();
}
// 注意:使用?的集合,不能添加元素(除了null)
public static void tryAddElement(List<?> list) {
// list.add("test"); // 编译错误
list.add(null); // 可以添加null
}
public static void main(String[] args) {
List<Integer> intList = new ArrayList<>();
intList.add(1);
intList.add(2);
intList.add(3);
List<String> strList = new ArrayList<>();
strList.add("Hello");
strList.add("World");
System.out.println("打印整数列表:");
printList(intList);
System.out.println("打印字符串列表:");
printList(strList);
}
}
运行结果:
11.1.4 有界类型参数
有界类型参数用于限制类型参数的范围,它分为上界和下界两种。
- 上界:
<? extends T>
表示类型参数必须是 T 或 T 的子类 - 下界:
<? super T>
表示类型参数必须是 T 或 T 的父类
示例代码:有界类型参数的使用
import java.util.ArrayList;
import java.util.List;
// 定义一个简单的动物类
class Animal {
public void eat() {
System.out.println("Animal is eating");
}
}
// 狗类继承自动物类
class Dog extends Animal {
@Override
public void eat() {
System.out.println("Dog is eating");
}
public void bark() {
System.out.println("Dog is barking");
}
}
// 猫类继承自动物类
class Cat extends Animal {
@Override
public void eat() {
System.out.println("Cat is eating");
}
public void meow() {
System.out.println("Cat is meowing");
}
}
public class BoundedTypeDemo {
// 上界:只能接受Animal或其子类
public static void feedAnimals(List<? extends Animal> animals) {
for (Animal animal : animals) {
animal.eat();
}
}
// 下界:只能接受Dog或其父类
public static void addDogs(List<? super Dog> list) {
list.add(new Dog());
list.add(new Dog());
}
public static void main(String[] args) {
List<Dog> dogs = new ArrayList<>();
dogs.add(new Dog());
dogs.add(new Dog());
List<Cat> cats = new ArrayList<>();
cats.add(new Cat());
cats.add(new Cat());
List<Animal> animals = new ArrayList<>();
animals.add(new Animal());
animals.add(new Dog());
animals.add(new Cat());
System.out.println("Feeding dogs:");
feedAnimals(dogs);
System.out.println("Feeding cats:");
feedAnimals(cats);
System.out.println("Feeding all animals:");
feedAnimals(animals);
// 测试下界
List<Object> objects = new ArrayList<>();
addDogs(objects);
System.out.println("Objects list size after adding dogs: " + objects.size());
List<Animal> animals2 = new ArrayList<>();
addDogs(animals2);
System.out.println("Animals list size after adding dogs: " + animals2.size());
}
}
运行结果:
11.1.5 类型擦除
Java 中的泛型是在编译时实现的,在运行时,泛型信息会被擦除,这就是类型擦除(Type Erasure)。类型擦除会将泛型类型参数替换为其边界类型(如果没有指定边界,则替换为 Object)。
示例代码:类型擦除的演示
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
public class TypeErasureDemo {
public static void main(String[] args) {
List<String> strList = new ArrayList<>();
List<Integer> intList = new ArrayList<>();
// 类型擦除后,strList和intList的类型是相同的
System.out.println("strList的类型:" + strList.getClass().getName());
System.out.println("intList的类型:" + intList.getClass().getName());
System.out.println("strList和intList的类型是否相同:" + (strList.getClass() == intList.getClass()));
try {
// 通过反射向String类型的List中添加Integer
Method addMethod = strList.getClass().getMethod("add", Object.class);
addMethod.invoke(strList, 123);
addMethod.invoke(strList, "Hello");
System.out.println("strList中的元素:");
for (Object obj : strList) {
System.out.println(obj + " (" + obj.getClass().getName() + ")");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
11.2 集合框架

Java 集合框架(Java Collections Framework)是一组用于存储和操作对象的类和接口,它提供了统一的方式来处理各种集合。集合框架位于java.util
包中。
下面是集合框架的整体结构思维导图:

集合框架的主要接口和类之间的关系如下(PlantUML 类图):
@startuml
interface Collection
interface List
interface Set
interface Queue
interface Deque
interface Map
class ArrayList
class LinkedList
class Vector
class Stack
class HashSet
class LinkedHashSet
class TreeSet
class ArrayDeque
class PriorityQueue
class HashMap
class LinkedHashMap
class TreeMap
class Hashtable
Collection <|-- List
Collection <|-- Set
Collection <|-- Queue
Queue <|-- Deque
List <|-- ArrayList
List <|-- LinkedList
List <|-- Vector
Vector <|-- Stack
Set <|-- HashSet
Set <|-- LinkedHashSet
Set <|-- TreeSet
Deque <|-- LinkedList
Deque <|-- ArrayDeque
Queue <|-- PriorityQueue
Map <|-- HashMap
Map <|-- LinkedHashMap
Map <|-- TreeMap
Map <|-- Hashtable
@enduml
11.3 List 接口及实现类
List 接口是 Collection 接口的子接口,它代表一个有序的集合,允许存储重复元素。List 中的元素可以通过索引来访问。
11.3.1 List 的操作
List 接口提供了一系列操作元素的方法,如添加、删除、修改、查找等。
示例代码:List 的基本操作
import java.util.ArrayList;
import java.util.List;
public class ListOperationDemo {
public static void main(String[] args) {
// 创建一个List集合
List<String> fruits = new ArrayList<>();
// 添加元素
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Orange");
System.out.println("添加元素后:" + fruits);
// 在指定位置添加元素
fruits.add(1, "Grape");
System.out.println("在指定位置添加元素后:" + fruits);
// 获取指定位置的元素
String fruit = fruits.get(2);
System.out.println("索引2处的元素:" + fruit);
// 修改指定位置的元素
fruits.set(2, "Strawberry");
System.out.println("修改元素后:" + fruits);
// 查找元素的索引
int index = fruits.indexOf("Banana");
System.out.println("Banana的索引:" + index);
// 判断集合是否包含某个元素
boolean contains = fruits.contains("Apple");
System.out.println("是否包含Apple:" + contains);
// 删除指定位置的元素
String removedFruit = fruits.remove(3);
System.out.println("删除的元素:" + removedFruit);
System.out.println("删除元素后:" + fruits);
// 获取集合的大小
int size = fruits.size();
System.out.println("集合的大小:" + size);
// 清空集合
fruits.clear();
System.out.println("清空集合后:" + fruits);
// 判断集合是否为空
boolean isEmpty = fruits.isEmpty();
System.out.println("集合是否为空:" + isEmpty);
}
}
运行结果:

11.3.2 ArrayList 类
ArrayList 是 List 接口的实现类,它基于动态数组实现,支持快速随机访问,但插入和删除元素的效率较低(尤其是在中间位置)。
示例代码:ArrayList 的使用
import java.util.ArrayList;
import java.util.List;
public class ArrayListDemo {
public static void main(String[] args) {
// 创建一个ArrayList
List<Integer> numbers = new ArrayList<>();
// 添加元素
for (int i = 1; i <= 5; i++) {
numbers.add(i);
}
System.out.println("ArrayList中的元素:" + numbers);
// 遍历ArrayList(使用for循环)
System.out.println("使用for循环遍历:");
for (int i = 0; i < numbers.size(); i++) {
System.out.print(numbers.get(i) + " ");
}
System.out.println();
// 截取子列表(注意:子列表是原列表的视图,修改会影响原列表)
List<Integer> subList = numbers.subList(1, 4);
System.out.println("子列表:" + subList);
// 修改子列表
subList.set(0, 10);
System.out.println("修改子列表后,原列表:" + numbers);
// 转换为数组
Integer[] numArray = numbers.toArray(new Integer[0]);
System.out.println("转换为数组后:");
for (int num : numArray) {
System.out.print(num + " ");
}
System.out.println();
}
}
运行结果:
11.3.3 遍历集合元素
遍历集合元素有多种方式:for 循环、增强 for 循环(foreach)、迭代器(Iterator)等。
示例代码:集合元素的遍历方式
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class TraverseListDemo {
public static void main(String[] args) {
// 创建一个List集合并添加元素
List<String> languages = new ArrayList<>();
languages.add("Java");
languages.add("Python");
languages.add("C++");
languages.add("JavaScript");
languages.add("C#");
System.out.println("集合元素:" + languages);
// 1. 使用普通for循环遍历
System.out.println("\n1. 使用普通for循环遍历:");
for (int i = 0; i < languages.size(); i++) {
System.out.print(languages.get(i) + " ");
}
// 2. 使用增强for循环(foreach)遍历
System.out.println("\n\n2. 使用增强for循环遍历:");
for (String lang : languages) {
System.out.print(lang + " ");
}
// 3. 使用迭代器(Iterator)遍历
System.out.println("\n\n3. 使用迭代器遍历:");
Iterator<String> iterator = languages.iterator();
while (iterator.hasNext()) {
String lang = iterator.next();
System.out.print(lang + " ");
}
// 4. 使用迭代器遍历并删除元素
System.out.println("\n\n4. 使用迭代器遍历并删除元素:");
Iterator<String> iter = languages.iterator();
while (iter.hasNext()) {
String lang = iter.next();
if (lang.equals("C++")) {
iter.remove(); // 使用迭代器的remove方法删除元素
}
}
System.out.println("删除C++后的集合:" + languages);
// 5. 使用Lambda表达式遍历(Java 8+)
System.out.println("\n5. 使用Lambda表达式遍历:");
languages.forEach(lang -> System.out.print(lang + " "));
}
}
运行结果:

11.3.4 数组转换为 List 对象
可以使用Arrays.asList()方法将数组转换为 List 对象,但需要注意的是,这样得到的 List 是固定大小的,不能进行添加和删除操作。如果需要可修改的 List,可以创建一个新的 ArrayList,并将转换后的 List 作为参数传入。
示例代码:数组转换为 List
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ArrayToListDemo {
public static void main(String[] args) {
// 定义一个字符串数组
String[] colors = {"Red", "Green", "Blue", "Yellow"};
// 将数组转换为List(固定大小,不可修改)
List<String> fixedList = Arrays.asList(colors);
System.out.println("固定大小的List:" + fixedList);
// 尝试添加元素,会抛出UnsupportedOperationException
try {
fixedList.add("Purple");
} catch (UnsupportedOperationException e) {
System.out.println("尝试添加元素时出错:" + e.getMessage());
}
// 尝试删除元素,同样会抛出异常
try {
fixedList.remove(0);
} catch (UnsupportedOperationException e) {
System.out.println("尝试删除元素时出错:" + e.getMessage());
}
// 可以修改元素
fixedList.set(0, "Pink");
System.out.println("修改元素后的List:" + fixedList);
System.out.println("原数组也会被修改:" + Arrays.toString(colors));
// 将数组转换为可修改的List
List<String> modifiableList = new ArrayList<>(Arrays.asList(colors));
System.out.println("可修改的List:" + modifiableList);
// 可以添加元素
modifiableList.add("Purple");
System.out.println("添加元素后:" + modifiableList);
// 可以删除元素
modifiableList.remove(0);
System.out.println("删除元素后:" + modifiableList);
System.out.println("原数组不会被修改:" + Arrays.toString(colors));
}
}
运行结果:
11.3.5 Vector 类和 Stack 类
Vector 类和 ArrayList 类似,也是基于动态数组实现的,但 Vector 是线程安全的,它的方法大多带有synchronized关键字。由于线程安全的开销,Vector 的性能比 ArrayList 差,在不需要线程安全的情况下,推荐使用 ArrayList。
Stack 类继承自 Vector 类,它实现了一个后进先出(LIFO)的栈。
示例代码:Vector 和 Stack 的使用
import java.util.Stack;
import java.util.Vector;
public class VectorAndStackDemo {
public static void main(String[] args) {
// Vector的使用
Vector<String> vector = new Vector<>();
vector.add("A");
vector.add("B");
vector.add("C");
System.out.println("Vector中的元素:" + vector);
// 获取元素
String firstElement = vector.firstElement();
String lastElement = vector.lastElement();
System.out.println("第一个元素:" + firstElement);
System.out.println("最后一个元素:" + lastElement);
// Stack的使用
Stack<Integer> stack = new Stack<>();
// 入栈
stack.push(1);
stack.push(2);
stack.push(3);
System.out.println("\n入栈后,栈的元素:" + stack);
// 查看栈顶元素
int top = stack.peek();
System.out.println("栈顶元素:" + top);
// 出栈
int popped = stack.pop();
System.out.println("出栈的元素:" + popped);
System.out.println("出栈后,栈的元素:" + stack);
// 判断栈是否为空
boolean isEmpty = stack.isEmpty();
System.out.println("栈是否为空:" + isEmpty);
// 查找元素在栈中的位置(从栈顶开始计数)
int position = stack.search(1);
System.out.println("元素1在栈中的位置:" + position);
}
}
运行结果:
11.4 Set 接口及实现类
Set 接口是 Collection 接口的子接口,它代表一个不包含重复元素的集合。Set 中的元素没有特定的顺序(某些实现类除外,如 TreeSet)。
11.4.1 HashSet 类
HashSet 是 Set 接口的实现类,它基于哈希表(HashMap)实现,不保证元素的顺序,添加、删除和查找元素的效率都很高。
示例代码:HashSet 的使用
import java.util.HashSet;
import java.util.Set;
public class HashSetDemo {
public static void main(String[] args) {
// 创建一个HashSet
Set<String> fruits = new HashSet<>();
// 添加元素
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Orange");
fruits.add("Apple"); // 添加重复元素,会被忽略
System.out.println("HashSet中的元素:" + fruits);
// 判断是否包含某个元素
boolean containsBanana = fruits.contains("Banana");
System.out.println("是否包含Banana:" + containsBanana);
// 删除元素
boolean removed = fruits.remove("Orange");
System.out.println("是否删除成功:" + removed);
System.out.println("删除元素后:" + fruits);
// 获取集合大小
int size = fruits.size();
System.out.println("集合大小:" + size);
// 遍历集合
System.out.println("遍历集合:");
for (String fruit : fruits) {
System.out.print(fruit + " ");
}
System.out.println();
// 清空集合
fruits.clear();
System.out.println("清空集合后:" + fruits);
// 判断集合是否为空
boolean isEmpty = fruits.isEmpty();
System.out.println("集合是否为空:" + isEmpty);
}
}
运行结果:
11.4.2 用 Set 对象实现集合运算
Set 接口提供了一些方法,可以方便地实现集合的交集、并集和差集等运算。
示例代码:使用 Set 实现集合运算
import java.util.HashSet;
import java.util.Set;
public class SetOperationDemo {
public static void main(String[] args) {
// 创建三个集合
Set<Integer> set1 = new HashSet<>();
set1.add(1);
set1.add(2);
set1.add(3);
set1.add(4);
Set<Integer> set2 = new HashSet<>();
set2.add(3);
set2.add(4);
set2.add(5);
set2.add(6);
System.out.println("集合1:" + set1);
System.out.println("集合2:" + set2);
// 1. 并集:将set2中的元素添加到set1中
Set<Integer> union = new HashSet<>(set1);
union.addAll(set2);
System.out.println("并集:" + union);
// 2. 交集:保留set1和set2中都有的元素
Set<Integer> intersection = new HashSet<>(set1);
intersection.retainAll(set2);
System.out.println("交集:" + intersection);
// 3. 差集:保留set1中有而set2中没有的元素
Set<Integer> difference = new HashSet<>(set1);
difference.removeAll(set2);
System.out.println("差集(set1 - set2):" + difference);
// 4. 对称差集:保留set1和set2中不共有的元素
Set<Integer> symmetricDifference = new HashSet<>(union);
symmetricDifference.removeAll(intersection);
System.out.println("对称差集:" + symmetricDifference);
}
}
运行结果:
11.4.3 TreeSet 类
TreeSet 是 Set 接口的实现类,它基于红黑树(一种自平衡二叉查找树)实现,能够保证元素的有序性。TreeSet 中的元素会按照自然顺序或指定的比较器进行排序。
示例代码:TreeSet 的使用
import java.util.TreeSet;
import java.util.Set;
public class TreeSetDemo {
public static void main(String[] args) {
// 创建一个TreeSet,元素会按照自然顺序排序
Set<Integer> numbers = new TreeSet<>();
// 添加元素
numbers.add(5);
numbers.add(2);
numbers.add(8);
numbers.add(1);
numbers.add(9);
numbers.add(3);
numbers.add(7);
numbers.add(4);
numbers.add(6);
System.out.println("TreeSet中的元素(自然顺序):" + numbers);
// 创建一个存储字符串的TreeSet,会按照字母顺序排序
Set<String> words = new TreeSet<>();
words.add("banana");
words.add("apple");
words.add("orange");
words.add("grape");
words.add("cherry");
System.out.println("字符串TreeSet(字母顺序):" + words);
// TreeSet的一些特殊方法
TreeSet<Integer> treeNumbers = (TreeSet<Integer>) numbers;
// 获取第一个元素
int first = treeNumbers.first();
System.out.println("第一个元素:" + first);
// 获取最后一个元素
int last = treeNumbers.last();
System.out.println("最后一个元素:" + last);
// 获取小于指定元素的最大元素
Integer lower = treeNumbers.lower(5);
System.out.println("小于5的最大元素:" + lower);
// 获取大于指定元素的最小元素
Integer higher = treeNumbers.higher(5);
System.out.println("大于5的最小元素:" + higher);
// 获取小于等于指定元素的最大元素
Integer floor = treeNumbers.floor(5);
System.out.println("小于等于5的最大元素:" + floor);
// 获取大于等于指定元素的最小元素
Integer ceiling = treeNumbers.ceiling(5);
System.out.println("大于等于5的最小元素:" + ceiling);
}
}
运行结果:
11.4.4 对象顺序
当我们将自定义对象存入 TreeSet 时,需要指定对象的排序方式,有两种方式:
- 让自定义类实现
Comparable
接口,重写compareTo
方法,定义自然顺序 - 在创建 TreeSet 时,传入一个
Comparator
对象,定义比较规则
示例代码:自定义对象在 TreeSet 中的排序
import java.util.Comparator;
import java.util.TreeSet;
// 1. 实现Comparable接口,定义自然顺序(按年龄升序)
class Person implements Comparable<Person> {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public int compareTo(Person other) {
// 按年龄升序排序
return Integer.compare(this.age, other.age);
}
@Override
public String toString() {
return name + "(" + age + ")";
}
}
public class ObjectOrderDemo {
public static void main(String[] args) {
// 使用自然顺序(按年龄升序)
TreeSet<Person> peopleByAge = new TreeSet<>();
peopleByAge.add(new Person("Alice", 25));
peopleByAge.add(new Person("Bob", 20));
peopleByAge.add(new Person("Charlie", 30));
peopleByAge.add(new Person("David", 22));
System.out.println("按年龄升序排序:" + peopleByAge);
// 2. 使用Comparator,按姓名长度排序
Comparator<Person> nameLengthComparator = new Comparator<Person>() {
@Override
public int compare(Person p1, Person p2) {
// 按姓名长度升序排序
int lengthCompare = Integer.compare(p1.getName().length(), p2.getName().length());
// 如果姓名长度相同,按姓名升序排序
if (lengthCompare == 0) {
return p1.getName().compareTo(p2.getName());
}
return lengthCompare;
}
};
TreeSet<Person> peopleByNameLength = new TreeSet<>(nameLengthComparator);
peopleByNameLength.add(new Person("Alice", 25));
peopleByNameLength.add(new Person("Bob", 20));
peopleByNameLength.add(new Person("Charlie", 30));
peopleByNameLength.add(new Person("David", 22));
System.out.println("按姓名长度升序排序:" + peopleByNameLength);
// 使用Lambda表达式简化Comparator
TreeSet<Person> peopleByName = new TreeSet<>((p1, p2) -> p1.getName().compareTo(p2.getName()));
peopleByName.add(new Person("Alice", 25));
peopleByName.add(new Person("Bob", 20));
peopleByName.add(new Person("Charlie", 30));
peopleByName.add(new Person("David", 22));
System.out.println("按姓名升序排序:" + peopleByName);
}
}
运行结果:
11.5 Queue 接口及实现类
Queue 接口是 Collection 接口的子接口,它代表一个队列,通常按照先进先出(FIFO)的原则排序元素。
11.5.1 Queue 接口和 Deque 接口
Queue 接口定义了队列的基本操作,如添加、删除、查看元素等。Deque(双端队列)接口继承自 Queue 接口,它允许在队列的两端进行元素的添加和删除操作。
Queue 接口的主要方法:
add(e)
:添加元素到队尾,失败则抛出异常offer(e)
:添加元素到队尾,失败则返回 falseremove()
:移除并返回队头元素,队列为空则抛出异常poll()
:移除并返回队头元素,队列为空则返回 nullelement()
:返回队头元素但不移除,队列为空则抛出异常peek()
:返回队头元素但不移除,队列为空则返回 null
Deque 接口的主要方法(额外增加的):
addFirst(e)
:添加元素到队头,失败则抛出异常addLast(e)
:添加元素到队尾,失败则抛出异常offerFirst(e)
:添加元素到队头,失败则返回 falseofferLast(e)
:添加元素到队尾,失败则返回 falseremoveFirst()
:移除并返回队头元素,队列为空则抛出异常removeLast()
:移除并返回队尾元素,队列为空则抛出异常pollFirst()
:移除并返回队头元素,队列为空则返回 nullpollLast()
:移除并返回队尾元素,队列为空则返回 nullgetFirst()
:返回队头元素但不移除,队列为空则抛出异常getLast()
:返回队尾元素但不移除,队列为空则抛出异常peekFirst()
:返回队头元素但不移除,队列为空则返回 nullpeekLast()
:返回队尾元素但不移除,队列为空则返回 null
11.5.2 ArrayDeque 类和 LinkedList 类
ArrayDeque 和 LinkedList 是 Deque 接口的两个主要实现类:
- ArrayDeque:基于动态数组实现,效率高,没有容量限制
- LinkedList:基于双向链表实现,除了实现 Deque 接口外,还实现了 List 接口
示例代码:Queue 和 Deque 的使用
import java.util.ArrayDeque;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Deque;
public class QueueAndDequeDemo {
public static void main(String[] args) {
// 使用LinkedList作为Queue(队列)
Queue<String> queue = new LinkedList<>();
// 添加元素到队尾
queue.offer("A");
queue.offer("B");
queue.offer("C");
System.out.println("队列元素:" + queue);
// 获取队头元素
String head = queue.peek();
System.out.println("队头元素:" + head);
// 移除队头元素
String removed = queue.poll();
System.out.println("移除的元素:" + removed);
System.out.println("移除后队列:" + queue);
// 使用ArrayDeque作为Deque(双端队列)
Deque<Integer> deque = new ArrayDeque<>();
// 添加元素到队尾
deque.addLast(1);
deque.offerLast(2);
// 添加元素到队头
deque.addFirst(0);
deque.offerFirst(-1);
System.out.println("\n双端队列元素:" + deque);
// 获取队头和队尾元素
System.out.println("队头元素:" + deque.getFirst());
System.out.println("队尾元素:" + deque.getLast());
// 移除队头和队尾元素
int firstRemoved = deque.removeFirst();
int lastRemoved = deque.removeLast();
System.out.println("移除的队头元素:" + firstRemoved);
System.out.println("移除的队尾元素:" + lastRemoved);
System.out.println("移除后双端队列:" + deque);
// 使用Deque作为栈(后进先出)
Deque<String> stack = new ArrayDeque<>();
// 入栈(添加到队头)
stack.push("X");
stack.push("Y");
stack.push("Z");
System.out.println("\n栈元素:" + stack);
// 查看栈顶元素
System.out.println("栈顶元素:" + stack.peek());
// 出栈(移除队头元素)
String popped = stack.pop();
System.out.println("出栈元素:" + popped);
System.out.println("出栈后栈:" + stack);
}
}
运行结果:
11.5.3 集合转换
集合之间可以进行相互转换,例如将 List 转换为 Set 去重,将 Set 转换为 List 保持顺序等。
示例代码:集合之间的转换
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
public class CollectionConversionDemo {
public static void main(String[] args) {
// 1. List转换为Set(去重)
List<String> fruitList = new ArrayList<>();
fruitList.add("Apple");
fruitList.add("Banana");
fruitList.add("Apple");
fruitList.add("Orange");
fruitList.add("Banana");
System.out.println("原List(有重复元素):" + fruitList);
// 转换为HashSet(去重,不保证顺序)
Set<String> hashSet = new HashSet<>(fruitList);
System.out.println("转换为HashSet(去重):" + hashSet);
// 转换为TreeSet(去重,按自然顺序排序)
Set<String> treeSet = new TreeSet<>(fruitList);
System.out.println("转换为TreeSet(去重排序):" + treeSet);
// 2. Set转换为List(保持顺序)
Set<Integer> numberSet = new TreeSet<>();
numberSet.add(5);
numberSet.add(2);
numberSet.add(8);
numberSet.add(1);
System.out.println("\n原Set:" + numberSet);
// 转换为ArrayList
List<Integer> arrayList = new ArrayList<>(numberSet);
System.out.println("转换为ArrayList:" + arrayList);
// 转换为LinkedList
List<Integer> linkedList = new LinkedList<>(numberSet);
System.out.println("转换为LinkedList:" + linkedList);
// 3. 数组转换为集合
String[] colorArray = {"Red", "Green", "Blue"};
System.out.println("\n原数组:" + Arrays.toString(colorArray));
List<String> colorList = new ArrayList<>(Arrays.asList(colorArray));
System.out.println("转换为List:" + colorList);
Set<String> colorSet = new HashSet<>(Arrays.asList(colorArray));
System.out.println("转换为Set:" + colorSet);
// 4. 集合转换为数组
String[] listToArray = colorList.toArray(new String[0]);
System.out.println("\nList转换为数组:" + Arrays.toString(listToArray));
String[] setToArray = colorSet.toArray(new String[0]);
System.out.println("Set转换为数组:" + Arrays.toString(setToArray));
}
}
运行结果:
11.6 Map 接口及实现类
Map 接口用于存储键值对(key-value)映射,其中键(key)是唯一的,每个键最多只能映射到一个值(value)。
11.6.1 Map 接口
Map 接口定义了一系列操作键值对的方法,如添加、删除、查找等。
示例代码:Map 接口的基本操作
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class MapOperationDemo {
public static void main(String[] args) {
// 创建一个Map集合
Map<String, Integer> studentScores = new HashMap<>();
// 添加键值对
studentScores.put("Alice", 90);
studentScores.put("Bob", 85);
studentScores.put("Charlie", 95);
studentScores.put("David", 88);
System.out.println("添加元素后:" + studentScores);
// 添加重复的键,会覆盖原来的值
studentScores.put("Bob", 87);
System.out.println("添加重复键后:" + studentScores);
// 获取指定键的值
int aliceScore = studentScores.get("Alice");
System.out.println("Alice的分数:" + aliceScore);
// 获取不存在的键的值,返回null
Integer tomScore = studentScores.get("Tom");
System.out.println("Tom的分数(不存在):" + tomScore);
// 获取指定键的值,如果不存在则返回默认值
int tomScoreWithDefault = studentScores.getOrDefault("Tom", 0);
System.out.println("Tom的分数(带默认值):" + tomScoreWithDefault);
// 判断是否包含指定的键
boolean hasCharlie = studentScores.containsKey("Charlie");
System.out.println("是否包含Charlie:" + hasCharlie);
// 判断是否包含指定的值
boolean hasScore90 = studentScores.containsValue(90);
System.out.println("是否包含90分:" + hasScore90);
// 删除指定的键值对
Integer removedScore = studentScores.remove("David");
System.out.println("删除的分数:" + removedScore);
System.out.println("删除后:" + studentScores);
// 获取Map的大小
int size = studentScores.size();
System.out.println("Map的大小:" + size);
// 获取所有的键
Set<String> names = studentScores.keySet();
System.out.println("所有的键:" + names);
// 遍历Map(通过键集)
System.out.println("遍历Map(通过键集):");
for (String name : names) {
System.out.println(name + ": " + studentScores.get(name));
}
// 获取所有的键值对
Set<Map.Entry<String, Integer>> entries = studentScores.entrySet();
System.out.println("\n所有的键值对:" + entries);
// 遍历Map(通过键值对集)
System.out.println("遍历Map(通过键值对集):");
for (Map.Entry<String, Integer> entry : entries) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// 清空Map
studentScores.clear();
System.out.println("\n清空后:" + studentScores);
// 判断Map是否为空
boolean isEmpty = studentScores.isEmpty();
System.out.println("Map是否为空:" + isEmpty);
}
}
运行结果:
11.6.2 Map 接口的实现类
Map 接口有多个实现类,常用的有:
- HashMap:基于哈希表实现,不保证元素的顺序,添加、删除和查找的效率都很高
- LinkedHashMap:继承自 HashMap,保留了元素的插入顺序
- TreeMap:基于红黑树实现,能够保证元素按照键的自然顺序或指定的比较器进行排序
- Hashtable:是一个古老的类,与 HashMap 类似,但它是线程安全的,不允许 null 作为键或值
示例代码:Map 实现类的使用
import java.util.HashMap;
import java.util.Hashtable;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
public class MapImplementationsDemo {
public static void main(String[] args) {
// 1. HashMap:不保证顺序
Map<String, String> hashMap = new HashMap<>();
hashMap.put("name", "Alice");
hashMap.put("age", "25");
hashMap.put("gender", "female");
hashMap.put("city", "New York");
System.out.println("HashMap:" + hashMap);
// 2. LinkedHashMap:保留插入顺序
Map<String, String> linkedHashMap = new LinkedHashMap<>();
linkedHashMap.put("name", "Bob");
linkedHashMap.put("age", "30");
linkedHashMap.put("gender", "male");
linkedHashMap.put("city", "London");
System.out.println("LinkedHashMap:" + linkedHashMap);
// 3. TreeMap:按键的自然顺序排序
Map<String, String> treeMap = new TreeMap<>();
treeMap.put("name", "Charlie");
treeMap.put("age", "35");
treeMap.put("gender", "male");
treeMap.put("city", "Paris");
System.out.println("TreeMap(按键排序):" + treeMap);
// 4. TreeMap:使用自定义比较器(按键的长度排序)
Map<String, String> treeMapWithComparator = new TreeMap<>((k1, k2) -> {
// 按键的长度排序
int lenCompare = Integer.compare(k1.length(), k2.length());
// 如果长度相同,按自然顺序排序
return lenCompare != 0 ? lenCompare : k1.compareTo(k2);
});
treeMapWithComparator.put("name", "David");
treeMapWithComparator.put("age", "40");
treeMapWithComparator.put("gender", "male");
treeMapWithComparator.put("city", "Tokyo");
System.out.println("TreeMap(按键长度排序):" + treeMapWithComparator);
// 5. Hashtable:线程安全,不允许null键或值
Map<String, String> hashtable = new Hashtable<>();
hashtable.put("name", "Eve");
hashtable.put("age", "45");
hashtable.put("gender", "female");
hashtable.put("city", "Sydney");
System.out.println("Hashtable:" + hashtable);
// 测试HashMap和Hashtable对null的处理
try {
hashMap.put(null, "test");
hashMap.put("testKey", null);
System.out.println("\nHashMap允许null键和null值:" + hashMap);
} catch (NullPointerException e) {
System.out.println("HashMap不允许null键或null值");
}
try {
hashtable.put(null, "test");
hashtable.put("testKey", null);
System.out.println("Hashtable允许null键和null值:" + hashtable);
} catch (NullPointerException e) {
System.out.println("Hashtable不允许null键或null值");
}
}
}
运行结果:
11.7 Collections 类
Collections 类是 Java 集合框架中的一个工具类,它提供了一系列静态方法,用于操作集合,如排序、查找、替换、同步化等。
示例代码:Collections 类的使用
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class CollectionsClassDemo {
public static void main(String[] args) {
// 创建一个List集合
List<Integer> numbers = new ArrayList<>();
numbers.add(3);
numbers.add(1);
numbers.add(4);
numbers.add(1);
numbers.add(5);
numbers.add(9);
numbers.add(2);
numbers.add(6);
System.out.println("原始列表:" + numbers);
// 1. 排序
Collections.sort(numbers);
System.out.println("排序后:" + numbers);
// 2. 反转
Collections.reverse(numbers);
System.out.println("反转后:" + numbers);
// 3. 随机打乱
Collections.shuffle(numbers);
System.out.println("随机打乱后:" + numbers);
// 4. 查找元素(二分查找,需要先排序)
Collections.sort(numbers); // 先排序
System.out.println("排序后用于查找:" + numbers);
int index = Collections.binarySearch(numbers, 5);
System.out.println("元素5的索引:" + index);
index = Collections.binarySearch(numbers, 7); // 查找不存在的元素
System.out.println("元素7的索引(不存在):" + index);
// 5. 查找最大和最小元素
int max = Collections.max(numbers);
int min = Collections.min(numbers);
System.out.println("最大元素:" + max);
System.out.println("最小元素:" + min);
// 6. 填充集合(替换所有元素)
Collections.fill(numbers, 0);
System.out.println("填充0后:" + numbers);
// 7. 复制集合
List<Integer> dest = new ArrayList<>();
// 目标集合需要有足够的容量
for (int i = 0; i < numbers.size(); i++) {
dest.add(0);
}
Collections.copy(dest, numbers);
System.out.println("复制后的目标集合:" + dest);
// 8. 替换元素
numbers.set(0, 1);
numbers.set(1, 2);
numbers.set(2, 1);
System.out.println("替换部分元素后:" + numbers);
Collections.replaceAll(numbers, 1, 10);
System.out.println("替换所有1为10后:" + numbers);
// 9. 统计元素出现次数
int count = Collections.frequency(numbers, 10);
System.out.println("元素10出现的次数:" + count);
// 10. 创建同步集合(线程安全)
List<Integer> syncList = Collections.synchronizedList(new ArrayList<>());
syncList.add(1);
syncList.add(2);
syncList.add(3);
System.out.println("同步List:" + syncList);
}
}
运行结果:
11.8 Stream API
Stream API 是 Java 8 引入的新特性,它提供了一种高效且易于使用的处理集合的方式。Stream API 可以对集合进行复杂的操作,如过滤、映射、排序等,同时支持并行处理。
11.8.1 流概述
流(Stream)是一系列元素的序列,它不是数据结构,不存储元素,而是通过管道从数据源(如集合、数组)中获取元素。Stream API 的操作可以分为中间操作和终端操作:
- 中间操作:返回一个新的流,可以进行链式操作,如
filter()
、map()
、sorted()
等 - 终端操作:返回一个结果或副作用,如
forEach()
、collect()
、count()
等
流的操作具有以下特点:
- 惰性执行:中间操作不会立即执行,直到终端操作被调用
- 一次性使用:流只能被消费一次,一旦执行了终端操作,流就不能再被使用
- 并行处理:支持并行流,可以充分利用多核处理器
下面是 Stream 操作的流程图:

11.8.2 创建与获得流
可以从集合、数组、值序列等创建流。
示例代码:创建流
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class CreateStreamDemo {
public static void main(String[] args) {
// 1. 从集合创建流
List<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Orange");
// 获取顺序流
Stream<String> streamFromList = fruits.stream();
System.out.println("从集合获取的顺序流:");
streamFromList.forEach(System.out::println);
// 获取并行流
Stream<String> parallelStreamFromList = fruits.parallelStream();
System.out.println("\n从集合获取的并行流:");
parallelStreamFromList.forEach(System.out::println);
// 2. 从数组创建流
String[] colors = {"Red", "Green", "Blue"};
Stream<String> streamFromArray = Arrays.stream(colors);
System.out.println("\n从数组获取的流:");
streamFromArray.forEach(System.out::println);
// 3. 从值序列创建流
Stream<String> streamFromValues = Stream.of("Java", "Python", "C++");
System.out.println("\n从值序列获取的流:");
streamFromValues.forEach(System.out::println);
// 4. 创建空流
Stream<String> emptyStream = Stream.empty();
System.out.println("\n空流的元素数量:" + emptyStream.count());
// 5. 创建无限流(需要限制大小,否则会无限执行)
System.out.println("\n从无限流中获取前5个元素:");
Stream.iterate(1, n -> n + 2) // 生成奇数的无限流
.limit(5)
.forEach(System.out::println);
// 6. 基本类型流
IntStream intStream = IntStream.range(1, 6); // 生成1到5的整数流
System.out.println("\n基本类型流(IntStream):");
intStream.forEach(System.out::println);
}
}
运行结果:
11.8.3 连接流和限制流
可以使用concat()
方法连接两个流,使用limit()
方法限制流的大小,使用skip()
方法跳过流中的前 n 个元素。
示例代码:连接流和限制流
import java.util.stream.Stream;
public class ConcatenateAndLimitStreamDemo {
public static void main(String[] args) {
// 创建两个流
Stream<String> stream1 = Stream.of("A", "B", "C");
Stream<String> stream2 = Stream.of("D", "E", "F");
// 连接两个流
Stream<String> concatenatedStream = Stream.concat(stream1, stream2);
System.out.println("连接两个流的结果:");
concatenatedStream.forEach(System.out::print);
System.out.println("\n");
// 使用limit()限制流的大小
System.out.println("限制流的大小为3:");
Stream.iterate(1, n -> n + 1) // 生成自然数的无限流
.limit(3)
.forEach(System.out::print);
System.out.println("\n");
// 使用skip()跳过前n个元素
System.out.println("跳过前2个元素:");
Stream.of(1, 2, 3, 4, 5)
.skip(2)
.forEach(System.out::print);
System.out.println("\n");
// 综合使用limit()和skip()实现分页效果
System.out.println("分页效果(第2页,每页2条):");
Stream.of(1, 2, 3, 4, 5, 6, 7, 8)
.skip(2) // 跳过前2条(第一页)
.limit(2) // 获取2条(第二页)
.forEach(System.out::print);
}
}
运行结果:
11.8.4 过滤流
使用filter()方法可以过滤流中的元素,只保留满足条件的元素。
示例代码:过滤流
java
import java.util.Arrays;
import java.util.List;
public class FilterStreamDemo {
public static void main(String[] args) {
// 创建一个整数列表
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 过滤出偶数
System.out.println("偶数:");
numbers.stream()
.filter(n -> n % 2 == 0)
.forEach(n -> System.out.print(n + " "));
System.out.println("\n");
// 过滤出大于5的数
System.out.println("大于5的数:");
numbers.stream()
.filter(n -> n > 5)
.forEach(n -> System.out.print(n + " "));
System.out.println("\n");
// 过滤出大于3且小于8的数(组合条件)
System.out.println("大于3且小于8的数:");
numbers.stream()
.filter(n -> n > 3 && n < 8)
.forEach(n -> System.out.print(n + " "));
System.out.println("\n");
// 创建一个字符串列表
List<String> words = Arrays.asList("apple", "banana", "cherry", "date", "elderberry", "apricot");
// 过滤出长度大于5的字符串
System.out.println("长度大于5的字符串:");
words.stream()
.filter(s -> s.length() > 5)
.forEach(s -> System.out.print(s + " "));
System.out.println("\n");
// 过滤出以"a"开头的字符串
System.out.println("以'a'开头的字符串:");
words.stream()
.filter(s -> s.startsWith("a"))
.forEach(s -> System.out.print(s + " "));
System.out.println("\n");
// 过滤出包含"err"的字符串
System.out.println("包含'err'的字符串:");
words.stream()
.filter(s -> s.contains("err"))
.forEach(s -> System.out.print(s + " "));
System.out.println("\n");
// 综合过滤:长度大于5且包含字母'e'
System.out.println("长度大于5且包含字母'e'的字符串:");
words.stream()
.filter(s -> s.length() > 5 && s.contains("e"))
.forEach(s -> System.out.print(s + " "));
}
}
总结

一、泛型:类型安全的基石
泛型的核心价值在于编译时类型检查,避免了运行时的ClassCastException。主要知识点包括:
- 泛型类型:在类 / 接口定义时声明类型参数(如class Box<T>),使用时指定具体类型,实现 "一次定义,多种使用"
- 泛型方法:在方法声明中定义类型参数(如<T> T getLastElement(T[] array)),可独立于泛型类使用
- 通配符:?表示未知类型,配合上下界(? extends T/? super T)实现灵活的类型限制
- 类型擦除:泛型是编译期特性,运行时会被擦除为边界类型(默认Object),需注意反射操作可能破坏类型安全
二、集合框架:数据存储的骨架
Java 集合框架提供了统一的接口规范和实现类,主要分为Collection和Map两大体系:
- Collection 接口:存储单元素集合,主要子接口包括:
- List:有序可重复,支持索引访问(ArrayList/LinkedList/Vector)
- Set:无序不可重复(HashSet/TreeSet)
- Queue:先进先出队列(ArrayDeque/LinkedList)
- Map 接口:存储键值对映射,键唯一(HashMap/LinkedHashMap/TreeMap/Hashtable)
框架设计遵循 "接口 - 实现类" 分离原则,方便根据场景选择不同实现(如需要排序用 TreeSet,需要快速访问用 ArrayList)。
三、核心容器实现类特点
- List 实现类:
- ArrayList:基于动态数组,随机访问快,增删中间元素慢
- LinkedList:基于双向链表,增删快,随机访问慢,同时实现 Deque 接口
- Vector:线程安全的 ArrayList,性能较差,推荐使用Collections.synchronizedList()替代
2.Set 实现类:
- HashSet:基于哈希表,查询效率高(O (1)),无序
- TreeSet:基于红黑树,元素自动排序,查询效率 O (log n)
- LinkedHashSet:保留插入顺序的 HashSet,兼顾有序性和效率
3.Map 实现类:
- HashMap:哈希表实现,key 允许为 null,非线程安全
- TreeMap:按键排序的 Map,支持范围查询
- LinkedHashMap:保留插入顺序,可用于实现 LRU 缓存
四、集合操作实用工具
- Collections 类:提供静态工具方法,如排序(sort())、查找(binarySearch())、同步化(synchronizedList())等
- 集合转换:通过构造器或Arrays.asList()实现数组与集合的互转,注意Arrays.asList()返回固定大小的列表
- 集合运算:利用 Set 的addAll()/retainAll()/removeAll()实现并集、交集、差集运算
五、Stream API:集合处理的利器
Java 8 引入的 Stream API 彻底改变了集合处理方式,其核心特点是:
- 流水线操作:中间操作(filter()/map())返回新流,终端操作(forEach()/collect())产生结果
- 惰性执行:中间操作仅在终端操作触发时执行
- 并行处理:通过parallelStream()实现多线程并行处理
常用操作包括:
- 过滤(filter())、映射(map())、排序(sorted())
- 限制(limit())、跳过(skip())、连接(concat())
- 收集结果(collect(Collectors.toList()))
六、实践建议
- 容器选择原则:根据操作类型选择实现类(如频繁增删用 LinkedList,频繁查询用 ArrayList)
- 泛型使用:始终指定泛型类型,避免使用原始类型
- 线程安全:非并发场景避免使用 Vector/Hashtable,优先选择 ArrayList/HashMap
- 性能考量:
- 哈希集合需重写hashCode()和equals()方法
- 大集合排序推荐Collections.sort()而非手动实现
- 批量操作优先使用 Stream API,代码更简洁高效

通过本章学习,我们不仅掌握了各种容器的使用方法,更理解了不同数据结构的特性与适用场景,为编写高效、健壮的 Java 程序奠定了基础。