泛型类和接口

1. 概述

泛型(Generics)是Java编程语言中的一项强大功能,它允许你编写可以处理多种数据类型的代码,而不需要为每种数据类型编写单独的实现。泛型类和接口是泛型编程的基础,它们使得代码更加灵活、可重用和类型安全。

2. 泛型类的定义

泛型类是使用类型参数(Type Parameter)定义的类。类型参数通常用大写字母表示,如T、E、K、V等。泛型类的定义语法如下:

public class GenericClass<T> {
    private T value;

    public GenericClass(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

在这个例子中,GenericClass是一个泛型类,它有一个类型参数T。T可以是任何数据类型,如Integer、String、Double等。

2.1 使用泛型类

你可以通过在实例化泛型类时指定具体的类型来使用它:

public class Main {
    public static void main(String[] args) {
        GenericClass<Integer> intObj = new GenericClass<>(10);
        System.out.println("Integer value: " + intObj.getValue());

        GenericClass<String> strObj = new GenericClass<>("Hello");
        System.out.println("String value: " + strObj.getValue());
    }
}

在这个例子中,我们分别创建了一个GenericClass<Integer>和一个GenericClass<String>对象,并分别存储了整数和字符串类型的值。

3. 泛型接口的定义

泛型接口的定义与泛型类类似,也是使用类型参数来定义的。泛型接口的定义语法如下:

public interface GenericInterface<T> {
    T getValue();
    void setValue(T value);
}

3.1 实现泛型接口

你可以通过在实现类中指定具体的类型来实现泛型接口:

public class GenericImpl<T> implements GenericInterface<T> {
    private T value;

    @Override
    public T getValue() {
        return value;
    }

    @Override
    public void setValue(T value) {
        this.value = value;
    }
}

3.2 使用泛型接口

你可以通过在实例化实现类时指定具体的类型来使用泛型接口:

public class Main {
    public static void main(String[] args) {
        GenericImpl<Double> doubleObj = new GenericImpl<>();
        doubleObj.setValue(3.14);
        System.out.println("Double value: " + doubleObj.getValue());

        GenericImpl<Boolean> boolObj = new GenericImpl<>();
        boolObj.setValue(true);
        System.out.println("Boolean value: " + boolObj.getValue());
    }
}

在这个例子中,我们分别创建了一个GenericImpl<Double>和一个GenericImpl<Boolean>对象,并分别存储了双精度浮点数和布尔类型的值。

4. 泛型方法

除了泛型类和泛型接口,Java还支持泛型方法。泛型方法是在方法声明中使用类型参数的方法。泛型方法的定义语法如下:

public class GenericMethodExample {
    public static <T> void printArray(T[] array) {
        for (T element : array) {
            System.out.println(element);
        }
    }
}

4.1 使用泛型方法

你可以通过在调用泛型方法时指定具体的类型来使用它:

public class Main {
    public static void main(String[] args) {
        Integer[] intArray = {1, 2, 3, 4, 5};
        GenericMethodExample.printArray(intArray);

        String[] strArray = {"A", "B", "C", "D"};
        GenericMethodExample.printArray(strArray);
    }
}

在这个例子中,我们分别调用了printArray方法来打印整数数组和字符串数组。

5. 类型擦除

Java的泛型是通过类型擦除(Type Erasure)实现的。类型擦除意味着在编译时,泛型类型参数会被替换为它们的边界类型(通常是Object),并且在运行时不会保留泛型类型的信息。

5.1 类型擦除的影响

类型擦除的影响包括:

  • 无法在运行时获取泛型类型的具体信息。
  • 无法创建泛型类型的数组。
  • 无法在泛型类型上使用instanceof操作符。

5.2 示例

public class TypeErasureExample {
    public static void main(String[] args) {
        List<String> stringList = new ArrayList<>();
        List<Integer> intList = new ArrayList<>();

        // 编译错误:无法创建泛型类型的数组
        // List<String>[] arrayOfLists = new List<String>[10];

        // 编译错误:无法在泛型类型上使用 instanceof
        // if (stringList instanceof List<String>) { }
    }
}

6. 通配符

通配符(Wildcard)是泛型编程中的一个重要概念,它允许你在泛型类型中使用不确定的类型。通配符用?表示。

6.1 无界通配符

无界通配符表示任何类型:

public void printList(List<?> list) {
    for (Object element : list) {
        System.out.println(element);
    }
}

6.2 上界通配符

上界通配符表示某个类型及其子类型:

public void printNumberList(List<? extends Number> list) {
    for (Number number : list) {
        System.out.println(number);
    }
}

6.3 下界通配符

下界通配符表示某个类型及其父类型:

public void addNumbers(List<? super Integer> list) {
    list.add(1);
    list.add(2);
}

7. 实践练习

7.1 练习1:实现一个泛型栈

实现一个泛型栈类GenericStack,支持push、pop和peek操作。

public class GenericStack<T> {
    private List<T> stack = new ArrayList<>();

    public void push(T item) {
        stack.add(item);
    }

    public T pop() {
        if (stack.isEmpty()) {
            throw new IllegalStateException("Stack is empty");
        }
        return stack.remove(stack.size() - 1);
    }

    public T peek() {
        if (stack.isEmpty()) {
            throw new IllegalStateException("Stack is empty");
        }
        return stack.get(stack.size() - 1);
    }

    public boolean isEmpty() {
        return stack.isEmpty();
    }
}

7.2 练习2:实现一个泛型队列

实现一个泛型队列类GenericQueue,支持enqueue、dequeue和peek操作。

public class GenericQueue<T> {
    private List<T> queue = new LinkedList<>();

    public void enqueue(T item) {
        queue.add(item);
    }

    public T dequeue() {
        if (queue.isEmpty()) {
            throw new IllegalStateException("Queue is empty");
        }
        return queue.remove(0);
    }

    public T peek() {
        if (queue.isEmpty()) {
            throw new IllegalStateException("Queue is empty");
        }
        return queue.get(0);
    }

    public boolean isEmpty() {
        return queue.isEmpty();
    }
}

8. 总结

泛型类和接口是Java泛型编程的基础,它们使得代码更加灵活、可重用和类型安全。通过类型参数,你可以编写处理多种数据类型的代码,而不需要为每种数据类型编写单独的实现。泛型方法、类型擦除和通配符是泛型编程中的重要概念,理解它们将帮助你更好地利用泛型功能。

通过实践练习,你可以更好地掌握泛型类和接口的使用,并能够在实际项目中灵活应用泛型编程技术。