什么是 ArrayList?
ArrayList 是 Java 集合框架中最常用的一种实现,它是一个动态数组,意味着它的 size(大小)可以在运行时动态改变,不像普通的 Java 数组([])在创建时就必须指定固定的大小。

核心特点:
- 有序性: 元素按照插入的顺序存储。
- 可重复性: 允许存储重复的元素。
- 随机访问快: 因为是基于数组实现的,所以可以通过索引(
get(int index))快速访问任意位置的元素,时间复杂度为 O(1)。 - 增删慢: 在中间或开头插入/删除元素时,需要移动大量元素,时间复杂度为 O(n)。
什么是泛型?
在 Java 5 之前,ArrayList 可以存储任何类型的对象,但这会带来两个主要问题:
- 类型不安全: 你可以向
ArrayList中添加任何类型的对象,String,Integer,Date等。 - 类型转换麻烦且危险: 从
ArrayList中取出元素时,都需要进行强制类型转换,并且编译器无法在编译时检查这种转换是否正确,容易在运行时抛出ClassCastException。
泛型 就是为了解决这些问题而设计的,它允许你在定义类、接口或方法时,不指定具体的类型,而是用一个类型参数来代替,当使用这个类时,再传入具体的类型。
核心作用:

- 类型安全: 在编译期就能检查类型,防止将错误的类型数据存入集合。
- 消除强制类型转换: 编译器会自动进行类型转换,代码更简洁、更安全。
ArrayList 与泛型的结合使用
1 基本语法
ArrayList 泛型的基本语法是:
ArrayList<具体类型> listName = new ArrayList<具体类型>();
从 Java 7 开始,可以使用菱形操作符 <>,让编译器自动推断类型,使代码更简洁:
ArrayList<具体类型> listName = new ArrayList<>(); // 推荐
示例:创建一个只能存储 String 类型的 ArrayList
import java.util.ArrayList;
public class GenericArrayListExample {
public static void main(String[] args) {
// 创建一个只能存储 String 对象的 ArrayList
ArrayList<String> stringList = new ArrayList<>();
// 1. 添加元素
stringList.add("Hello");
stringList.add("World");
stringList.add("Java");
// 下面这行代码在编译时就会报错!
// stringList.add(100); // Error: incompatible types: int cannot be converted to String
// 2. 获取元素
// 不需要强制类型转换,编译器知道 list 里的元素都是 String
String firstElement = stringList.get(0);
System.out.println("第一个元素是: " + firstElement); // 输出: 第一个元素是: Hello
// 3. 遍历元素
System.out.println("--- 遍历列表 ---");
for (String item : stringList) {
System.out.println(item);
}
}
}
2 常用泛型 ArrayList 示例
示例1:存储 Integer (整数)

