跳转至

集合框架与泛型 - 泛型

1. 概述

在Java编程中,泛型(Generics)是一种允许类、接口和方法在定义时使用类型参数的机制。泛型的主要目的是提供类型安全,并减少类型转换的需要。通过使用泛型,我们可以编写更通用、更灵活的代码,同时避免运行时类型转换错误。

1.1 为什么需要泛型?

在没有泛型的情况下,Java集合类(如ArrayList)只能存储Object类型的对象。这意味着我们可以将任何类型的对象放入集合中,但在取出时需要进行强制类型转换。这种设计容易导致运行时错误,因为编译器无法在编译时检查类型是否匹配。

泛型的引入解决了这个问题,它允许我们在编译时指定集合中存储的对象类型,从而避免了运行时类型转换错误。

2. 泛型的基本概念

2.1 泛型类

泛型类是指具有一个或多个类型参数的类。类型参数在类定义时指定,并在实例化类时确定具体的类型。

// 定义一个泛型类 Box,T 是类型参数
class Box<T> {
    private T item;

    // 设置 item 的值
    public void setItem(T item) {
        this.item = item;
    }

    // 获取 item 的值
    public T getItem() {
        return item;
    }
}

// 使用泛型类
public class Main {
    public static void main(String[] args) {
        // 创建一个存储 Integer 类型的 Box
        Box<Integer> integerBox = new Box<>();
        integerBox.setItem(123);
        System.out.println("Integer Box: " + integerBox.getItem());

        // 创建一个存储 String 类型的 Box
        Box<String> stringBox = new Box<>();
        stringBox.setItem("Hello, Generics!");
        System.out.println("String Box: " + stringBox.getItem());
    }
}

解释: - Box<T> 是一个泛型类,T 是类型参数。 - 在实例化 Box 时,我们可以指定 T 的具体类型,如 IntegerString。 - 通过泛型,我们可以在编译时确保类型安全,避免了运行时类型转换错误。

2.2 泛型方法

泛型方法是指在方法定义时使用类型参数的方法。泛型方法可以在普通类或泛型类中定义。

// 定义一个泛型方法
public class GenericMethodExample {

    // 泛型方法,T 是类型参数
    public static <T> void printArray(T[] array) {
        for (T element : array) {
            System.out.print(element + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        // 使用泛型方法打印 Integer 数组
        Integer[] intArray = {1, 2, 3, 4, 5};
        printArray(intArray);

        // 使用泛型方法打印 String 数组
        String[] strArray = {"Hello", "World"};
        printArray(strArray);
    }
}

解释: - printArray 是一个泛型方法,<T> 表示类型参数。 - 在调用 printArray 时,编译器会根据传入的数组类型自动推断 T 的具体类型。 - 泛型方法可以处理不同类型的数组,而不需要为每种类型编写单独的方法。

2.3 泛型接口

泛型接口是指具有一个或多个类型参数的接口。实现泛型接口时,可以指定具体的类型参数。

// 定义一个泛型接口
interface Pair<K, V> {
    K getKey();
    V getValue();
}

// 实现泛型接口
class OrderedPair<K, V> implements Pair<K, V> {
    private K key;
    private V value;

    public OrderedPair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public K getKey() { return key; }
    public V getValue() { return value; }
}

// 使用泛型接口
public class Main {
    public static void main(String[] args) {
        // 创建一个存储 String 和 Integer 的 OrderedPair
        Pair<String, Integer> pair = new OrderedPair<>("One", 1);
        System.out.println("Key: " + pair.getKey() + ", Value: " + pair.getValue());
    }
}

解释: - Pair<K, V> 是一个泛型接口,KV 是类型参数。 - OrderedPair 类实现了 Pair 接口,并在实例化时指定了具体的类型参数。 - 通过泛型接口,我们可以定义更通用的接口,并在实现时指定具体的类型。

3. 泛型的通配符

泛型通配符(Wildcard)用于表示未知类型。通配符通常用于方法参数中,以增加方法的灵活性。

3.1 上界通配符

上界通配符(<? extends T>)表示类型参数是 T 或其子类。

// 定义一个方法,使用上界通配符
public class WildcardExample {

    public static void printList(List<? extends Number> list) {
        for (Number number : list) {
            System.out.print(number + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        List<Integer> intList = Arrays.asList(1, 2, 3);
        List<Double> doubleList = Arrays.asList(1.1, 2.2, 3.3);

        // 可以传入 Integer 或 Double 的 List
        printList(intList);
        printList(doubleList);
    }
}

解释: - printList 方法接受一个 List<? extends Number> 参数,表示可以传入 Number 或其子类的 List。 - 通过上界通配符,我们可以编写更通用的方法,同时保持类型安全。

3.2 下界通配符

下界通配符(<? super T>)表示类型参数是 T 或其父类。

// 定义一个方法,使用下界通配符
public class WildcardExample {

    public static void addNumbers(List<? super Integer> list) {
        for (int i = 1; i <= 5; i++) {
            list.add(i);
        }
    }

    public static void main(String[] args) {
        List<Number> numberList = new ArrayList<>();
        addNumbers(numberList);
        System.out.println("Number List: " + numberList);
    }
}

解释: - addNumbers 方法接受一个 List<? super Integer> 参数,表示可以传入 Integer 或其父类的 List。 - 通过下界通配符,我们可以向集合中添加特定类型的元素,同时保持类型安全。

4. 练习题

4.1 简单练习

  1. 编写一个泛型类 Pair,它包含两个类型参数 TU,并实现 getFirstgetSecond 方法。

4.2 中等练习

  1. 编写一个泛型方法 swap,它接受一个 List<T> 和两个索引,交换这两个索引处的元素。

4.3 复杂练习

  1. 编写一个泛型类 Stack<T>,实现栈的基本操作(pushpoppeek),并使用泛型确保类型安全。

5. 总结

  • 泛型允许我们在编译时指定类型参数,从而提高代码的类型安全性和灵活性。
  • 泛型类、泛型方法和泛型接口是泛型的主要应用形式。
  • 通配符(<? extends T><? super T>)用于增加方法的灵活性,同时保持类型安全。
  • 通过使用泛型,我们可以编写更通用、更健壮的代码,减少运行时错误。

通过本主题的学习,你应该能够理解泛型的基本概念,并能够在实际编程中应用泛型来编写更安全、更灵活的代码。