Java

java 泛型通配符和边界

2021-09-09  本文已影响0人  zhbi98

1. 通配符

泛型中常用的通配符:

我们在定义泛型类,泛型方法,泛型接口的时候经常会碰见很多不同的通配符,比如 T,E,K,V ,?等等,这些通配符又都是什么意思呢?

  • ? 表示不确定的 java 类型
  • T (type) 表示具体的一个java类型
  • K V (key value) 分别代表java键值中的Key Value
  • E (element) 代表Element

比如在不确定泛型参数的具体类型时,可以使用?代替,比如下方的例子:
比如public void set(List<?> list)
在调用方法时,参数类型可以是任何参数,既可以是List<String>也可以是List<Integer>。

2. 边界

A<? extends B>是带通配符的参数化类型,它可以作为普通类型那样用于变量和方法声明等中。

用法例如下方的样式:

1. A<? extends B> a = ...;

2. public void foo(A<? extends B> a) { ... }

3. List<? extends Number> l;

<? extends Number> 表示上边界限定,泛型参数只能是Number及其子类,一旦实例化其他参数类型则会报错。
<? super Number> 表示下边界限定,泛型参数只能是Number和它的父类,如果传入的参数类型不是 Number 或者 Number 的子类,无法编译成功。
<?> 表示无界通配符,可以表示任意的参数类型。

image.png

3. T和?的区别

?和 T 都表示不确定的类型,区别在于我们可以对 T 进行操作,但是对 ? 不行,比如如下这种 :

// 可以操作
T t = operate();

// 不能操作
? car = operate();

T 是一个确定的类型(确定是因为通过 T 来确保泛型参数的一致性这方面来说的),通常用于泛型类和泛型方法的定义,?是一个 不确定 的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。

3.1. 通过 T 来确保多个泛型参数的一致性

// 通过 T 来确保泛型参数类型的一致性
public <T extends Number> void Hello(List<T> dest, List<T> src)

// 通配符类型是不确定的,所以这个方法不能保证两个 List 具有相同的参数类型
public void Hello(List<? extends Number> dest, List<? extends Number> src)

下面的例子由于泛型指定的数据类型是Number,但是在赋值时给出的数据类型是String,所以 发生数据类型无法相互转换的错误。改正错误的办法是赋值时使用Number的数据类型进行赋值。

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


class Generic {
    public <T extends Number> void genericTest(List<T> dest, List<T> src) {

    }
}


public class GenericLearn {
    public static void main(String[] args) {
        List<String> a = new ArrayList<>();
        List<String> b = new ArrayList<>();
        // List<String> a = new ArrayList<String>();
        // List<String> b = new ArrayList<String>();

        Generic generic = new Generic();
        generic.genericTest(a, b);
    }
}
----------------------------------------------------
Exception in thread "main" java.lang.RuntimeException: 
Uncompilable source code - Erroneous sym type: com.zhbi.source.Generic.genericTest
    at com.zhbi.source.GenericLearn.main(GenericLearn.java:22)
Java Result: 1

下方为改正错误后的代码:

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


class Generic {
    public <T extends Number> void genericTest(List<T> dest, List<T> src) {

    }
}


public class GenericLearn {
    public static void main(String[] args) {
        List<Number> a = new ArrayList<>();
        List<Number> b = new ArrayList<>();
        // List<Number> a = new ArrayList<Number>();
        // List<Number> b = new ArrayList<Number>();

        Generic generic = new Generic();
        generic.genericTest(a, b);
    }
}

3.2. 类型参数可以多重限定而通配符不行

使用 & 符号设定多重边界(Multi Bounds),指定泛型类型 T 必须是 A 和 B 的共有子类型,此时被修饰的变量就具有了所有限定的方法和属性。对于通配符?来说,因为它不是一个确定的类型,所以不能用于多重限定。

class Generic {
    public <T extends A & B> void genericTest(List<T> dest, List<T> src) {

    }
}

但是这两种数据类型必须为接口(interface)类型,比如在这里定义了两个接口A和B,同时在类中也实现了这两个接口,具体看下方的代码。

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


interface A {
    public void a();
}


interface B {
    public void b();
}


class Generic implements A, B {
    public <T extends A & B> void genericTest(List<T> dest, List<T> src) {

    }

    @Override
    public void a() {

    }

    @Override
    public void b() {

    }
}


public class GenericLearn {
    public static void main(String[] args) {
        List<Generic> a = new ArrayList<>();
        List<Generic> b = new ArrayList<>();

        Generic generic = new Generic();
        generic.genericTest(a, b);
    }
}

3.3. 通配符可以使用超类限定而类型参数不行

类型参数 T 只具有 一种 类型限定方式:
T extends A

但是通配符 ? 可以进行 两种限定:
? extends A
? super A

Class<T>在实例化的时候,T 要替换成具体类。Class<?>它是个通配泛型,? 可以代表任何类型,所以主要用于声明时的限制情况。比如,我们可以这样做申明:

// 可以
public Class<?> clazz;
// 不可以,因为 T 需要指定类型
public Class<T> clazzT;

所以当不知道定声明什么类型的 Class 的时候可以定义一 个Class<?>。

public class Test3 {
    public Class<?> clazz;
    // 不会报错
    public Class<T> clazzT;
}

那如果也想 public Class<T> clazzT; 这样的话,就必须让当前的类也指定 T :

public class Test3<T> {
    public Class<?> clazz;
    // 不会报错
    public Class<T> clazzT;
}
上一篇下一篇

猜你喜欢

热点阅读