杰瑞科技汇

java arraylist泛型

什么是 ArrayList

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

java arraylist泛型-图1
(图片来源网络,侵删)

核心特点:

  • 有序性: 元素按照插入的顺序存储。
  • 可重复性: 允许存储重复的元素。
  • 随机访问快: 因为是基于数组实现的,所以可以通过索引(get(int index))快速访问任意位置的元素,时间复杂度为 O(1)。
  • 增删慢: 在中间或开头插入/删除元素时,需要移动大量元素,时间复杂度为 O(n)。

什么是泛型?

在 Java 5 之前,ArrayList 可以存储任何类型的对象,但这会带来两个主要问题:

  1. 类型不安全: 你可以向 ArrayList 中添加任何类型的对象,String, Integer, Date 等。
  2. 类型转换麻烦且危险:ArrayList 中取出元素时,都需要进行强制类型转换,并且编译器无法在编译时检查这种转换是否正确,容易在运行时抛出 ClassCastException

泛型 就是为了解决这些问题而设计的,它允许你在定义类、接口或方法时,不指定具体的类型,而是用一个类型参数来代替,当使用这个类时,再传入具体的类型。

核心作用:

java arraylist泛型-图2
(图片来源网络,侵删)
  • 类型安全: 在编译期就能检查类型,防止将错误的类型数据存入集合。
  • 消除强制类型转换: 编译器会自动进行类型转换,代码更简洁、更安全。

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 (整数)

java arraylist泛型-图3
(图片来源网络,侵删)
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

表示“TT 的子类”,当你需要从集合中读取元素,并且希望这些元素都是 T 或其子类的类型时使用。

场景: 假设 Animal 是父类,DogCat 是子类,你希望一个方法可以接受 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

表示“TT 的父类”,当你需要向集合中写入元素,并且写入的元素都是 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 代码的关键一步,希望这个详细的解释对你有帮助!

分享:
扫描分享到社交APP
上一篇
下一篇