# 泛型概述

当增加了泛型的集合,可以记住集合中元素的类型,也可以检查元素的类型。
泛型的好处有:

  • 可以记住元素类型,避免强制类型转换,使代码更加简洁
  • 可以对元素类型进行检查,避免异常的发生,增加代码健壮性

# 泛型的使用

语法

// 指定一种类型
<类型>

// 指定多种类型
<类型, 类型, ···, 类型>
1
2
3
4
5

这样的写法又被称为“菱形”语法,它更好地简化了泛型编程。

父类名|接口名<类型> 变量名 = new 父类名|接口名<> {
	···
}
1
2
3

# 泛型类

语法

[修饰符] class 类名<类型形参> {
	···类型形参···
}
1
2
3

其中,类型形参可以为任意标识,常用:T、E、K、V
说明
虽然只定义了一个 类名<类型形参> 类,但在实际使用时可以根据泛型实参的不同产生无数个类。
示例

public class Container<T> {
    private T parameter;

    public T getParameter() {
        return parameter;
    }

    public void setParameter(T parameter) {
        this.parameter = parameter;
    }
}
public class Test {
    public static void main(String[] args) {
        // 不传入实参
        Container a = new Container();
        a.setParameter(123);
        System.out.println(a.getParameter());
        a.setParameter("123");
        // 需要进行强制类型转换
        String temp1 = (String) a.getParameter();
        System.out.println(temp1);

        System.out.println("------");

        // 传入实参
        Container<String> b = new Container<>();
        // b.setParameter(123);
        System.out.println(b.getParameter());
        b.setParameter("123");
        // 无需强制类型转换
        String temp2 = b.getParameter();
        System.out.println(temp2);
    }
}
// 输出结果
  123
  123
  ------
  null
  123
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# 泛型接口

语法

[修饰符] interface 接口名<类型形参> {
	···类型形参···
}
1
2
3

其中,类型形参可以为任意标识,常用:T、E、K、V
说明
虽然只定义了一个 接口名<类型形参> 接口,但在实际使用时可以根据泛型实参的不同产生无数个接口,因此可以:

  • 根据需要重写不同参数的方法
  • 与泛型类相接口,使得泛型类也可以实现泛型接口

示例

public interface Echo<T> {
    void echo(T parameter);
}
1
2
3

根据需要重写不同参数的方法

public class Test implements Echo<String>{

    @Override
    public void echo(String parameter) {
        System.out.println(parameter + "," + parameter);
    }

    public static void main(String[] args) {
        Test test = new Test();
        test.echo("Hello");
    }
}
// 输出结果
  Hello,Hello
1
2
3
4
5
6
7
8
9
10
11
12
13
14

泛型类实现泛型接口

public class Test2<T> implements Echo<T> {

    @Override
    public void echo(T parameter) {
        System.out.println(parameter + "," + parameter);
    }

    public static void main(String[] args) {
        Test2<String> test2 = new Test2<>();
        test2.echo("world");
    }
}
// 输出结果
  world,world
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 泛型方法

语法

[修饰符] <类型形参> 返回值类型 方法名(类型形参 参数名){
    方法体
    return 返回值;
}
1
2
3
4

其中,类型形参可以为任意标识,常用:T、E、K、V
说明
虽然只定义了一个方法,但在实际使用时根据泛型实参的不同可以适用于各种类型的数据。
与泛型类、泛型接口的区别在于:

  • 泛型类、泛型接口中,泛型形参在整个接口、类中有效 泛型方法中,泛型形参仅在方法中有效
  • 泛型类、泛型接口在创建对象、声明变量时显式指定泛型实参
    泛型方法可以在调用方式时由系统根据传入参数判断泛型实参

示例

public class Test {
    public static void main(String[] args) {
        doubleCall("123");
        doubleCall(123);
        doubleCall(66.6);
    }

    public static <T> void doubleCall(T parameter) {
        System.out.println(parameter.getClass());
        System.out.println(parameter + "+" + parameter);
    }
}
// 输出结果
  class java.lang.String
  123+123
  class java.lang.Integer
  123+123
  class java.lang.Double
  66.6+66.6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 泛型构造器

语法

[修饰符] <类型形参> 类名<类型形参> {
	···类型形参···
}
1
2
3

其中,类型形参可以为任意标识,常用:T、E、K、V
说明
与泛型方法类似,可以在调用时根据参数推断泛型形参的类型,也可以显式指定泛型形参的类型。
示例

public class Test {

    public <T> Test(T parameter) {
        System.out.println(parameter);
    }

    public static void main(String[] args) {
        Test test = new Test("abc");
    }
}
// 输出结果
  abc
1
2
3
4
5
6
7
8
9
10
11
12

# 类型通配符

为了表示泛型类的父类,可以使用类型通配符 ?

泛型类<?>
1
void fun(List<?> list) {
  ···
}
// 可以传入任意类型的泛型类实例
fun(list<String>)
fun(list<Double>)
fun(list<Object>)
fun(list<Student>)
1
2
3
4
5
6
7
8

调用方法时,可以传入任意类型的泛型类实例,因为 泛型类<?> 是所有泛型类实例的父类,因此可以接受任意类型的泛型类实例。
上限类型通配符

泛型类<? extends 父类名>
1

只能匹配类型为父类名的子类的泛型类实例,因此可以把父类名看作上限。
下限类型通配符

泛型类<? super 子类名>
1

只能匹配类型为子类名的父类的泛型类实例,因此可以把子类名看作下限。

# 泛型方法的重载

泛型允许设定通配符的上限和下限,因此允许在一个类中包含两个方法的定义,从而实现了泛型方法的重载。

[修饰符] <类型形参1> 返回值类型 方法名(类型形参1 参数名){
    方法体
    return 返回值;
}
[修饰符] <类型形参2> 返回值类型 方法名(类型形参2 参数名){
    方法体
    return 返回值;
}
1
2
3
4
5
6
7
8
public class Test {
  static  <T extends Double> void fun(T parameter) {
      System.out.println("Double");
  }

  static <T extends Integer> void fun(T parameter) {
      System.out.println("Integer");
  }

  static <T extends String> void fun(T parameter) {
      System.out.println("String");
  }

  public static void main(String[] args) {
      Test.fun(66.6);
      Test.fun(123);
      Test.fun("123");
  }
}
// 输出结果
  D:\Test
  D:\Test\hello
  D:\Test\hello
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23