《Java基础》目录
例如:第一章 Python 机器学习入门之pandas的使用
文章目录
- 《Java基础》目录
-
- [6. 泛型](#6. 泛型)
-
- [6.1 泛型类](#6.1 泛型类)
- [6.2 泛型方法](#6.2 泛型方法)
- [6.3 类型变量的限定](#6.3 类型变量的限定)
- [6.4 泛型代码和虚拟机](#6.4 泛型代码和虚拟机)
-
- [6.4.1 类型擦除](#6.4.1 类型擦除)
- [6.4.2 转换泛型表达式](#6.4.2 转换泛型表达式)
- [6.4.3 转换泛型方法](#6.4.3 转换泛型方法)
- [6.4.4 总结](#6.4.4 总结)
- [6.5 限制与局限性](#6.5 限制与局限性)
-
- [6.5.1 不能用基本类型实例化类型参数](#6.5.1 不能用基本类型实例化类型参数)
- [6.5.2 运行时类型查询只适用于原始类型](#6.5.2 运行时类型查询只适用于原始类型)
- [6.5.3 不能创建参数化类型的数组](#6.5.3 不能创建参数化类型的数组)
- [6.5.4 `Varargs`警告](#6.5.4
Varargs
警告) - [6.5.5 不能实例化类型变量](#6.5.5 不能实例化类型变量)
- [6.5.6 泛型类的静态上下文中类型变量无效](#6.5.6 泛型类的静态上下文中类型变量无效)
- [6.5.7 不能抛出或捕获泛型类的实例](#6.5.7 不能抛出或捕获泛型类的实例)
- [6.5.8 可以取消检查型异常的检查](#6.5.8 可以取消检查型异常的检查)
- [6.5.9 擦除后的冲突](#6.5.9 擦除后的冲突)
- [6.6 泛型类型的继承](#6.6 泛型类型的继承)
- [6.7 通配符类型](#6.7 通配符类型)
-
- [6.7.1 通配符概念](#6.7.1 通配符概念)
- [6.7.2 通配符的超类限定](#6.7.2 通配符的超类限定)
- [6.7.3 无限定通配符](#6.7.3 无限定通配符)
- [6.8 反射和泛型](#6.8 反射和泛型)
-
- [6.8.1 泛型`Class`类](#6.8.1 泛型
Class
类)
- [6.8.1 泛型`Class`类](#6.8.1 泛型
6. 泛型
泛型可以使代码对不同类型的对象重用。
6.1 泛型类
泛型类就是有一个或多个类型变量的类。该节使用Pair
类进行示范,该类使我们只需要关注泛型,而不用为数据存储细节而分心。
java
public class Pair<T>
{
private T first;
private T second;
public Pair(T f,T s){first=f;second=s;}
public T getFirst(){return first;}
}
Pair
类引入了类型变量T,用尖括号括起来,放在类名的后面。泛型类可以有多个类型变量,用不同的变量表示。如public class Pair<T,U>
。
一般情况类型变量用短且大写的字母表示。
- E:表示集合的元素类型
- K,V:分别表示键和值的类型
- T:必要时可以使用相邻的字母U或S表示任意类型
6.2 泛型方法
除了定义泛型类,该可以在普通类中定义带类型参数的泛型方法。
java
class ArrayAlg{
public static <T> T getMiddle(T... a){
return a[a.length/2];
}
}
类型变量要放在修饰符的后面,并且放在返回类型的签名。
当进行调用的时候,可以把具体类型放在尖括号中,放在方法名签名:
String s =ArrayAlg.<String>getMiddle("a","b","c")
。
在这种情况下,可以省略类型参数,编译器可以直接进行类型转换。
String s=ArrayAlg.getMiddle("a","b","c")
。
这个利用了Java的自动装箱机制,但当同时出现了不同类型的参数的时候,有可能会导致报错,只需要调整一下传递的参数类型即可。
6.3 类型变量的限定
如果定义了一个比较大小的泛型方法,但是如何确定这个具体类型中具有comparaTo
的比较方法呢?
只需要在定义泛型方法的时候,对类型变量进行一个限定来实现这一要求。
public static <T extends Comparable> T min(T[] a)
。
实际上Comparable
接口本身就是一个泛型类型,而这里使用的是extend
而非implements
。
这里的
extends
是因为限定类型更接近于子类型的概念。同时多个限定可以用&
来连接
6.4 泛型代码和虚拟机
虚拟机没有泛型类型,只有普通类,所以编译器在编译过程中会进行类型擦除。
6.4.1 类型擦除
无论如何定义一个泛型类型,都会自动提供一个与其对应的普通类,此时类型变量就会被擦除,替替换为其限定类型(若无限定类型则是Object
类)。
6.4.2 转换泛型表达式
编写泛型方法时除了进行类型擦除,还有对其进行强制类型转换。
比如
java
Pair<Empty> b=...
Empty d=b.getFirst();
其中getFirst进行类型擦除后返回的是Object类型,编译器自动插入转换到Empty的强制类型转换。
访问同一个泛型字段的时候也要进行强制类型转换。
6.4.3 转换泛型方法
类型擦除也会出现在泛型方法中。
例如:
public static <T extends Comparable> T min(T[] a)
进行类型擦除后只剩下一个方法
public static Comparable min(Comparable[] a)
类型擦除会和多态产生冲突
- 没有返回值类型
java
class DateInterval extends Pair<LocalDate>{
public void setFirst(LocalDate f){
if(f.getFirst()>=0)
super.setFirst(f);
}
}
类型擦除后会出现两个同名方法:
public void setFirst(LocalDate f)
public void setFirst(Object f)
为解决这个方法,编译器在DateInterval
类中生成一个桥方法
public void setFirst(Object f){setFirst((LocalDate) f)}
- 带有返回值类型
虚拟机中会由参数类型和返回值类型共同指定一个方法。所以如果只使用桥方法,在下面的情况就会很奇怪。
java
class DateInterval extends Pair<LocalDate>{
public LocalDate getFirst(){
return (LocalDate) super.getFirst();
}
}
但是就会出现两种方法:
public LocalDate getFirst()
public Object getFirst()
这种情况可以通过对不同的方法生成字节码,虚拟机就可以正确的处理这种情况。
6.4.4 总结
对于Java的类型转换,会遇到一下事实:
- 虚拟机中没有泛型,只有普通的类和方法
- 所有的类型参数都会替换为他们的限定类型
- 会合成桥方法在类型擦除下保持多态
- 为保持类型安全性,必要时会插入强制类型转换
6.5 限制与局限性
Java中泛型类型的限制,大部分都是由于编译器进行类型擦除所引起的。
6.5.1 不能用基本类型实例化类型参数
不能用基本类型替代类型参数,所以没有Pair<double>
只有Pair<Double>
。
原因就是类型擦除后的Pair类只有Object字段,而Object不能存储double值。
6.5.2 运行时类型查询只适用于原始类型
虚拟机中的对象总有一个特定的非泛型类型。因此所有类型查询只产生原始类型。
例如a instanceof Pair<String>
实际上测试a instanceof Pair<T>
或强制类型转换
为了提示这一风险,在对泛型类型进行查询时,会得到一个编译器错误,或是警告。
6.5.3 不能创建参数化类型的数组
不能实例化参数化类型的数组。table=new Pair[10]
因为执行类型擦除后,就会变成Object[]
,数组会记住他的数据类型,如果数组试图存储其他类型的元素,就会抛出异常。
可以声明但不可以实例化创建
6.5.4 Varargs
警告
向参数可变长方法传递泛型参数。
为了调用这个方法,Java虚拟机创建一个数组来存放参数,就违反了不能实例化类型数组的规则。但不会报错,只会提供警告。
可以采用两种方法来解决。
- 为调用的方法增加注解
SuppressWarnings("unchecked")
- 直接使用
SafeVarargs
注解(该注解只能用于声明为static
,final
或private
的构造器或方法)
6.5.5 不能实例化类型变量
不能使用诸如new T()
的构造方法,因为不会有人希望会new一个Object
对象。
在java8
之后,最好的解决办法就是提供一个构造器表达式,比如
Pair<String> p=Pair.makePair(String::new);
该方法接受一个Supplier<T>
,这是一个函数式接口,表示一个无参函数且返回类型为T的函数。
传统的方法是使用Java的反射机制,同时不能使用
T.class
,需要适当设计API
6.5.6 泛型类的静态上下文中类型变量无效
不能在静态字段或方法中引用类型变量。例如
java
public class Singleton<T>{
private static T singleInstance;
}
进行类型擦除后,只会剩下包含一个singleInstance
字段的Singleton
类,所以禁止使用带有类型变量的静态字段和方法。
6.5.7 不能抛出或捕获泛型类的实例
既不能抛出也不能捕获泛型类的对象,实际上泛型类可以扩展Throwable
甚至都不是合法的。
catch
语句中不能使用类型变量,因为会导致不能编译。
但是在异常规范中,使用类型变量是允许的。
不允许:
java
public static <T extends Throwable> void do(){
try{
do...
}catch(T e){
...
}
}
允许:
java
public static <T extends Throwable> void do(){
try{
do...
}catch(Throwable e){
...
}
}
6.5.8 可以取消检查型异常的检查
Java异常处理的原则就是,必须为所有检查型异常提供一个处理器。不过可以利用泛型取消该机制。
使用该方法
java
@SuppressWarnings("unchecked")
static <T extends Throwable> void throws(Throwable t) throws T{
throw (T) t;
}
通过使用泛型类,擦除和注解,实现欺骗编译器,使其将异常识别为非检查型异常,达到消除Java类型系统的部分基本限制。
6.5.9 擦除后的冲突
当泛型类型被擦除后,不允许创建引发冲突的条件。
为了支持擦除转换,倘若两个接口类型是同一个接口的不同参数化,一个类或类型变量就不能同时作为这两个接口类型的子类。
6.6 泛型类型的继承
泛型类可以继承泛型类,但是泛型类的具体类型没有继承关系。
比如:Manager
和Employee
是继承关系,但是Pair<Manager>
和Pair<Employee>
没有继承关系。
同理,ArrayList<T>
继承了List<T>
,但是ArrayList<Manager>
和List<Manager>
没有继承关系。
6.7 通配符类型
6.7.1 通配符概念
在通配符类型中,允许类型参数发生变化,例如Pair<? extends Employee>
表示任何泛型Pair
类型,他的我类型参数是Employee
的子类。
加入要实现这个方法:
java
public static void print(Pair<Employee> p){
...
}
如8.6 所说,不能将Pair<Manager>
传递给这个方法,但是可以使用通配符解决这个问题。如:
java
public static void print(Pair<? extends Employee> p){
...
}
6.7.2 通配符的超类限定
通配符限定和类型变量限定很相似,但是还有附加的功能,就是可以指定一个超类型限定,如:
? super Manager
可以限制为Manager
的所有超类型。
6.7.3 无限定通配符
还可以使用根本无限顶的通配符,例如Pair<?>
。
带有无限定通配符的可以被任意Object对象调用原始类中的方法。
6.8 反射和泛型
6.8.1 泛型Class
类
Class
类是泛型的,例如String.class实际就是Class<String>
类的对象。
Class方法 | 说明 |
---|---|
T newInstance() |
返回无参构造器构造的一个新实例 |
T cast(Object obj) |
如果obj为null或有可能转换成类型T,则返回obj;否则抛出异常 |
T[] getEnumConstants() |
如果T是枚举类型,返回所有值组成的数组;否则返回null |
Class<? super T> getSuperclass() |
返回这个类的超类,如果不是类或Object对象,返回null |
Constructor<T> getConstructor(Class... parameterTypes) |
获得公共构造器 |
Constructor<T> getDeclaredConstructor(Class... parameterTypes) |
获得有给定参数类型的构造器 |
TypeVariable[] getTypeParameters() |
这个类型被声明为泛型,则获得泛型变量,否则获得长度为0的数组 |
Type getGenericSuperclass() |
获得这个类型所声明的超类的泛型类型;否则返回null |
Type[] getGenericInterfaces() |
获得这个类所声明的接口的泛型类型;否则返回长度为0的数组 |
Constructor方法 | 说明 |
---|---|
T newInstance(Object... parameters) |
返回用指定参数构造的新实例 |
Method方法 | 声明 |
---|---|
TypeVariable[] getTypeParameters() |
获得泛型类型变量;否则返回长度0的数组 |
Type getGenericReturnType() |
获得这个方法声明的泛型返回类型 |
Type[] getGenericParameterTypes() |
获得声明的泛型参数类型;否则返回长度0数组 |
TypeVariable 方法 |
声明 |
---|---|
String getName() |
获得这个类型变量的名字 |
Type[] getBounds() |
获得这个类型变量的子类限定,否则返回长度0数组 |
WildcardType 方法 |
声明 |
---|---|
Type[] getUpperBounds() |
获得子类限定;否则返回长度0数组 |
Type[] getLowerBounds() |
获得超类限定;否则返回长度0数组 |
ParameterizedType方法 | 声明 |
---|---|
Type getRawType() |
获得原始类型 |
Type[] getActualTypeArguments() |
获得参数化类型声明的类型参数 |
Type getOwnerType() |
如果是内部类型,返回其外部类型;否则返回null |
GenricArrayType 方法 |
声明 |
---|---|
Type getGenericComponentType |
获得数组类型声明的泛型元素类型 |