1. Why ——引入通用机制的原因
如果需要实现字符串数组并允许动态调整大小,请考虑使用ArrayList聚合字符串对象。 但是,过一段时间后,我们希望实现一个可以调整大小的Date对象数组。 此时,您希望重用刚才编写的对String对象的ArrayList实现。
Java 5之前的ArrayList实现大致如下:
从上面的代码中可以看出,用于向ArrayList添加元素的add函数接收Object类型的参数,而从ArrayList获取指定元素的get方法也返回Object类型的对象。 可以将此ArrayList对象存储在Object对象数组elementData中,也可以存储ArrayList对象
基于继承的通用实现存在两个问题。 第一个问题涉及get方法,每次调用get方法时都会返回一个Object对象,每次都必须将类型转换为所需的类型。 这很麻烦。 第二个问题涉及add方法,如果将File对象添加到聚合了字符串对象的ArrayList中,编译器将不会生成意外的错误消息。
因此,从Java 5开始,ArrayList可以使用类型参数“type parameter”来指示ArrayList中元素的类型。 引入类型参数解决了上述两个问题,如以下代码所示。
在上面的代码中,编译器会“知道”ArrayList的类型参数String,并强制执行类型转换和类型检查。
2 .通用类
类属类(generic class )是具有一个或多个类型参数的类。 例如:
在上面的代码中,您可以看到泛型类型Pair的类型参数为t,u,放在类名之后的尖括号中。 这里的t是Type的首字母,表示类型的意思,常用的有e(element )、k (key )、V(value )等。 当然,这些字符完全可以不指向类型参数。
实例化泛型类时,只需用特定类型替换类型参数。 例如,实例化Pair类时,您将看到以下内容:
3 .通用方法
泛型方法是具有类型参数的方法,可以在泛型类和常规类中定义。 例如:
上述代码中的getMiddle方法是一种通用方法,用于定义类型变量位于修饰符之后和返回类型之前的格式。 以上的泛型方法可以对各种类型的数组调用。 如果您知道这些数组的类型是有限的,也可以重载实现,但编码效率要低得多。 调用上述通用方法的代码示例如下所示。
4 .限定类型变量
在某些情况下,泛型类或泛型方法希望对自己的类型参数进行进一步限制。 例如,您可能希望将类型参数限制为特定类的子类,或者限制为实现接口的类。 相关语法如下
(BoundingType是类或接口。)。 其中的BoundingType可以多于一个,用“”连接即可。
5.Java泛型类型的标记含义
E - Element (因为集合包含元素,所以在集合中使用) )。
T - Type(Java类)
K - Key (密钥)
V - Value (值)
N - Number (数值类型)
? -表示不确定的java类
s,u,V - 2nd,3rd,4th types
6 .注意事项
)1)无法在基本类型中实例化类型参数
换句话说,以下语句是非法的。
但是,我们可以用合适的包装类型代替。
)既不能抛出也不能捕获通用类实例
由于通用类扩展Throwable无效,因此无法抛出或捕获通用类实例。 但是,在异常声明中使用类型参数是有效的。
)3)参数类型数组不正确
在Java中,Object[]数组可以是任何数组的父类。 这是因为任何数组都可以上载到在定义时指定元素类型的父类的数组中。 考虑以下代码。
虽然上面的代码指定数组元素满足父类Object类型,但是与原始类型Pair的对象不同,它可以在编译时通过,并且在运行时抛出ArrayStoreException异常。
由于这些原因,假设Java可以使用以下语句声明和初始化通用数组:
在类型清除虚拟机后,pairs实际上是Pair[]数组,可以升级到Object[]数组。 在此处添加Pair对象可以通过编译和运行时检查,但由于要在此数组中存储Pair对象,因此会出现难以定位的错误。 因此,在Java中,不能以上述语句的形式声明和初始化泛型数组。
可以使用以下语句声明和初始化通用数组:
)4)无法实例化类型变量
不能以" new T(…(…)、" newt (…)、" T.class "等形式使用类型变量。 Java禁止的理由很简单,因为存在类型擦除,所以像“newt(…)这样的
语句就会变为”new Object(…)”, 而这通常不是我们的本意。我们可以用如下语句代替对“new T[...]“的调用:(5)泛型类的静态上下文中不能使用类型变量
注意,这里我们强调了泛型类。因为普通类中可以定义静态泛型方法,如上面我们提到的ArrayAlg类中的getMiddle方法。关于为什么有这样的规定,请考虑下面的代码:
我们知道,在同一时刻,内存中可能存在不只一个People类实例。假设现在内存中存在着一个People对象和People对象,而类的静态变量与静态方法是所有类实例共享的。那么问题来了,name究竟是String类型还是Integer类型呢?基于这个原因,Java中不允许在泛型类的静态上下文中使用类型变量。
7. 类型通配符
介绍类型通配符前,首先介绍两点:
(1)假设Student是People的子类,Pair却不是Pair的子类,它们之间不存在”is-a”关系。
(2)Pair与它的原始类型Pair之间存在”is-a”关系,Pair在任何情况下都可以转换为Pair类型。
现在考虑这样一个方法:
在以上的方法中,我们想要同时能够传入Pair和Pair类型的参数,然而二者之间并不存在”is-a”关系。在这种情况下,Java提供给我们这样一种解决方案:使用Pair extends People>作为形参的类型。也就是说,Pair和Pair都可以看作是Pair extends People>的子类。
形如” extends BoundingType>”的代码叫做通配符的子类型限定。与之对应的还有通配符的超类型限定,格式是这样的: super BoundingType>。
现在我们考虑下面这段代码:
以上代码的第三行会报错,因为wildchards是一个Pair extends People>对象,它的setFirst方法和getFirst方法是这样的:
对于setFirst方法来说,会使得编译器不知道形参究竟是什么类型(只知道是People的子类),而我们试图传入一个People对象,编译器无法判定People和形参类型是否是”is-a”的关系,所以调用setFirst方法会报错。而调用wildchards的getFirst方法是合法的,因为我们知道它会返回一个People的子类,而People的子类“always is a People”。(总是可以把子类对象转换为父类对象)
而对于通配符的超类型限定的情况下,调用getter方法是非法的,而调用setter方法是合法的。
除了子类型限定和超类型限定,还有一种通配符叫做无限定的通配符,它是这样的:>。这个东西我们什么时候会用到呢?考虑一下这个场景,我们调用一个会返回一个getPairs方法,这个方法会返回一组Pair对象。其中既有Pair, 还有Pair对象。(Student类和Teacher类不存在继承关系)显然,这种情况下,子类型限定和超类型限定都不能用。这时我们可以用这样一条语句搞定它:
对于无限定的通配符,调用getter方法和setter方法都是非法的。