【Java集合框架】3 - 1 泛型

§3-1 泛型

3-1.1 泛型编程

泛型编程(generic programming),是一种编程思想。算法根据需要稍后指定的类型编写,在需要时,类型作为参数,提供特定类型的实例化算法。

Java 泛型(generics)是 JDK 5 所引入的一个新特性,提供了编译时类型安全检测机制。该机制允许程序员在编译时检测到非法的类型。

泛型的本质就是参数化类型,所操作的类型被指定为一个参数。这使得我们可以专注于算法设计本身,而不需要针对不同的数据类型,为相同的问题重写相同的算法。

:编写一个可以针对任何数据类型的数组进行排序的算法。若用非泛型的方法,针对不同的数据类型,我们就要重写不同的方法,而这些方法的算法逻辑却是完全相同的。这时,使用泛型,就可以让我们专注于算法设计本身,而无需进行这些荣誉的操作。

Java 中的泛型标识符

  • E:Element,表示元素,在集合中使用(集合中存放的是元素);
  • T: Type,表示 Java 类;
  • K: Key,表示键;
  • V: Value,表示值;
  • N: Number,数值类型;
  • ?:表示不确定的 Java 类型。

注意:一个泛型标识符对应着一个泛型类型参数,又称类型变量,用于指定一个泛型名称的标识符。类型参数只能代表引用数据类型,基本数据类型应当使用其对应包装类。

3-1.2 泛型方法

泛型方法允许在方法调用时接收不同类型的参数,根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。

泛型方法定义规则

  • 方法声明的返回值类型之前必须有一个参数类型声明部分,由尖括号 <> 分隔,声明该方法为泛型方法;
  • 参数类型声明部分可包含一个或多个类型参数,参数间用逗号 , 分隔;
  • 类型参数可以用作声明返回值类型,也可用作声明实际参数的类型(占位符);

示例:打印数组(使用 Arrays.toString() 的实现方式)

// 泛型方法:打印数组
public static <E> String printArray(E[] arr) {
    if (arr == null || arr.length == 0)
        return "[]";

    StringBuilder sb = new StringBuilder();
    sb.append("[");

    for (int i = 0; ; i++) {
        sb.append(arr[i]);

        if (i == arr.length - 1)
            return  sb.append("]").toString();
        else
            sb.append(", ");
    }
}

有界类型参数:有时我们希望限制类型参数的类型,例如,操作数字的方法可能只需要接受 Number 或其子类实例,这是有界类型参数。

声明有界类型参数,列出类型参数后,接着 extends 关键字,其后跟着上限。注意,extends 在一般意义上表示扩展(类的继承)或实现(接口实现)。

使用有界类型参数,可允许调用边界中(类或接口)所定义的方法,请看下面的例子。

示例:使用有界类型参数,编写一个冒泡排序的泛型方法

// 泛型方法:冒泡排序
// extends 关键字表继承/实现接口
public static <T extends Comparable<T>> void bubbleSort(T[] arr) {
    if (arr == null)
        throw new NullPointerException();
    if (arr.length == 0)
        throw new ArrayIndexOutOfBoundsException();

    for (int i = 0; i < arr.length - 1; i++) {
        for (int j = 0; j < arr.length - i - 1; j++) {
            //调用 Comparable 接口中的唯一方法 
            if (arr[j].compareTo(arr[j + 1]) > 0) {
                T temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

多重界限:一个类型参数可以有多个边界,不同边界用 & 分隔。具有多个边界的类型变量是边界中列出的所有类型的子类型。

<T extends A & B & C>

3-1.3 泛型类

泛型类的声明类似于非泛型类。

泛型类声明方法:在类名后紧跟类型参数声明部分。

示例:声明一个泛型类

class Box<T> {
    private T t;

    public Box() {
    }

    public Box(T t) {
        this.t = t;
    }
    
    public T getT() {
        return t;
    }
    
    public void setT(T t) {
        this.t = t;
    }

    public String toString() {
        return "Box{t = " + t + "}";
    }
}

在测试类中,实例化该泛型类,需要在尖括号中指定参数类型,且前后都需要含有尖括号

//JDK 7 以前的实例化语句
Box<String> box = new Box<String>("Hello");
//JDK 7 以后的实例化语句:后半句中的实际类型可省略
Box<String> box = new Box<>();

3-1.4 类型通配符

类型通配符一般是使用 ? 代替具体的类型参数,表是不确定的 Java 类。

示例List<?> 在逻辑上就是 List<String>, List<Integer> 等所有 List<具体类型实参> 的父类。见下例中的 getData() 方法。

import java.util.ArrayList;
import java.util.List;

public class GenericsTest {
    public static void main(String[] args) {
        List<Integer> intList = new ArrayList<>();
        List<String> strList = new ArrayList<>();
        List<Number> numList = new ArrayList<>();

        intList.add(123);
        strList.add("Hello world");
        numList.add(3.14159);

        getData(intList);
        getData(strList);
        getData(numList);
    }
    
    //? 类型通配符,不确定的Java类,所有具体类型实参的父类
    //List<?> 类型参数,使得 intList, strList, numList 可入参
    public static void getData(List<?> list) {
        System.out.println("Data: " + list.get(0));
    }
}

注意

  • ? 通配符也可以通过有界参数类型限定边界,方法相同(见前文);

  • ? 通配符可使用 super 定义下限,例如 <? super Number> 表示只能接受 Number 及其上层父类类型;

    <? extends T> 表示该通配符所代表的类型是 T 的子类(含自己);

    <? super T> 表示该通配符所代表的类型是 T 的父类(含自己);

    可以使用通配符,结合继承体系,限定传入参数的类型的范围;

  • 泛型不具备继承性,而数据具备继承性;

    泛型不具备继承性:传递类型时只允许所指定的类型,其他类型均不接受;

    public static method(ArrayList<Person> list) {...}
    

    此时,传递其它类型的 ArrayList 则会出错,例如 ArrayList<String> 等;

    数据具备继承性:传递数据时,仍可以通过多态与继承,传递不同类型的数据;

    仍以上述方法为例,若 Person 具有一个子类 Student,在该 ArrayList 中,可以添加其子类的数据:

    list.add(new Person("Zhang San", 20));
    list.add(new Student("Li Si", 20));
    list.add(new Student("Wang Wu", 20));
    

3-1.5 泛型接口

泛型接口就是带有类型参数声明的接口。

示例:Java 集合框架中声明了许多泛型接口,以 List 为例

public interface List<E> extends Collection<E> {...}

泛型接口的使用

  • 实现类中指定参数类型;

    这种情况下,实现类中只能允许使用所指定的具体类型参数,且要重写其中方法;

    public class MyList implements List<String> {...}
    
  • 实现类中继续沿用泛型接口的类型参数;

    这种情况下,实现类中的参数类型仍不确定,接口中的方法仍需要重写。

3-1.6 参考资料

Java 泛型 | 菜鸟教程 (runoob.com)

[有界类型参数 | JAVA8 官网笔记教程 (zq99299.github.io)](

posted @ 2023-08-02 22:25  Zebt  阅读(48)  评论(0)    收藏  举报