ArrayList<Integer> numberList = new ArrayList<>();
numberList.add(10);
numberList.add(20);
numberList.add(30);
// int num = numberList.get(0); // 错误,get() 返回的是 Integer 对象
int num = numberList.get(0); // 自动拆箱,Integer -> int
System.out.println("数字列表的第一个元素: " + num);
示例2:存储自定义对象
假设我们有一个 Student 类:
class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{name='" + name + "', age=" + age + "}";
}
}
现在我们创建一个 Student 列表:
import java.util.ArrayList;
public class StudentListExample {
public static void main(String[] args) {
ArrayList<Student> studentList = new ArrayList<>();
studentList.add(new Student("张三", 20));
studentList.add(new Student("李四", 22));
for (Student student : studentList) {
System.out.println(student);
}
}
}
泛型通配符
我们希望方法能够接受多种不同类型的 ArrayList,这时就需要使用通配符。
1 无界通配符
表示“任意类型”,当你只是想读取集合中的元素,而不关心其具体类型时使用。
public void printList(ArrayList<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
// 调用
ArrayList<String> stringList = ...;
ArrayList<Integer> intList = ...;
printList(stringList); // 可以
printList(intList); // 也可以
注意: 使用 时,你不能向集合中添加任何元素(除了 null),因为编译器不知道这个 代表什么类型,为了避免类型不安全,它禁止了写入操作。
ArrayList<?> list = new ArrayList<String>();
list.add("Hello"); // 编译错误!
list.add(null); // 可以,因为 null 是任何类型的子类型
2 上界通配符 ? extends T
表示“T 或 T 的子类”,当你需要从集合中读取元素,并且希望这些元素都是 T 或其子类的类型时使用。
场景: 假设 Animal 是父类,Dog 和 Cat 是子类,你希望一个方法可以接受 List<Dog> 或 List<Cat>。
class Animal { /* ... */ }
class Dog extends Animal { /* ... */ }
class Cat extends Animal { /* ... */ }
// 这个方法可以接受任何元素是 Animal 或其子类的 List
public void addAnimal(List<? extends Animal> animalList) {
// 可以安全地读取,因为取出来的一定是 Animal 或其子类
Animal a = animalList.get(0);
// 但是不能添加!
// animalList.add(new Dog()); // 编译错误!
// 因为 animalList 可能是 List<Cat>,你不能把 Dog 放进去。
}
// 调用
List<Dog> dogList = new ArrayList<>();
addAnimal(dogList); // 可以
List<Cat> catList = new ArrayList<>();
addAnimal(catList); // 可以
3 下界通配符 ? super T
表示“T 或 T 的父类”,当你需要向集合中写入元素,并且写入的元素都是 T 或其子类的类型时使用。
场景: 你想把一堆 Dog 对象放进一个 List 中,这个 List 可以是 List<Dog>,也可以是 List<Animal>,甚至是 List<Object>。
// 这个方法可以向 List 中添加 Dog 或其子类
public void addDog(List<? super Dog> dogList) {
// 可以安全地添加 Dog 或其子类
dogList.add(new Dog());
dogList.add(new Puppy()); // 假设 Puppy 是 Dog 的子类
// 但是读取出来的类型不明确,只能用 Object 接收
Object obj = dogList.get(0);
}
// 调用
List<Dog> dogList = new ArrayList<>();
addDog(dogList); // 可以
List<Animal> animalList = new ArrayList<>();
addDog(animalList); // 可以
List<Object> objectList = new ArrayList<>();
addDog(objectList); // 可以
泛型方法
除了在类级别使用泛型,你也可以在方法上使用泛型。
public class GenericMethodExample {
// 这是一个泛型方法,可以交换任意类型数组中的两个元素
public static <T> void swap(T[] array, int i, int j) {
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
public static void main(String[] args) {
String[] names = {"Alice", "Bob", "Charlie"};
System.out.println("交换前: " + names[0] + ", " + names[1]);
swap(names, 0, 1);
System.out.println("交换后: " + names[0] + ", " + names[1]);
Integer[] numbers = {10, 20, 30};
System.out.println("\n交换前: " + numbers[0] + ", " + numbers[1]);
swap(numbers, 0, 1);
System.out.println("交换后: " + numbers[0] + ", " + numbers[1]);
}
}
| 概念 | 描述 | 示例 |
|---|---|---|
ArrayList |
动态数组,可变大小,支持快速随机访问。 | ArrayList list = new ArrayList(); |
| 泛型 | 提供编译时类型安全,消除强制类型转换。 | ArrayList<String> list = new ArrayList<>(); |
| 类型参数 | <> 中的具体类型,如 String, Integer。 |
new ArrayList<String>() |
| 无界通配符 | 表示任意类型,通常用于只读。 | void print(List<?> list) |
上界通配符 ? extends T |
表示 T 或其子类,用于安全地读取。 |
void addAnimal(List<? extends Animal> list) |
下界通配符 ? super T |
表示 T 或其父类,用于安全地写入。 |
void addDog(List<? super Dog> list) |
| PECS 原则 | Producer Extends, Consumer Super,生产者用 extends,消费者用 super,记住这个原则能帮助你更好地选择通配符。 |
掌握 ArrayList 和泛型是编写健壮、类型安全和可维护的 Java 代码的关键一步,希望这个详细的解释对你有帮助!
