JAVA泛型通配符T,E,K,V,?

前言

泛型是 JAVA1.5中引入的新特性,其带来的好处如下:

  1. 编译时的类型检查安全;
  2. 所有的强制装换都是隐式和自动的;
  3. 参数化类型,将所操作的数据类型被指定为一个参数,提高代码的重用率;

1. 泛型与Object的比较

1.1 类型检查

对于Object的属性,编译器是不会有类型检查的,一些问题只有在运行的时候才会抛异常,这样本身就是一个安全隐患,例子如下:

public class ObjectEntity {
    private Object pro;
	... getter,setter
}

public static void main(String[] args) {
      ObjectEntity objectEntity = new ObjectEntity();
      objectEntity.setPro(new double[][]{});
      Integer pro =(Integer) objectEntity.getPro();
  }

如果将类型改成泛型类,那就会是另一种情况,在编译的时候就会进行类型检查。

泛型

public class GenericsEntity<T> {
   private T pro;
   ... getter,setter
}

类型检查

1.2 自动转换

对于Object的属性,预知的情况下取出值的时候还要进行一次转换

 ObjectEntity objectEntity=new ObjectEntity();
 objectEntity.setPro("str");
 String str= (String) objectEntity.getPro();

对于泛型取出的值如果类型对应的上则无需手动转换

GenericsEntity<String> genericsEntity = new GenericsEntity<String>();
genericsEntity.setPro("str");
String str = genericsEntity.getPro();

总结一下

上面的示例使用了泛型的实体,强制转换,可以在编译时候检查类型安全。

2. T,E,K,V,?

2.1 T,E,K,V,?介绍

想必在平时的开发中会经常见到,这个几个泛型通配符 “T,E,K,V,?” ,那这些又是什么意思呢?

其实这个几个并没有什么区别,它们都是JAVA泛型通配符,之所有会经常见到这些通配符是因为代码的约定俗成,说白了这样写会增加代码的可阅读性,也就是说换成A-Z 之间的字母也都是可以的。

通常情况下T,E,K,V,?是这样约定的:

  1. ?:表示不确定的java类型;
  2. T(type):表示具体的Java类型;
  3. K V (key value) :表示Java中的 Key,Value;
  4. E(element):表示Element;

举个栗子

public class GenericsEntity<T> {
   private T pro;

    public T getPro() {
        return pro;
    }

    public void setPro(T pro) {
        this.pro = pro;
    }
}
public class GenericsEntity<A> {
   private A pro;

    public A geAPro() {
        return pro;
    }

    public void seAPro(A pro) {
        this.pro = pro;
    }
}

上面代码把T换成了A,效果是没有任何区别的,只不过T约定代表type,为了良好的可读性,所以还是按照约定规范将A替换成T会比较合适。

2.2 ? 无界通配符

在介绍,无界通配符前先看个例子。
有个超类Animal和几个子类,如狗,猫,鹦鹉,等…。
超类

public class Animal {
    public void call(){
     
    }
}

Dog类

public class Dog  extends Animal{
    @Override
    public void call() {
        System.out.println("汪汪汪");
    }
}

Cat类

public class Cat extends Animal{
    @Override
    public void call() {
        System.out.println("喵喵喵");
    }
}

现在我需要一个动物的列表

通常的想法是:

 List<Animal> listAnimals = new ArrayList<Animal>();

正确的做法:

List<? extends Animal> listAnimals

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

    public static void main(String[] args) {
        List<Dog> dogs = new ArrayList<>();
        // 不报错
        test(dogs);
        // 报错
        test1(dogs);
    }

    static void test(List<? extends Animal> animals) {
        for (Animal animal : animals) {
            animal.call();
        }
    }

    static void test1(List<Animal> animals) {
        for (Animal animal : animals) {
            animal.call();
        }
    }

当调用test1的时候会报如下的错误:

