Skip to content

泛型 ​

一、 泛型的引入与好处 ​

1.1 传统方法的问题 ​

在集合中,使用传统方法(不使用泛型)存在两个主要问题:

  1. 类型不安全:不能对加入到集合中的数据类型进行约束,可以添加任意类型的对象。
  2. 效率低:遍历时,需要进行频繁的类型转换(Object -> 具体类型),数据量大时影响效率。

1.2 泛型的好处 ​

使用泛型可以解决上述问题:

  1. 类型安全:在编译时检查添加元素的类型,不符合则报错,提高了安全性。

  2. 提高效率:减少了遍历时不必要的类型转换次数。

    • 不使用泛型:Dog -> Object (存入) -> Dog (取出,需强转)
    • 使用泛型:Dog -> Dog (存入) -> Dog (取出,无需强转)
  3. 消除警告:代码更加简洁、健壮,不再有编译警告。

二、 泛型介绍 ​

2.1 基本概念 ​

  • 泛型:又称参数化类型,是 JDK 5.0 引入的新特性。
  • 核心思想:在类声明或实例化时,通过一个标识符来指定操作的数据类型。
  • 作用:可以在类声明时,用一个标识符表示类中某个属性的类型、某个方法的返回值类型或参数类型。
  • 优点:保证如果程序在编译时没有发出警告,运行时就不会产生 ClassCastException 异常。

三、 泛型语法与使用 ​

3.1 泛型的声明与实例化 ​

  • 声明:在类名或接口名后使用尖括号 <> 定义泛型标识符,如 class Person&lt;E>, interface List&lt;T>。
  • 实例化:在创建对象时,在尖括号 <> 中指定具体的类型。
java
// 声明一个泛型类
class Person<E> { ... }
// 实例化,指定 E 为 String 类型
Person<String> person = new Person<String>("韩顺平教育");

3.2 泛型使用的注意事项和细节 ​

  1. 类型限制:泛型标识符 T, E 等只能是引用类型,不能是基本数据类型。
java
List<Integer> list1 = new ArrayList<Integer>(); // OK
// List<int> list2 = new ArrayList<int>(); // 错误
  1. 继承关系:在给泛型指定具体类型后,可以传入该类型或其子类类型。
  2. 使用形式:
java
// 完整写法
ArrayList<Integer> list1 = new ArrayList<Integer>();
// 简化写法(类型推断),推荐使用
ArrayList<Integer> list2 = new ArrayList<>();
// 未指定泛型,默认为 Object
ArrayList list3 = new ArrayList(); // 等价于 ArrayList<Object> list3 = ...

四、 自定义泛型 ​

4.1 自定义泛型类 ​

  • 基本语法:class 类名&lt;T, R...>

  • 注意细节:

    1. 普通成员(属性、方法)可以使用泛型。
    2. 不能初始化使用泛型的数组(因为 new 时无法确定 T 的具体类型)。
    3. 静态成员(属性、方法)中不能使用类的泛型(因为静态成员在类加载时初始化,而泛型类型在创建对象时才确定)。
    4. 泛型类的类型,是在创建对象时确定的。
    5. 如果在创建对象时没有指定类型,则默认为 Object。

4.2 自定义泛型接口 ​

  • 基本语法:interface 接口名&lt;T, R...>

  • 注意细节:

    1. 接口中,静态成员也不能使用泛型(同泛型类)。
    2. 泛型接口的类型,在继承接口或实现接口时确定。
    3. 没有指定类型,默认为 Object。

4.3 自定义泛型方法 ​

  • 基本语法:修饰符 &lt;T, R...> 返回类型 方法名(参数列表)

  • 注意细节:

    1. 泛型方法可以定义在普通类中,也可以定义在泛型类中。
    2. 当泛型方法被调用时,类型会确定。
    3. public void eat(E e) {} 这样的方法,其泛型 E 依赖于类定义的泛型,不是泛型方法。
    4. public &lt;T> void fly(T t) {} 这样的方法,其泛型 T 独立于类,是泛型方法。

五、 泛型的继承和通配符 ​

5.1 泛型没有继承性 ​

  • 虽然 String 是 Object 的子类,但 List&lt;String> 并不是 List&lt;Object> 的子类。它们是并列关系,没有继承关系。

5.2 泛型通配符 ​

为了表示泛型之间的某种关系,引入了通配符 ?。

  1. <?>:无界通配符

    • 表示可以接受任意的泛型类型。
    • List<?> 是 List&lt;String>, List&lt;Object> 等各种泛型 List 的父类。
    • 注意:不能向 List<?> 中添加除 null 以外的任何元素(因为类型不确定),但可以读取,读取出的类型是 Object。
  2. <? extends A>:上界通配符

    • 表示可以接受 A 类以及 A 类的子类(不限于直接子类)。
    • 规定了泛型的上限。
    • 同样,不能向 List<? extends A> 中添加除 null 以外的任何元素(因为无法确定具体是哪个子类),但可以读取,读取出的类型是 A。
  3. <? super A>:下界通配符

    • 表示可以接受 A 类以及 A 类的父类(不限于直接父类)。
    • 规定了泛型的下限。
    • 可以向 List<? super A> 中添加 A 及其子类对象(因为父类引用可以指向子类对象)。
    • 读取时,返回的类型是 Object。

六、 JUnit 单元测试(补充) ​

6.1 为什么需要 JUnit ​

  • 传统测试需要将代码写入 main 方法,测试多个功能时需要来回注销、切换,非常麻烦。
  • JUnit 可以直接运行一个方法进行测试,并给出相关信息,方便快捷。

6.2 基本介绍 ​

  • JUnit 是一个 Java 语言的单元测试框架。
  • 多数 Java 开发环境(如 IDEA)都已集成 JUnit 作为单元测试工具。

6.3 使用步骤(简述) ​

  1. 在需要测试的类中,导入 JUnit 相关包(通常由 IDE 自动完成)。
  2. 在需要测试的方法上添加 @Test 注解。
  3. 在方法体内编写测试代码。
  4. 右键点击方法名或类名,选择 Run ‘方法名()’ 或 Run ‘类名’ 执行测试。