ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

关于泛型和协变、逆变

2022-06-11 02:03:29  阅读:148  来源: 互联网

标签:Plate 变量 逆变 类型 协变 泛型 new


关于泛型和协变、逆变

写在前边

在复习的过程中,发现对于泛型的一些性质以及其实现了解的不到位,所以写一个关于泛型及泛型中的协变、逆变等内容的相关博客。

泛型

泛型是参数化类型,即将原来具体的类型定义为参数形式,使用统一的类型遍历表达多种类型,而不明确具体的类型。

从本质上看,泛型实际上是一种延迟明确的方式,即把类型明确的任务推迟到调用方法或创建对象时完成。在运行时根据具体指定类型确定具体类型(编译成class文件时,会用指定类型替换类型变量“擦除”)。

泛型在运行时会被擦除,因此不能使用instanceof()方法来检查泛型内的类型。

泛型中传入不同的父子类型的泛型不具有父子关系。

泛型变量的三种使用形式:泛型类、泛型接口和泛型方法。

泛型类

类中声明了一个或多个泛型变量,它定义了一个或多个充当参数的类型变量,所有这些参数化类型在运行时共享同一个类。泛型类结构如下图所示。

image

泛型接口

如果接口声明了一个或多个类型变量,那么它就是泛型的。定义了一个或多个类型变量作为参数。

与泛型类相似,将类转化为接口理解即可。

Java Set

Set就是一个泛型接口的典型实例,设计并实现了Set<E>.
image

其部分操作如下:

image

实现类

泛型接口的实现类可以是非泛型的实现类、也可以是泛型的实现类。

非泛型的实现类如下图所示,在实现时直接给定类型变量的具体类型。
image

泛型的实现类,其实现类仍然是泛型。

image

泛型方法

泛型方法就是声明了一个或多个类型变量的方法,这些类型变量被称为方法的形式类型参数,形式类型参数的形式与类或接口的类型参数列表相同。其结构如下图所示:

image

注:

  • 泛型类/接口,是在实例化类的时候指明泛型的具体类型
  • 泛型方法,是在调用方法的时候指明泛型的具体类型

普通类中的泛型方法如下图所示:

image

返回类型前记得要写<T>!!!

泛型类中的泛型方法

下面给出一段泛型类中的泛型方法实例:

class GenericTest<T>{
    //下面的T同所在类的类型变量一致,show1不是泛型方法
    public void show1(T t){
        System.out.println(t.toString());
    }
    //下面的E是新的类型变量,只适用于此方法,show2是泛型方法
    public <E> void show2(E t){
        System.out.println(t.toString());
    }
    //下面的T是新的类型变量,同类的类型变量无关(即使名字一样)
    //show3是泛型方法
    public <T> void show3(T t){
        System.out.println(t.toString());
    }
}

通过这个实例可以得出结论:

泛型方法需要在返回类型前加一个新的类型变量声明,这个类型变量与同类的类型变量无关。

对于上述这个例子,给出如下客户端代码:

public static void main(String[] args){
    GenericTest<String> genericTest = new GenericTest<>();
    genericTest.show1("genericTest!"); //succeed, "genericTest!" 
    genericTest.show1(Integer.valueOf("1")); //compile error
    genericTest.show2(Integer.valueOf("1")); //succeed, 1
    genericTest.show2(person); //succeed, maybe name of person
    genericTest.show3(Integer.valueOf("1")); //succeed, 1
    genericTest.show3(person); //succeed, maybe name of person
}

泛型方法与静态方法之间的一个关系

  • 静态方法不能使用所在泛型类定义的泛型变量。
  • 如果静态方法也要使用泛型的话,必须将静态方法定义为泛型方法。

比如我们在实验2中,Graph接口的empty方法实现,就将此静态方法定义成了泛型方法。

image-20220611005542789

协变与逆变

概念

首先,协变和逆变是用来描述类型转换前后的继承关系的两个概念。以A 、B表示类型,f(⋅)表示类型转换(f(A)表示A转换后的类型),≤表示继承关系(比如,A≤B表示A是由B派生出来的子类),其定义可概括如下:
f(⋅)是协变(covariant)的,当A≤B时有f(A)≤f(B)成立;
f(⋅)是逆变(contravariant)的,当A≤B时有f(B)≤f(A)成立;
f(⋅)是不变(invariant)的,当A≤B时上述两个式子均不成立,即f(A)与f(B)相互之间没有继承关系。

数组是协变的

我们创建类型为 NumberInteger 类型的两个数组,并分别输出数组的类名,代码以及运行结果如下:

Number[] n1 = new Number[1];
Integer[] n2 = new Integer[1];
System.out.println(n1.getClass().getName());
System.out.println(n2.getClass().getName());
//java.lang.Number;
//java.lang.Integer;
//说明数组是协变的

泛型是不变的

给定一个以泛型编程构建的列表List<T>,并且B是A的子类型,范式List<B>不是List<A>的的子类型。由于泛型只存在于编译阶段,在运行时会进行类型擦除,所以导致泛型的不变性,List<A>和List<B>实际上是一种兄弟关系,其均属于Object的子类型。即如果我们运行以下代码:

List<Number> l1 = new ArrayList<>();
List<Integer> l2 = new ArrayList<>();
System.out.println(l1.getClass().getName());
System.out.println(l2.getClass().getName());
//结果如下
java.util.ArrayList
java.util.ArrayList

输出结果表面两个列表均属于 java.util.ArrayList 类,因为经过类型擦除后,列表中的 StringInteger 都被擦除掉,被替换为其上限类型(E,T extends E),导致其均属于 java.util.ArrayList 类。

类型擦除

编译后,泛型会被替换为其上限类型,如果未指定其上限,缺省为Object类型。

泛型中的协变

泛型是不支持数据协变的,但在实际设计数据类型的时候,这种协变却又是我们需要的,这个时候,我们就可以通过 通配符 以达到泛型协变的目的。

通配符

常用的通配符符号有T、E、K、V、?,运用通配符时会遵循约定:

  • ? 表示不确定的java类型
  • T 表示具体的java类型
  • K V 分别表示java键值中的key和value
  • E 表示Element

上界通配符 形如 <? extends E>

规定了参数化的类型可以是E及其子类,即确定了类型的上界,规定上界后,就可以使用对应的类型的操作

下界通配符 形如<? super E>

规定了参数化的类型可以是E及其父类,即确定了类型的下界。

例如我们创建了一个盘子类 Plate ,以及水果类 Fruit 以及其子类 Apple 定义如下:

class Plate<T> {
	private T item;
	
	public Plate(T t){
		item = t;
	}
}
class Fruit {}
class Apple extends Fruit {}

按照泛型常规方法实现我们的需求如下所示

Plate<Fruit> p = new Plate<Apple>(new Apple());
//代码编译报错
//Error:  cannot convert from Plate<Apple> to Plate<Fruit>

这时我们可以在定义水果盘子的时候加上通配符 “?” ,以解决相应的问题:

Plate<? extends Fruit> p = new Plate<Apple>(new Apple());

泛型中的逆变

泛型中的逆变与泛型中的协变相似,只不过完成的是从子类型到父类型的抽象。

仍然以上边的例子为基础,给出如下逆变代码:

Plate<? super Apple>wildcardApples =new Plate<Fruit>(new Fruit());

标签:Plate,变量,逆变,类型,协变,泛型,new
来源: https://www.cnblogs.com/hit-rhino54/p/16365114.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有