Error:(21, 15) java: 不兼容的类型: java.util.List<entity.Dog>无法转换为java.util.List<entity.Animal>

像 tes方法中,限定了上限,但是不关心具体类型是什么,所以对于传入的 Animal 的所有子类都可以支持,并且不会报错。而 tes1就不行。

2.3 上限通配符 < ? extends E>

上限:用 extends 关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。

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

  • 如果传入的类型不是 E 或者 E 的子类,编译不成功
  • 泛型中可以使用 E 的方法,要不然还得强转成 E 才能使用
static <T> void test(List<? extends T> animalSub, List<T> animals ){}

public static void main(String[] args) {
    List<Dog> dogs = new ArrayList<>();
    List<Animal> animals = new ArrayList<>();
    test(dogs,animals);
}

这边当animals 作为参数被传进去的时候,animalSub参数类型以及被限定为animals的子类

2.4 下界通配符 < ? super E>

下限: 用 super 进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至 Object;

在类型参数中使用 super 表示这个泛型中的参数必须是 E 或者 E 的父类。

static <T> void test(List<? super T> animalSub, List<T> animals ){}

public static void main(String[] args) {
    List<Dog> dogs = new ArrayList<>();
    List<Animal> animals = new ArrayList<>();
    test(animals ,dog);
}
2.5 ? 和 T 的区别
// 指定集合类型只能是T
List<T> arrayT=new ArrayList<T>();
// 集合中可以是任何类型,这种没有意义,一般在方法中,只是为了说用法
List<?> array=new ArrayList<?>();

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

// 可以
T t = operate();

// 不可以
? car = operate();

简单总结下:

T 是一个 确定的 类型,通常用于泛型类和泛型方法的定义,?是一个 不确定 的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。

2.5.1 通过 T 来 确保 泛型参数的一致性

通过 T 来 确保 泛型参数类型的一致性

static <T extends Number> void  test(List<T> ids,List<T> num){}
public static void main(String[] args) {
        // 可以保证,ids和num类型一致
        List<Number> ids = new ArrayList<>();
        List<Number> num = new ArrayList<>();
        test(ids, num);
 }

通配符是 不确定的,所以这个方法不能保证两个 List 具有相同的元素类型

static void  test(List<? extends Number> ids,List<? extends Number> num){}
public static void main(String[] args) {
        // 无法保证ids和num类型一致
        List<Integer> ids = new ArrayList<>();
        List<Double> num = new ArrayList<>();
        test(ids, num);
}
2.5.2 类型参数可以多重限定而通配符不行

接口

public interface InterfaceA {};
public interface InterfaceB {};

实现类

public class ImplAB implements InterfaceA, InterfaceB {}

指定一个类型参数同时满足 InterfaceA 和 InterfaceB

static <T extends InterfaceA & InterfaceB> void test2(T t) {}
public static void main(String[] args) {
       ImplAB implAB = new ImplAB();
       test2(implAB);
}

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

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

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

T extends A

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

? extends A
? super A

3.Class<T> 和 Class<?> 区别

前面介绍了 ? 和 T 的区别,那么对于,Class<T> 和 Class<?> 又有什么区别呢?
Class<T> 和 Class<?>

最常见的是在反射场景下的使用,这里以用一段反射的代码来说明下:

 ImplAB implAB=(ImplAB)Class.forName("entity.ImplAB").newInstance();;

对于上述代码,在运行期,如果反射的类型不是 ImplAB类,那么一定会报 java.lang.ClassCastException 错误。

对于这种情况,则可以使用下面的代码来代替,使得在在编译期就能直接 检查到类型的问题:

static <T> T newInstance(Class<T> clazz) throws IllegalAccessException, InstantiationException {
        return clazz.newInstance();
    }
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        ImplAB implAB = newInstance(ImplAB.class);

    }

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

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

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

public class clazz<T> {
    public Class<?> clazzPro;
    // 不会报错
    public Class<T> clazzTPro;
 }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值