Java泛型和泛型通配符

1 泛型

1.1 什么是泛型

泛型是 Java SE5 出现的新特性,泛型的本质是类型参数化或参数化类型,在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型。

1.2 泛型的好处

在没有泛型的情况的下,通过对类型 Object 的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是本身就是一个安全隐患。

那么泛型的好处就是在编译的时候能够检查类型安全,并且所有的强制转换都是自动和隐式的。

1.3 泛型的使用方式

泛型类,泛型接口,泛型方法

1.3.1 泛型类
1
2
3
4
5
6
7
8
9
10
11
public class Generic<T> {
private T key;

public T getKey() {
return key;
}

public void setKey(T key) {
this.key = key;
}
}

这里的T可以换成任意标识符,但约定俗成的常用E,T,K,N,V,?表示

1
2
3
4
5
6
E-Element(在集合中使用,因为集合中存放的是元素)
T-TypeJava类)
K-Key(键)
N-Number(数值类型)
V-Value()
?-表示不确定的java类型

如何实例化泛型类:

1
Generic<String> generic = new Generic<String>();
1.3.2.泛型接口
1
2
3
public interface Generator<T> {
public T method();
}

实现泛型接口,不指定类型:

1
2
3
4
5
6
class GeneratorImpl<T> implements Generator<T>{
@Override
public T method() {
return null;
}
}

实现泛型接口,指定类型:

1
2
3
4
5
6
class GeneratorImpl<T> implements Generator<String>{
@Override
public String method() {
return "szh";
}
}
1.3.3 泛型方法
1
2
3
4
5
public static <E> void printList(List<E> list){
for (E e : list) {
System.out.println(e);
}
}

使用:

1
2
3
4
5
//创建不同类型的list----String和Integer
List<String> strings = Arrays.asList("s", "z", "h");
List<Integer> integers = Arrays.asList(1, 2, 3);
printList(strings);
printList(integers);

注意: public static <E> void printList(List<E> list) 一般被称为静态泛型方法;在 java 中泛型只是一个占位符,必须在传递类型后才能使用。类在实例化时才能真正的传递类型参数,由于静态方法的加载先于类的实例化,也就是说类中的泛型还没有传递真正的类型参数,静态的方法的加载就已经完成了,所以静态泛型方法是没有办法使用类上声明的泛型的。只能使用自己声明的 <E>

2 泛型的通配符

2.1 无界通配符<?>

我有一个父类 Fruit 和几个子类,如Apple,Pear等,现在我需要一个水果的列表,我的第一个想法是像这样的:

1
List<Fruit> Fruits

但是老板的想法确实这样的:

1
List<? extends Fruit> fruits

为什么要使用通配符而不是简单的泛型呢?通配符其实在声明局部变量时是没有什么意义的,但是当你为一个方法声明一个参数时,它是非常重要的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static void countFruits(List<? extends Fruit> fruits) {
for (Fruit fruit : fruits) {
System.out.println(fruit.getName());
}
}

static void countFruits1(List<Fruit> fruits) {
for (Fruit fruit : fruits) {
System.out.println(fruit.getName());
}
}

public static void main(String[] args) {
List<Apple> apples = new ArrayList<>();
// 不会报错
countFruits(apples);
// 报错
countFruits1(apples);
}

运行后提示:

1
2
java: 不兼容的类型: java.util.List<fanxing.demo05.Apple>无法转换为
java.util.List<fanxing.demo05.Fruit>

所以,对于不确定或者不关心实际要操作的类型,可以使用无限制通配符<?>,表示可以持有任何类型。countFruits方法中,限定了上届,但是不关心具体类型是什么,所以对于传入的Fruit的所有子类都可以支持,并且不会报错。而 countFruits1 就不行。**LIst<?>相当于LIst<? extends Object>**

2.2 上界通配符 < ? extends E>

在类型参数中使用 extends 表示这个泛型中的参数必须是 E 或者 E 的子类,这样有两个好处:

  • 如果传入的类型不是 E 或者 E 的子类,编译不成功
  • 泛型中可以使用 E 的方法,要不然还得强转成 E 才能使用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class Test {

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

    public static void main(String[] args) {
    Test test = new Test();
    List<Integer> integers = new ArrayList<>(Arrays.asList(1,2,3));
    //通过
    test.print(integers);

    List<Double> doubles = new ArrayList<>(Arrays.asList(1D,2D,3D));
    //通过
    test.print(doubles);

    List<String> strings = new ArrayList<>(Arrays.asList("s", "z", "h"));
    //不通过
    // test.print(strings);
    }
    }
    上述代码中print方法只能传入NumberNumber子类的集合,传入String的集合后编译不通过

注意
对于上限通配符需要注意的一点就是使用上限通配符只能从结构中获取值而不能将值放入结构中。比如:

1
2
3
4
5
6
7
8
9
10
11
12
List<? extends Fruit> ls = new ArrayList<>();
Apple apple = new Apple();
Pear pear = new Pear();
Fruit fruit = new Fruit();
//报错
ls.add(fruit);
//报错
ls.add(apple);
//报错
ls.add(pear);
//不报错
Fruit fruit1 = ls.get(1);

2.3 下界通配符 <? super E>

顾名思义,下界通配符就是已经规定好了下界,只能传入参数必须是E或者是E的父类。

如下是一个三级继承

屏幕截图 2022-10-06 160330.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Test2 {

public void print(List<? super Pear> pears){
System.out.println("----");
}

public static void main(String[] args) {
Test2 test = new Test2();
List<Fruit> fruits = new ArrayList<>();
//通过
test.print(fruits);

List<Pear> pears = new ArrayList<>();
//通过
test.print(pears);

List<FragrantPear> fragrantPears = new ArrayList<>();
//不通过
test.print(fragrantPears);
}
}

以上代码可见Pear及其父类的集合可以传入print方法中,但是Pear的子类FragrantPear的集合却不行。

注意
对于下限通配符需要注意的一点就是使用下限通配符只能将值放入结构中或者将读取结果转换为Object。比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
List<? super Fruit> ls = new ArrayList<>();
Apple apple = new Apple();
Pear pear = new Pear();
Fruit fruit = new Fruit();
//不报错
ls.add(fruit);
//不报错
ls.add(apple);
//不报错
ls.add(pear);
//不报错
Object object = ls.get(1);
//不报错
Fruit fruit1 = (Fruit) ls.get(0);
//报错
Fruit fruit2 = ls.get(0);

2.4 多重边界限定

1
public static <T extends Number & Comparable<T>> T maximum(T x, T y, T z)

T是传递给泛型类的类型参数应该是Number类的子类型,并且必须包含Comparable接口。
如果一个类作为绑定传递,它应该在接口之前先传递,否则编译时会发生错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class GenericMoreTypeExtends { 
public static void main(String[] args) {
System.out.println(maxMum(1,2,3));
System.out.println(maxMum(1.1,2.2,3.3));
}

//限制类型参数T必须实现Comparable 和 必须是Number的子类,限制上界
public static <T extends Number & Comparable<T>> T maxMum(T x, T y, T z) {
T max = x;
if (y.compareTo(max) > 0) {
max = y;
}
if (z.compareTo(max) > 0) {
max = z;
}
return max;
}
}

System.out.println(maxMum("s","z","h"));编译则不会通过!


Java泛型和泛型通配符
https://huajframe.github.io/2020/10/02/Java基础/Java泛型和泛型通配符/
作者
HuaJFrame
发布于
2020年10月2日
许可协议