为什么会引入泛型
泛型的本质是为了参数化类型,也就是在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,这样就可以在不创建新类型的情况下,也可以限制类型
泛型出现的原因,就是为了解决类型转换的问题
我的理解是:
- 针对class:比如集合,在jdk1.5之前,集合里可以添加任何元素,没有限制,我们get的时候也需要强制成具体的类型。但是在引入泛型之后,我们使用集合需要传入具体的(类型参数),集合里的元素通过编译器的手段,就限制了类型的统一,我们在使用的时候也不需要强转
- 针对方法:在jdk1.5之前针对不同类型数据的相同操作,我们可能需要重载成多个方法,但是在引入泛型之后,我们可以使用一个方法,在使用方法的时候需要传入不同的(类型参数)
- 为了实现泛型,引入了一些不是普通class的类型,于是在引入泛型的同时也引入了类型系统(Type),来表示泛型的不同类型
(Type)类型系统
原始类型在会生成字节码文件对象,而泛型类型相关的类型并不会生成与其相对应的字节码文件(因为泛型类型将会被擦除),因此,无法将泛型相关的新类型与class相统一。因此,为了程序的扩展性以及为了开发需要去反射操作这些类型,就引入了Type这个类型。并且新增了ParameterizedType, TypeVariable, GenericArrayType, WildcardType四个表示泛型相关的类型,再加上Class,这样就可以用Type类型的参数来接受以上五种子类的实参或者返回值类型就是Type类型的参数。统一了与泛型有关的类型和原始类型Class。参考文章–Java中的Type类型详解
- 通过大量demo后的个人理解:
- GenericArrayType的实参类型包括ParameterizedType和TypeVariable和Class
- ParameterizedType的实参类型包括TypeVariable和WildcardType和Class
泛型的一些概念
- 泛型就是定义一种模板,它解决了类型转换的问题,比如
List<String> list = new ArrayList<>();String s = list.get(0);
在get
的时候返回值类型肯定是String
,不在需要强转 - Java泛型只是在语法上支持泛型,也是在语法上对泛型引用进行类型检查。添加了不符合泛型规定的类型会报错
- 泛型在编译阶段会进行所谓的泛型擦除
(Type Erasure)
,所以泛型类型检查也只是语法层面的检查
类型擦除
- 类定义中的类型参数
- 无限制类型擦除==类型参数没有限制时,会被直接替换为Object,即即形如
<T>
和<?>
的类型参数都被替换为Object。例如public class Point<?>{} 和public class Point<T>{}
都会被擦除为public class Object
- 有限制类型擦除==如形如
<T extends Number>
和<? extends Number>
的类型参数被替换为Number
,<? super Number>
被替换为Object,例如public class Point<? extends Number>{}
和public class Point<T extends Number>
都会被擦除为public class Number{}
,而public class <? super Number>
会被擦除为public class Object
- 无限制类型擦除==类型参数没有限制时,会被直接替换为Object,即即形如
- 方法中的类型擦除==擦除方法定义中的类型参数原则和擦除类定义中的类型参数是一样的
getClass
public class A { }
public class B extends A{ }
public class C extends B{ }
public class Demo3 {
public static void main(String[] args) {
A a = new A();
A b = new B();
A c = new C();
System.out.println(a.getClass());//class demo.generic.basic.demo.A
System.out.println(b.getClass());//class demo.generic.basic.demo.B
System.out.println(c.getClass());//class demo.generic.basic.demo.c
//可见getClass返回的是实际的类型,而不是应用的类型
}
}
常见的泛型使用方式
- 泛型类
- 泛型方法
- 泛型接口—设计到基于泛型接口的多态是利用桥接实现的
泛型的通配符
-
? extends Number
-
extend表明了类型实参的上界,类型实参可以Number及其子类型
public class TestJava { public static void main(String[] args) { /** * List<? extends Number> 是泛型,其真正的类型(参数化类型)可能是ArrayList<Integer>、ArrayList<Float>或者ArrayList<Long>或者其他Number的子类 * 总之其实际类型只有可能是上面实际类型中的一种,所以无法add,因为添加的类型不确定。但是可以get,因为取到的类型肯定是Number类型,所以称之为上界,称之为生产者(只能对外提供) */ List<? extends Number> list = new ArrayList<Integer>(); // List<? extends Number> list = new ArrayList<Float>(); // List<? extends Number> list = new ArrayList<Long>(); } }
List<? extends T> 是说 这个list放的是T或者T的子类型的对象,但是不能确定具体是什么类型,所以可以get(),不能add()(可以add null值),编译时擦除到类型T,即用T类型代替类型参数
-
-
<? super Number>
-
super表明了类型实参的下界,类型实参可以是Number或者Number的父类
public class TestJava { public static void main(String[] args) { /** * 表面了类型的下届,表示参数化的类型(参数化类型)可能是指定的类型或者其父类 * List<? super Number> 是泛型 其(参数化类型)可能是new ArrayList<Number>或者new ArrayList<Object>() * 总之实际类型只可能是上面中的一种,根据多态规则,所以add Number或者Number的子类型的数据肯定没问题,但是get就不确定是什么类型,所以称之为上界,称之为消费者 */ // List<? super Number> list = new ArrayList<Number>(); List<? super Object> list = new ArrayList<Object>(); } }
-
关于class的一些泛型操作
public class Shape { }
public interface Point { }
public interface Food { }
public class MixinClass extends Shape implements Point, Food { }
/**
* 父类BaseClass不是一个普通的class(Type),而是一个ParameterizedType
* 这个ParameterizedType的形参是<T,K extends Shape & Point & Food>,实参是<String,MixinClass>
*/
public class UserClass extends BaseClass<Integer,MixinClass>{
public static void main(String[] args) {
Type superclass = UserClass.class.getGenericSuperclass();
if (superclass instanceof ParameterizedType){
ParameterizedType type = (ParameterizedType) superclass;
System.out.println(type);//demo.generic.classDemo.BaseClass<java.lang.Integer, demo.generic.classDemo.MixinClass>
Type[] arguments = type.getActualTypeArguments();//返回实参的类型
for (Type argument : arguments) {
System.out.println(argument);//java.lang.Integer和demo.generic.classDemo.MixinClass
}
}
//有时候封装框架的时候需要用到这个知识点,有一个基类,其他的都继承他,根据继承的类型不同,做具体的操作
Type type = UserClass.class.getGenericSuperclass();
if (type instanceof ParameterizedType){
ParameterizedType parameterizedType = (ParameterizedType) type;
Class argument = (Class) parameterizedType.getActualTypeArguments()[0];
System.out.println(argument == Integer.class);//true 判断实际类型是不是某个class
System.out.println(Number.class.isAssignableFrom(argument));//true 判断实际类型是不是继承于某个类
}
}
}
/**
* TypeVariable
* 范型信息在编译时会被转换为一个特定的类型, 而TypeVariable就是用来反映在JVM编译该泛型前的信息。(通俗的来说,TypeVariable就是我们常用的T,K这种泛型变量)
*
* Java泛型编程的边界可以是多个,使用如<T extends A & B & C>语法来声明,其中只能有一个是类,并且只能是extends后面的第一个为类,
* 其他的均只能为接口(和类/接口中的extends意义不同)。
* 这里不能写成BaseClass<T,? extends Number>
*
* getBounds:返回当前类型的上边界,如果没有指定上边界,则默认为Object
*/
public class BaseClass<T,K extends Shape & Point & Food> {
public static void main(String[] args) {
TypeVariable<Class<BaseClass>>[] types = BaseClass.class.getTypeParameters();
for (TypeVariable<Class<BaseClass>> type : types) {
// System.out.println(type);//T,K
Type[] bounds = type.getBounds();//getBounds之所以返回数据,是因为类型可以有多个限制
for (Type bound : bounds) {
System.out.println(bound);
}
}
TypeVariable<Class<InnerClass>>[] typeParameters = InnerClass.class.getTypeParameters();//显然InnerClass不是泛型类,所以也就没有TypeVariable
for (TypeVariable<Class<InnerClass>> typeParameter : typeParameters) {
System.out.println(typeParameter);
}
}
static class InnerClass{
}
}
属性的泛型操作
public class FiledType<T> {
private Map<String, FiledType> map;//ParameterizedType
private Set<String> set1;//ParameterizedType
private Class<?> clz;//ParameterizedType,?是WildcardType
private List<T> paList;//ParameterizedType,T是TypeVariable
private Holder<String> holder;//ParameterizedType
private List<String> list;//ParameterizedType
private ArrayList<String> arrayList;//ParameterizedType
private Map.Entry<String, String> entry;//ParameterizedType
private List<? extends Number> wildCardList;//ParameterizedType,,? extends java.lang.Number是WildcardType
private String str;//Class
private Integer i;//Class
private Set set;//Class
private List aList;//Class
private T name;//TypeVariable
private T[] value;//GenericArrayType,T是GenericArrayType的实际参数类型,T是TypeVariable
private List<String>[] listArray;//GenericArrayType,List<String>是ParameterizedType
private List<? extends Number>[] wildArray;//GenericArrayType;List<? extends Number>是ParameterizedType;? extends Number是WildcardType
private int[] intArray;//Class
private String[] strArray;//Class
static class Holder<V> {
}
}
public class FiledDemo {
public static void main(String[] args) {
Field[] declaredFields = FiledType.class.getDeclaredFields();
for (Field declaredField : declaredFields) {
Type type = declaredField.getGenericType();
if (type instanceof ParameterizedType){//一个参数化类型对应一个原始类型,可能对应好多参数类型
ParameterizedType parameterizedType = (ParameterizedType) type;
System.out.println("FiledName:"+declaredField.getName()+"是ParameterizedType");
Type[] arguments = parameterizedType.getActualTypeArguments();
for (Type argument : arguments) {
if (argument instanceof WildcardType){
System.out.println(declaredField.getName()+"--的泛型参数"+argument.getTypeName()+"--是-WildcardType");
}else if (argument instanceof TypeVariable){
System.out.println(declaredField.getName()+"--的泛型参数"+argument.getTypeName()+"--是-TypeVariable");
}
}
}else if (type instanceof Class){
System.out.println(declaredField.getName()+"是Class");
}else if (type instanceof TypeVariable){
System.out.println(declaredField.getName()+"是TypeVariable");
}else if (type instanceof WildcardType){
System.out.println(declaredField.getName()+"是WildcardType");
}else if (type instanceof GenericArrayType){
System.out.println(declaredField.getName()+"是GenericArrayType");
GenericArrayType arrayType = (GenericArrayType) type;
Type componentType = arrayType.getGenericComponentType();//返回组成泛型数组的实际参数化类型,如List[] 则返回 List
if (componentType instanceof TypeVariable){
System.out.println("泛型数组的实际参数化类型:"+componentType+"是TypeVariable");
}else if (componentType instanceof ParameterizedType){
System.out.println("泛型数组的实际参数化类型:"+componentType+"是ParameterizedType");
}else if (componentType instanceof Class){
System.out.println("泛型数组的实际参数化类型:"+componentType+"是Class");//这个可能,如果原始类型是Class,那么这个属性的类型就不可能是GenericArrayType
}
}
}
}
}
方法的泛型操作
/**
* 泛型方法和类型擦除
* 在调用泛型方法时,可以指定泛型,也可以不指定泛型:
* 在不指定泛型的情况下,泛型变量的类型为该方法中的几种类型的同一父类的最小级,直到Object
* 在指定泛型的情况下,该方法的几种类型必须是该泛型的实例的类型或者其子类
*
* 其实在泛型类中,不指定泛型的时候,也差不多,只不过这个时候的泛型为Object,就比如ArrayList中,如果不指定泛型,那么这个ArrayList可以存储任意的对象。
*
* Java编译器是通过先检查代码中泛型的类型,然后在进行类型擦除,再进行编译。
*/
public class GenericMethod {
/**
* 泛型方法
* @param <T> 声明一个泛型T,定义泛型方法时,必须加上这个泛型声明
* @param t 参数为泛型类型
* @return 返回值也是泛型类型
*/
public <T> T getSubs(T t){
return t;
}
public static void main(String[] args) {
Integer add = GenericMethod.add(1, 2);//不指定泛型,这两个参数都是Integer,所以T为Integer
Number add1 = GenericMethod.add(1, 1.2);//不指定泛型,参数类型一个是Integer,一个是Float,取同一父类最小级,所以是Number
Serializable test = GenericMethod.add(1, "test");//不知道泛型,参数类型一个是Integer,一个是String,取同一父类最小级,所以是Object
Integer add2 = GenericMethod.<Integer>add(1, 1);//指定了泛型类型Integer,所以只能为Integer极其子类
// GenericMethod.<Integer>add(1,1.2)//编辑错误,指定了Integer,不能为Float
Number add3 = GenericMethod.<Number>add(1, 1.2);//指定为Number,所以可以为Integer和Float
ArrayList<String> list = new ArrayList<>();
list.add("p");
// list.add(1);//编辑不通过
//类型检查就是针对引用的,谁是一个引用(=左边的),用这个引用调用泛型方法,就会对这个引用调用的方法进行类型检测,而无关它真正引用的对象(=后边的)
ArrayList list1 = new ArrayList<String>();
list1.add("1");//编译通过
list1.add(1);//编译通过
}
public static <T> T add(T x,T y){
return y;
}
}
public class ReturnMethod<T> {
/**
* @return TypeVariable
*/
public <T> T getT(T parameter){
return parameter;
}
/**
* 返回值是class
*/
public void normalMethod(){
System.out.println("12");
}
/**
* 返回值是Class
*/
public String getName(){
return "parade0393";
}
/**
* @return ParameterizedType,实参类型T是TypeVariable
*/
public List<T> getListT(){
return new ArrayList<>();
}
/**
* @return ParameterizedType,实参类型? extends Number是WildcardType
*/
public List<? extends Number> getNum(){
return new ArrayList<Integer>();
}
/**
* @return ParameterizedType 实参类型是Class
*/
public List<String> getRe(){
return new ArrayList<String>();
}
}
/**
* 方法返回值泛型Demo
*/
public class ReturnDemo {
public static void main(String[] args) {
Method[] declaredMethods = ReturnMethod.class.getDeclaredMethods();
for (Method method : declaredMethods) {
Type returnType = method.getGenericReturnType();
if (returnType instanceof ParameterizedType){
System.out.println(method.getName()+"--是--ParameterizedType--"+returnType);
}else if (returnType instanceof TypeVariable){
System.out.println(method.getName()+"返回值是:TypeVariable--"+returnType);
}else if (returnType instanceof WildcardType){
System.out.println(method.getName()+"返回值是:WildcardType--"+returnType);
}else if (returnType instanceof Class){
System.out.println(method.getName()+"返回值是:Class--"+returnType);
}
}
}
}
/**
* 方法参数Demo
*/
public class ParameterMethod<T> {
/**
* @param value 本身是GenericArrayType,但是实参T是TypeVariable
* @param wildArray 本身是GenericArrayType,但实参是ParameterizedType,他的实参是WildcardType
*/
public void test(Map<Point, Shape> map,
Set<String> set1,
Class<?> clz,
List<String> list,
ArrayList<String> arrayList,
Map.Entry<String, String> entry,
List<? extends Number> wildCardList,
String str,
Integer i,
Set set,
List aList,
T name,
T[] value,
List<String>[] listArray,
List<? extends Number>[] wildArray,
int[] intArray,
String[] strArray) {
}
}
public class ParameterDemo {
public static void main(String[] args) {
Method[] methods = ParameterMethod.class.getDeclaredMethods();
for (Method method : methods) {
Type[] types = method.getGenericParameterTypes();
Parameter[] parameters = method.getParameters();
for (int i = 0; i < types.length; i++) {
Type type = types[i];
if (type instanceof ParameterizedType){
System.out.println("参数:"+parameters[i]+"是ParameterizedType类型");
ParameterizedType parameterizedType = (ParameterizedType) type;
Type[] arguments = parameterizedType.getActualTypeArguments();
for (Type argument : arguments) {
if (argument instanceof WildcardType){
System.out.println("\t实参类型"+argument.getTypeName()+"是WildcardType");
}else if (argument instanceof TypeVariable){
System.out.println("\t实参类型"+argument.getTypeName()+"是TypeVariable");
}
}
}else if (type instanceof GenericArrayType){
System.out.println("参数:"+parameters[i]+"是GenericArrayType类型");
GenericArrayType arrayType = (GenericArrayType) type;
Type componentType = arrayType.getGenericComponentType();
if (componentType instanceof ParameterizedType){
System.out.println("\t实参类型"+componentType.getTypeName()+"是ParameterizedType");
ParameterizedType parameterizedType = (ParameterizedType) componentType;
Type[] arguments = parameterizedType.getActualTypeArguments();
for (Type argument : arguments) {
if (argument instanceof TypeVariable){
System.out.println("\t实参类型"+argument.getTypeName()+"是TypeVariable");
}else if (argument instanceof WildcardType){
System.out.println("\t实参类型"+argument.getTypeName()+"是WildcardType");
}
}
}else if (componentType instanceof TypeVariable){
System.out.println("\t实参类型"+componentType.getTypeName()+"是TypeVariable");
}
}
}
}
}
}
协变和逆变
协变和逆变用来描述类型转换后的继承关系,其定义是:如果A、B是继承关系,f(x)表示类型转换,≤表示继承关系(比如A≤B表示A继承于B,也就是A是B的子类)
- f(x)是协变(covariant)的,当A≤B时,f(A)≤f(B)成立
- f(x)是逆变(contravariant)的,当A≤B时,f(B)≤f(A)成立
- f(x)是不变(invariant)的,当A≤B时,上述两个式子均不成立,即f(A)与f(B)之间没有继承关系
Java中的协变、逆变和不变
-
泛型
-
集合
List<Number> list = new ArrayList<Integer>(); List<Integer> list = new ArrayList<Number>();
上面两行代码绝不能通过编译:说明泛型集合是不变的
-
数组
public class Shape { } public class Circle extends Shape{ } Shape[] shapes = new Circle[10];//类型不会被擦除 List<Shape>[] shapeArr = new ArrayList<Circle>[10];//无法通过编译 类型会被擦除 List<Circle>[] circleArr = new ArrayList<Point>[10];//无法通过编译 ArrayList<String>[] lists = new ArrayList<String>[10];//无法通过编译
从上面示例代码可以看出泛型数组也是不变的,而数组是支持协变的
数组是相同类型数据的有序集合。数组描述的是相同类型的若干个数据。这里的类型可以是值类型(如int)或引用类型(如String),但必须是具体的类型。我理解的具体的类型就是可以生成字节码的类型,而范围不会生成相应的字节码(泛型信息运行时会擦除),所以不存在泛型数组
但是数组支持协变,所以在运行时有可能会产生CastException
-
方法
调用方法
result = method(n)
,传入n的类型应该是method形参的子类型,即typeof(n)<=typeof(methods′ s parameter),result应为返回值的基类型,即typeof(methods's return)≤typeof(result)
public class GetClass { public static void main(String[] args) { Object result = method(new Integer(2)); //correct Number result1 = method(new Object()); //error Integer result2 = method(new Integer(2)); //error } static Number method(Number num) { return 1; } }
在Java 1.4中,子类覆盖(override)父类方法时,形参与返回值的类型必须与父类保持一致:
class Super { Number method(Number n) { ... } } class Sub extends Super { @Override Number method(Number n) { ... } }
从Java 1.5开始,子类覆盖父类方法时允许协变返回更为具体的类型:
class Super { Number method(Number n) { ... } } class Sub extends Super { @Override Integer method(Number n) { ... } }
-