【Java基础----泛型易混淆概念解析】

背景


泛型是JavaSE1.5的新特性,新型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
这种参数类型可以用在类、接口、方法的创建中,分被称为泛型类、泛型接口、泛型方法。

在Java反射包下涉及的泛型类有一个典型类型Class

[ public final class Class<T> implements Serializable, GenericDeclaration, Type, AnnotatedElement ]

常用注解与反射类型


关于反射与注解相关的常用类后一篇会重点讲述,现在这里简单描述加粗样式

  • GenericDeclaration:
    定义就是获取指定类上的被声明的参数化类型(K、T、V),
    这个参数可以通过TypeVariable[] K = clazz.getTypeParameters()

  • AnnotatedElement:
    定义是获取类上面的注解Annotaion信息,
    Annotation T getAnnotation(Class annoClzss)
    Annotation[] getAnnotations()

  • ParameterizedType extends Type:
    Type 在Java中是所有类型(如 原始类型、参数化类型、数组类型、类型变量和基元类型)的通用的超级接口。而
    ParameterizedType 表示的就是参数化类型类,如:如Collection<String>中String 就是参数化类型。
    通过下面这个方法就可以获取到当前泛型类中使用了哪些泛型参数数组如(java.lang.Double,java.lang.Integer)
    Type[] genericActualType= ParameterizedType.getActualTypeArguments()

  • TypeVariable:
    就是泛型里面的泛型变量K,V,T

  • Method:
    代码中获取某个防范的泛型参数的具体类型
    outParam = method.getGenericReturnType();
    inParam = method.getGenericParameterTypes();

泛型类


泛型类定义的泛型,在整个类中有效而且必须要当前泛型实力护才能使用泛型参数

List<Integer>  intList = new ArrayList();

这个具体的泛型类型Integer会跟随着实例intList实例的整个生命周期。使用泛型类可以解决重复业务的代码,实现业务颗粒的复用,同时使用泛型类型在编译阶段就可以确定并发现错误。类型的转换都是自动和隐式的,提高了代码的准确率和复用率。
关于泛型类可以参考ArrayList类和ArrayList.toArray(T[] array)方法。

泛型方法


普通方法如下

public E elementData(int index){
	return (E) dataArray[index];
}

泛型方法如下

public <E> E elementData(int index) {
	return (E)dataArray[index];
}

注意泛型犯法定义是,必须在返回值前面加上“< T >” ,表示当前方法为泛型方法,泛型方法还有一个特点就是入参里面一定要含有一个与泛型T相关的Class参数如(T.getClass), 这样做的目的就是指明在运行过程中需要的具体出参对象,比如

Map<String, List<Integer>> dataMap = JSON.parseObject("json", new TypeReference<Map<String, List<Integer>>>() {});

泛型类在代码实例化的时候才能确定具体的类型,泛型方法在代码调用的时候才能确定具体使用的真是类型,然后再进行代码编译时将具体泛型表示擦除,通过编译器进行泛型类型的转化。
一般的基础类型、实体类都会转成Object类,如果有通配符做上下界限制,则会转成上下界的类型。

静态泛型方法
此类方法是不可以反问类上定义的泛型,因为类上的泛型类型必须要在实例化才能确定使用,静态方法却遭遇类的实例化被加载到内存了,为了让方法可以操作一个运行期不确定的的泛型类型,我们可以将泛型定义在静态方法上面

public class Generic<T> {
	// 错
	private static T name; 
	// 错
	public static void testT(T t){
		....
	}
	// 正确泛型静态方法
	public static <E> void testE(E e) {
		......
	}
	// 正确泛型方法
	public <E> E getArrayClass(Class<E> c, T t) {
		......
	}
}

为什么要使用泛型方法呢?
因为泛型类要在实例化的时候才能指明参数化的类型,如果这个实例想换一个类型操作,又得重新new一次泛型类的实例,可能不过灵活,而泛型方法可以再调用的时候指明泛型类型,更加灵活些。

泛型的擦除


JVM并不知道泛型的存在,因为泛型在编译阶段就已经被处理成普通的类型和方法;
处理机制就是通过类型擦除,其规则如下:
如果泛型没有指定上下限,则用Object作为原始类型做一个强转
如果有上下界限定,比如使用XClass作为上或者下界,则擦除后使用XClass作为原始类型
如果有多个限定如" <T extends A1 & B1> ", 使用第一个边界类型A1作为原始类型

泛型和方法的重载


Jvaa编译器编译后的泛型被擦除,对于方法的重载有什么影响么?

code1:
public static void printArray(Object[] objs) {.......}
public static <T> void printArray(T[] objs) {......}

code2:
public static void printArray(Object[] objs) {......}
public static <T extends Person> void printArray(T[] objs) {.........}
  • code1:泛型会被擦除,也就是说在运行时期,T[]---->Object[], 因此不会构成重载, 但是他们有相同的擦除功能编译会报错
  • code2:表明接收的方法是Person子类,可以构成方法的重载,不会编译报错。

泛型反序列化


我们常常使用FastJson在反序列化复杂的集合类型对象的时候往往都需要借用TypeReference,其中核心方法就是:
在这里插入图片描述
Java编译器不是有泛型擦除么?为啥这样写泛型参数没有被擦除

Map<String, Integer> dataMap = JSON.parseObject("json", new TypeReference<Map<String, Integer>>(){});

泛型擦除不能一概而论,在运行期间如果方法中出现带泛型的内名内部类,那么泛型依然可以保留下来然后可以通过下面的代码获取到泛型正式类型, 其实new TypeReference<Map<String, Integer>>(){} 是实例化了一个TypeReference的匿名内部类实例

Type superClass = this.getClass().getGenericSuperclass();
ParameterizedType argType = (ParameterizedType)((ParameterizedType)superClass).getActualTypeArguments()[0];
Type[] argTypes = argType.getActualTypeArguments();

泛型不可变性


先看几个概念
协变:
如果 A 是 B 的父类,并且 A 类型的容器(如"List< A >") 也是 B 类型容器(如 “List< B >”) 的父类,
这种关系就称之为协变(父子关系任然保持一致)
逆变:
如果 A 是 B 的父类,,但是 A 类容器是 B 类容器的子类,则称之为逆变
不可变:
不论A、B两类是什么关系,A 类容器 和 B 类容器对象没有任何关系,称之为不可变关系
我举个栗子
在这里插入图片描述


从上面代码可以看出数组是具有协变性,泛型容器是具有不可变性,为了解决这个不可变性的问题于是就出现了一个新的技术手段,通配符功能 “ ?”,表示一个不确定的类型;一般是需要配合具类型的上下界来使用的,这样就可以是的泛型容器具有类似协变的特性,我举个栗子
在这里插入图片描述

泛型中的T、Object、?的真是区别

Object 在运行期间随便什么类型的实例他都能容纳,不会进行校验,这是一个常识。

T 是在编译期间就已经确定了固定类型,从此该线程里面所有用到的 T 是不能变换类型的。T从一开始就会限定这个类型

?如果没有做上下限界定,其实跟Object差不多,但是他们有使用上的区别,?类型的容器如果没上下界,对容器是不能操作的比如下面的栗子,?类型的容器可以初始化,但是不能添加元素。
从使用的角度来讲 “ ?” 适用于做上下界定,范围更小,Object范围最广没法控制。
在这里插入图片描述
好了,泛型相关的基础知识目前就涉及到这里,有错误或者遗漏的大家可以评论区留言哟。。。
下一期继续讲述反射与注解相关重要类。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值