Java基础 -- 04泛型<T>

目录

泛型概念:

泛型种类:

泛型接口

泛型类

泛型方法

        普通泛型方法

        静态泛型方法

    泛型构造函数

泛型进阶:

    泛型通配符

    泛型上界

    泛型下界

    泛型擦除

泛型注意事项

泛型的用法:

泛型的示例:

高阶篇


java 泛型:死记硬背的

代码中有个   不要担心,只要编译能通过就OK!

泛型 定义

定义在类上      class MyClass {}
定义在接口上  interface MyInterface {}
定义在方法上  public T myMethod( T arg) {}

泛型 运用

运用时,才能明确泛型T到底是个啥类型:
类实例化时      MyClass obj = new MyClass< String>() ; // 明确 T 是 String
方法被调用时  obj.myMethod( "stringxx") ;                                   // 明确 T 是 String(入参arg)

T 类型 的私有数据成员,要求类定义时一定要携带,这样才能编译通过:

public class Test {
    private T name; // 编译报错
}


public class Test {
    private T name; // 编译通过
}

泛型概念:

没有JDK5问世以前的老代码:

  • 先看第1个小示例:Integer类型数值求和a+b,Long类型数值求和a+b
public class GenericTest1
{
	public int sum(int a, int b){
		return a+b;
	}

	public long sum(long a, long b){
		return a+b;
	}
}

我们来分析一下第1个小示例,会发现两个方法除了类型不一样,几乎长得是一摸一样,像双包胎。试问?如果我们还有byte类型的求和,short类型的求和,float类型的求和。。。我们是不是要繁琐的机械的重写很多份这样的方法,这些方法仅仅参数类型和返回类型不一样而已,其它都一样。 那么,我们能不能像模具那样来写方法呢?比如:

public  T sum(T a, T b){

      return a+b;

}

这里的T就是个模具,整个方法就是模具式的方法。当你想把T当成byte就当成byte,想把T当成int就当成int,...随心所欲,看你真正使用时想给模具T什么样的真实身份。就是一个模具方法,足矣代表byte类型参数的求和,也能代表short类型的求和,也能代表int类型的求和,等等...恭喜你,当你对此有个认识的时候,你就开始慢慢摸进了泛型方法编码的领域

  • 再看第2个小示例:从ArrayList集合类中取出Integer对象
public class GenericTest2 {

	private ArrayList arr = new ArrayList();

	public void setIntValue(Integer value){
		arr.add(value);
	}

    public void setStrValue(String value){
        arr.add(value);
    }

	public Integer getIntValue(int idx){
		return (Integer)arr.get(idx);
	}
}

我们来分析一下第2个小示例,会发现getIntValue方法的内部,arr.get(i)每次都要进行类型强转(Integer)arr.get(i);

注意:这里是有风险的,因为既可以往arr中存放Integer类型的元素,也可以往arr中存放String类型的元素,所以取出时的类型强转有可能我们取出的元素想强转为Integer,但该元素却是String类型的,此时运行程序时就会报ClassCastException类型转换异常。那么,试问?如果我限定了能放入arr中的只能是Integer类型的元素的话,当我从arr中取出元素时,根本不用类型强制转换,铁定取出的元素是Integer类型的,由此我们可以这样来定义一个模具集合类,请看第2个小示例的改造:

public class GenericTest2 {

    private ArrayList arr = new ArrayList();

    public void setValue(T value){
        arr.add(value);
    }

    public T getIntValue(int idx){
        return arr.get(idx);
    }
}

这里的T就是个模具,整个ArrayList集合类就是模具式的类。当你想把T当成Integer就当成Integer,这样取出的元素铁定是Integer类型的,当你想把T当成String就当成String,这样取出的元素铁定是String类型的...随心所欲,看你真正使用这个模具时想给模具T什么样的真实身份。足矣明确取出的元素类型就是我当初放入时的元素类型,并不需要取出时进行类型强制转换...恭喜你,当你对此有个认识的时候,你就开始慢慢摸进了泛型类编码的领域

JDK5的到来,为我们提供了模具思想的API,只不过JDK5不叫模具,而是叫做泛型。

泛型的定义:参数化类型,也就是说类型是参数化(/模具)的,Java编译后才知道这个类型到底是个啥,类型T仅仅是个模具。泛型的表现形式有尖括号括起来T的整体,单单的一个T并不符合泛型的定义,而仅仅是利用了T而已。创建类型安全的代码,从而在编译时能够捕获类型不匹配错误,这是泛型的一个关键优势,也能体现JDK5引入泛型的意义。

泛型定义时:模具 或 有边界的

泛型使用时:   

     // 泛型 只能用于定义
     // 泛型 使用:new T(); 非法哦!
    //               需要明确T的类型 (比如: MyCls)
    //       或者 使用?通配符 (比如: MyCls)
    //       或者 使用?有界通配符 (比如: MyCls)
    //       或者 仅仅用来做obj的类型强制转换 (比如:(T)obj; )

泛型的思想:定义时模具,使用时明确类型,本质:类类型的校验,是否还要强制转换


泛型种类:有  是泛型的前提条件

    泛型接口

interface MyInterface {

    // 实现该接口的类,可以仅限定T,class MyClass implements MyInterface{ }
    // 实现该接口的类,可以扩容,class MyClass implements MyInterface{ }
}
public interface MyInterface {
    
    // 仅仅使用T的普通方法
    T xxxMethod();       // 返回值类型T
    T xxxMethod(T t);    // 参数类型T,返回值类型T
    void yyyMethod(T t); // 参数类型T,无返回值(这里举例无返回值,其实返回值是啥类型都行)
    
    // 的泛型方法
     T aaaMethod();       
     T aaaMethod(T t);    
     void bbbMethod(T t); 
}

public abstract class MyClass implements MyInterface{}   // OK
public abstract class MyClass implements MyInterface{} // OK


泛型类

class MyClass {

    // 为什么泛型类?目的:类内部的数据成员是泛型变量的成员,例如:private T a;

    // 该类被实例化时,已经明确了T到底是啥类型,所以编译时能做到类型检查和泛型擦除
}

// 使用时才明确泛型类的MyClass的T到底是个啥

MyClass obj = new MyClass<>(); // T为String

MyClass obj = new MyClass<>(); // T为Integer

........

泛型类想要解决的就是,类内的成员T,刚开始不知道,是个模具,但是当实例化该泛型类时,就知晓了某个确定的类型,这样可以该类运行时仅处理实例化时明确下来的确定类型,无需我们进行类型强制转换,降低了运行时的ClassCastException风险。

public class MyClass {
    
    // 仅仅使用T的数据成员
    private T t;

    //仅仅使用T的普通方法
    T xxxMethod(){ return (T)obj; 或 return t; }       //返回值类型T
    T xxxMethod(T t){ return (T)obj; 或 return t; }    //参数类型T,返回值类型T
    void yyyMethod(T t){}     //参数类型T,无返回值(其实返回值是啥类型都行)
    
    // 的泛型方法
     T aaaMethod(){ return (T)obj; 或 return t; }     
     T aaaMethod(T t){ return (T)obj; 或 return t; }    
     void bbbMethod(T t){}

    // 的静态泛型方法
    static  T mmmMethod(){ return (T)obj; 或 return t; }     
    static  T mmmMethod(T t){ return (T)obj; 或 return t; }    
    static  void nnnMethod(T t){}
}

泛型方法

        普通泛型方法

         returnType xxxMethod(T t) {} // 普通泛型方法的定义

        静态泛型方法

        static returnType xxxMethod(T t) {} // 静态泛型方法的定义

泛型方法场景
方法返回值类型 方法有参数的 方法无参数的

类型T

T xMtd(T t) {return (T)obj; }

T xMtd() { return (T)obj; }

类型XxClass

XxClass xMtd(T t) { return obj; }

XxClass xMtd() {return obj; }

void xMtd(T t) {} void xMtd() {}

泛型方法想要解决的就是:

第一种场景:方法除了参数类型和返回类型不一样,整体方法的业务逻辑是一致的,我们将这样的"多胞胎"式的多个方法用一个泛型方法来表示即可,泛型方法内部是想不论T明确后的类型到底是什么类型,最终都是超类中的超类方法。(实现面向接口编程(/多态性)的思想)。这种方法的编写常见结构为:T xxxMtd(T t){ return t; }

第二种场景:写了一个泛型方法,传入参数T,方法内部的逻辑使用instanceof来倒推t的明确类型,进而根据不同的明确类型,执行不同的分支的业务逻辑,最后泛型方法无返回值 或 返回T类型。这种方法的编写常见结构为:T xxxMtd(anyType yyy, Class c){ return (T)obj; }

一般泛型方法都是结合泛型类一起来使用的,因为这样泛型类在实例化的时候,就已经明确了泛型方法中的泛型T到底是什么类型。

泛型方法注意点:泛型方法的 和 泛型类的 不是同一个T

Class MyGeneric{

    public void xxMtd(T t){
        System.out.println(t.getClass().getName());
    }

    public   T yyMtd(T t){ // 泛型方法的T和泛型类上的T没关系
        return t;
    }
}

// 测试
public class Test{
    public static void main(String[] args){
        MyGeneric t = new MyGeneric();
        t.xxMtd("generic");
        Integer i = t.yyMtd(new Integer(1));
    }
}

/**说明:
 * 泛型类的实际类型参数是 String,
 * 而传递给泛型方法的类型参数是 Integer,两者不想干。
 * /

// 一般建议这样写 泛型类 和 泛型方法 共存的场景
Class MyGen {

    public void xxMtd(T t){
        System.out.println(t.getClass().getName());
    }

    public   E yyMtd(E e){ // 泛型方法用E和泛型类上的T分开来表示更清晰
        return e;
    }
}

    泛型构造函数

class MyClass{ MyClass(T arg){} } 构造函数前加以修饰。这种用的比较少,了解下即可。


泛型进阶:

    泛型通配符

?问号即是泛型通配符,表示未知类型。可以理解为,比T更广泛的范围。通配符不能用于泛型的定义,一般都是用于作为方法的入参来运用的。MyClass 可以代表 MyClass 也可以代表MyClass 也可以代表MyClass...通配符只能出现在运用泛型的地方,比如:方法的入参,方法的返回值通配符不能出现在泛型定义的地方


    泛型上界

  • 泛型上界:

当T想被明确时,只能明确的是SuperClass类型或者SuperClass的子类类型。使用泛型上界时,往集合里add()元素是非法的,此时编译不会通过。

/**
* List list 意指:使用时,可以是Date类型或者Date的子类型
*/
public void upperBound(List list, Date date)  
{  
    Date now = list.get(0);  
    System.out.println("now==>" + now);  
    // list.add(date); // 无法编译  
}  

// 为什么 list.add(date); 无法编译呢?看如下代码:

public void testUpperBound()  
{  
    List list = new ArrayList(); // 明确Timestamp 
    Date date = new Date();  
    upperBound(list,date); // 报错,因为已经明确Timestamp,就不能往集合添加Date类型 
}  

Java基础 -- 04泛型<T>_第1张图片

啥水果都能放的盘子:但是,上界不能往里存,只能往外取!

泛型上界的主要设计目的:主要运用于方法入参的限定,是为了运行时,无论你明确T为儿子/父亲/爷爷中的哪一种时,都可以调用爷爷中的方法而达到面向接口编程(/多态性)的思想。示例如下:

//当明确T时,T明确为爷爷Grandpa,父亲Father,儿子Son都行。

T明确为爷爷Grandpa时,想调用的是爷爷中的grandpaMtd()方法。

T明确为父亲Father时,想调用的是父亲中的fatherMtd()方法(这个方法爷爷中不存在)。

T明确为儿子Son时,想调用的是儿子中的sonMtd()方法(这个方法父亲中不存在)。

问题来了,我现在的诉求就是需要调用父亲中的fatherMtd()方法,如果明确T时传进来个爷爷,就坏了。解决此问题有两种办法:

1:业务代码里面,利用instanceof “儿->父->爷”倒退

public void xxxMethod( MyClass t ) {

    if( t instanceof Son ){ t.sonMtd(); }
    else if( t instanceof Father){ t.fatherMtd(); }
    else if( t instanceof Grandpa){ t.grandpaMtd(); }
}

2:泛型上界 -- 限制方法的入参

public void xxxMethod( MyClass t ) {

     t.fatherMtd();
}

相当于 是无上界的:因为所有的类都是Object的子类,所以可以说无上界,当T想明确时,给个啥类型都是可以的。


    泛型下界

  • 泛型下界:

当T想被明确时,只能明确的是SubClass类型或者其超类类型泛型下届的主要设计目的往集合里add()元素是合法的,但也只能往集合里添加已明确的SubClass的超类型T或者最小后代SubClass类型的元素,当从这样的集合往外读出时,只能用已明确的T类型

public void lowerBound(List list) {
  
    Timestamp now = new Timestamp(System.currentTimeMillis());  
    list.add(now);  
    // Timestamp time = list.get(0); // 不能编译  
    // 换成下面代码 testLowerBound() 里的这句 Date date = list.get(0); 
}

// 为什么上面代码 Timestamp time = list.get(0); 不能通过编译呢?看如下代码:

public void testLowerBound() {
  
    List list = new ArrayList(); // 明确Date类型是Timestamp的超类
    list.add(new Date());  
    lowerBound(list); 
    Date date = list.get(0); // 从集合中读出元素,Date是明确指定的类型
}  

Java基础 -- 04泛型<T>_第2张图片

一个能放水果以及一切是水果基类的盘子:下界不影响往里存,但往外取只能放在使用时明确的T的超类引用里!

    泛型擦除

编译时类型检查校验并擦除至上限。当提供了泛型上界时就擦至ParentClass,也就是说,运用T的地方,T只能接受ParentClass或者它的子类;当没有提供泛型上界时就擦至Object,因为相当于,所以说,运用T的地方,T可以是任意类类型,因为任意类都是Object的子类;


泛型注意事项

【不能 new T】【不能异常T】

  • new T();         // 非法 不能实例化T
  • new T[]10;     // 非法 不能实例化T[]的数组
  • static T t;       // 非法 不能声明静态数据成员
  • MyException extends Exception;  // 非法 不能创建泛型化的异常类

泛型的用法:

泛型定义
泛型的定义
MyInterface 泛型接口
MyClass 泛型类
rtnType xxxMtd(T t) 普通泛型方法
static rtnType xxxMtd() 静态泛型方法
泛型使用
泛型的使用 泛型类实例化T 泛型方法 T、MyClass、MyClass

MyClass myObj = new MyClass<>(); //类接受T为String
方法 rtnType xxxMtd(T t){} //入参T为String
T xxxMtd(T t){} //入参/返回值T为String
T xxxMtd(MyClass t){}

//入参为MyClass 返回值为String

T xxxMtd(MyClass t){}

//入参为MyClass 返回值为String

//入参为MyClass 返回值为Integer

......
T xxxMtd(MyClass t){}

//入参为MyClass 返回值为Long

//入参为MyClass 返回值为Float

......
MyClass xxxMtd(MyClass t){}

//入参为MyClass 返回值为MyClass

泛型的示例:

泛型接口的示例

泛型类的示例

普通泛型方法的示例

静态泛型方法的示例

泛型上界的示例

模范Spring的JdbcTemplate简易版的示例

高阶篇

泛型类型

泛型到底是个啥类型呢?JDK1.5引入Type接口;在此之前,Java中只有原始类型(也就是Java中除了8种基本数据之外,剩下的都是类类型),所有的原始类型都是通过Class进行抽象;有了Type以后,Java的数据类型得到了扩展,用来支持泛型

(参数化的)泛型 ParameterizedType List list;
(变量式的)泛型 TypeVariable T a;
(上下界的)泛型 WildcardType Set b; 
泛化的数组 GenericArrayType T[] arrs; 或者 List[] brrs;

Java基础 -- 04泛型<T>_第3张图片

Java基础 -- 04泛型<T>_第4张图片

以上两张图:最终是殊途同归的。

第一张图是从类型本身出发,来知晓,泛型、泛型变量、泛型(/泛型变量)数组、泛型上下界

第二张图是从反射角度出发,来知晓,泛型、泛型变量、泛型(/泛型变量)数组、泛型上下界


以上接口的应用小示例:

import java.lang.reflect.*;
import java.util.*;

class A {
	public int a;
}

public class Test extends A {

    private static Class clazz = Test.class;
	
    /**
	* 演示 ParameterizedType 泛型
	*/
    private List name ;
	private Map.Entry mapEntry;
	public static void testParameterizedType() throws Exception {
					
		Field fname = clazz.getDeclaredField("name");
		Type tname = fname.getGenericType();
		ParameterizedType pT = (ParameterizedType)tname; // 字段name的类型是泛型List
		
		System.out.println("我泛型:"+pT.getTypeName());
		Type[] ts = pT.getActualTypeArguments(); // <>尖括号里面的T,K,E等等
		for (Type t : ts){
			System.out.println("我定义中的变量有:"+t.getTypeName());				
		}	

		System.out.println( "我运行时类身份是:"+pT.getRawType().getTypeName() ); // <>前面的那个值
		pT.getOwnerType(); // null,因为pT这个泛型不是别人的内部类 (获取内部类的拥有者)
		
		Field f2 = clazz.getDeclaredField("mapEntry");
		Type f2OfType = f2.getGenericType();
		ParameterizedType p2T = (ParameterizedType)f2OfType; // 字段name的类型是泛型List
		System.out.println( "Map.Entry内部类的拥有者是:"+p2T.getOwnerType().getTypeName() ); // 返回java.util.Map (获取内部类的拥有者)
	}

    /**
	* 演示 TypeVariable 泛型变量
	*/
	private T tv;	
	public static void testTypeVariable() throws Exception {

		Field f3 = clazz.getDeclaredField("tv");
		Type f3OfType = f3.getGenericType();
		TypeVariable p3T = (TypeVariable)f3OfType; // 字段tv的 类型是泛型变量T
		System.out.println( "我是泛型变量T,在"+p3T.getGenericDeclaration()+"这里定义的" ); // T在哪定义的(注意泛型的定义只能在类/构造函数/方法上,类的数据成员上不能泛型定义,只能使用泛型)
		System.out.println( "我定义是边界的第一个元素是:"+p3T.getBounds()[0].getTypeName() ); // T extends了哪些上界 上界的第一个元素一般为Class类型, 后面 &上的元素必须是接口			
	}

	/**
	* 演示 WildcardType 泛型上界/下界
	*/
	private Set sextends;
	private Set ssuper;
	public static void testWildcardType() throws Exception{

		Field f4 = clazz.getDeclaredField("sextends");
		Type f4OfType = f4.getGenericType();
		ParameterizedType p4T = (ParameterizedType)f4OfType;
		WildcardType wt4 = (WildcardType)p4T.getActualTypeArguments()[0];
		System.out.println( "我是泛型上界的第一个元素:"+wt4.getUpperBounds()[0] ); //Set的上界

		Field f5 = clazz.getDeclaredField("ssuper");
		Type f5OfType = f5.getGenericType();
		ParameterizedType p5T = (ParameterizedType)f5OfType; //字段tv的 类型是泛型变量T
		WildcardType wt5 = (WildcardType)p5T.getActualTypeArguments()[0];
		System.out.println( "我是泛型下界的第一个元素:"+wt5.getLowerBounds()[0] ); //Set的上界
	}

	/**
	* 演示 GenericArrayType 泛型(/泛型变量)数组
	*/
	private S[] ss;
	public static void testGenericArrayType() throws Exception{
		
		Field f6 = clazz.getDeclaredField("ss");
		Type f6OfType = f6.getGenericType();
		GenericArrayType p6T = (GenericArrayType)f6OfType; //字段tv的 类型是泛型变量T
		System.out.println( "我是泛型(/泛型变量)数组,泛型变量是:"+p6T.getGenericComponentType() ); //Set的上界
	}

	
	public static void main(String[] args) {
		try{
			testParameterizedType();
			System.out.println("---测试泛型ParameterizedType在反射中的运用!\r\n");

			testTypeVariable();
			System.out.println("---测试泛型变量TypeVariable在反射中的运用!\r\n");
			
			testWildcardType();
			System.out.println("---测试泛型上下界WildcardType在反射中的运用!\r\n");
			
			testGenericArrayType();
			System.out.println("---测试泛型(/泛型变量)的数组GenericArrayType在反射中的运用!\r\n");

		} catch(Exception e) { }						
	}
}

/**
* 以上是对Type这个类型的初步理解,就算我们拿到了泛型定义中的T,MyClass,或者Set,
* 我们也仅仅是知道了泛型变量为T,这个T我们拿到怎么利用起来呢?
* 这个T在现实代码运行的时候,是被实例化的,那么实例化时:比如Integer还是String我们怎么能知道嘛?
*/

泛型的反射,通过jdk提供的API,我们能拿到泛型的T变量,也能知道泛型MyClass,可代码运行阶段,T一定是被实例化的,实例化时的比如Integer还是String,我们能不能知道,又怎么能知道呢?继续往下看...


泛型的签名信息

class类文件,可以通过命令行 javap -v Myclass 来查看类的字节码,字节码中能看到泛型的签名信息,我下方小示例中的部分字节码信息,比如:

  #23 = Utf8               t1
  #24 = Utf8               LTypeVariable1;
  #25 = Utf8               Signature
  #26 = Utf8               LTypeVariable1<Ljava/lang/Integer;>; //t1变量的签名信息

  #27 = Utf8               t2
  #28 = Utf8               LTypeVariable1<TT;>; //t2变量的签名信息

类在编译的时候,如果类中有使用泛型的地方,此类在编译的时候,我们上方已经说过,泛型在运行时会被擦除,擦除至泛型的上界,比如: 等价于 就擦除至Object,表示任何运行时的真实类型都可以作为T的明确类型(比如:MyClass可以,String可以,Integer也可以等等); 就拆除至Number,表示运行时的真实类型只能是Number以及Number的子类才可以作为T的明确类型(比如:Number可以,Long可以,Integer可以,而String不可以...);

jdk5引入泛型的最主要的作用:1:解决多胞胎雷同方法的简易写法;2:解决集合类中的元素编译时的强校验,对于集合类来说,编译时不报错,那么集合内的元素铁定都是统一的一种类型,所以就不需要从集合中取出元素的向下类型强转换逻辑了。

jdk4传统的一切皆是对象,除了8种基本数据类型之外,一切都是Class类类型。那么jdk5引入了泛型,也在编译时给擦除干净了,是为了运行时向前兼容,仍然坚持一切皆是对象的运行思路,那为什么还要扩展出Type接口以及Type的子接口呢?要解释这个现象还要从我们编写代码的诉求来出发,有了诉求,java公司才会提供出相应的解决方法和API。

好,顺着这个思路,我们写代码现在有诉求了,我怎么知道我的某个对象的类型到底Class类类型还是个JDK5中引入的泛型呢?比如:

MyClass obj = new MyClass<>();

MyClass obj2 = new MyClass();

obj.getClass() == obj2.getClass() //得到true,那么我就啥都不管不顾的认为MyClass也能处理一切类型的元素,其实这违背了泛型的意义,我定义成MyClass 就是意指,类内只想处理String类型的成员。那么问题来了,api现在只提供给我一个getClass()而已,我也只能用它,用它拿不到关于任何泛型的东东,这让我怎么办呢?针对这种问题,java公司扩展出Type接口以及这个Type的若干子接口专门用来和泛型相关的诉求。问题又来了,我们不是一直在讨论泛型擦除,泛型擦除,泛型擦除...泛型都擦除了,运行时还怎么能拿到泛型的相关东东呢?很好,问题很好很强大,接下来终于步入正轨:泛型签名消息。

泛型签名信息:就是类在编译的时候,泛型的相关信息都被保留在class类文件的签名信息之后,这个签名信息不参与class的运行时期(也就是jvm运行时期用不到签名信息,专业属于叫泛型擦除),但这个签名信息可以被java公司设计的api来获取到,这些api就是jdk5对类型扩展后的Type接口、Type接口的子接口,以及这些接口的默认实现类,API暴漏给我们来使用以便我们能在运行时获悉泛型的签名信息。

泛型签名信息的意义:那我们根据Type等接口的api能获取到了泛型的签名信息,获取到它有个什么用呢?我目前的答案是:

泛型定义时,也即泛型签名信息(泛型签名信息,一定来源于泛型的定义,而不是泛型的运行时)

1:--定义就明确了泛型变量T为具体的某个Class类型,我们Class类型,能做一切事

2:--定义时未明确泛型变量T,我们只能拿到T,这个T能干啥,目前我还没发现

3:--针对上述2因为我个人还在学习当中,我猜,说不准就有分析内存字节码的动态技术来感知运行时这个T到底是个什么具体的明确类型,只是我也不知,还在进一步探索学习中.......

泛型签名信息的小示例:下面一个小示例,说明我们拿到的一定是编译时的泛型签名信息

/**
* jdk5 类型一共有:
*
*      8种基本数据
*      Class类
*      ParameterizedType 泛型
*      TypeVariable 泛型变量
*      GenericArrayType 泛型(/泛型变量)数组
*/

import java.lang.reflect.*;

class TypeVariable1 {
}

//测试
public class Test {

	private static TypeVariable1 t1; //t1是泛型,已明确泛型变量T就是Integer
	
	private TypeVariable1 t2;              //t2是泛型,尚未明确泛型变量T

	public Test(TypeVariable1 t2){
		this.t2 = t2;
	}
	
	public static void main(String[] args) {

		try{

			t1 = new TypeVariable1<>();		

			Class clazz = Test.class;
			Field f1 = clazz.getDeclaredField("t1");
			ParameterizedType f1Type = (ParameterizedType)f1.getGenericType();
			Type[] ts0 = f1Type.getActualTypeArguments();
			for(Type t : ts0){
				if(t instanceof Class)
					System.out.println(((Class)t).getName()); 
					//t1是泛型,定义时private static TypeVariable1 t1;
					//         定义时已明确泛型变量T就是Integer
					//         Integer是传统的Class类型,所以这里可以强转(Class)t
					
			}

			Test test = new Test<>( new TypeVariable1<>());
			Field f2 = clazz.getDeclaredField("t2");
			ParameterizedType f2Type = (ParameterizedType)f2.getGenericType();
			Type[] ts2 = f2Type.getActualTypeArguments();
			for(Type t : ts2){

				if(t instanceof Class) 
					System.out.println(((Class)t).getName()); //不走这个分支

				else if(t instanceof TypeVariable)
					System.out.println(t.getTypeName()); //输出T 不是Long

					//t2是泛型,定义时private TypeVariable1 t2;
					//         定义时尚未明确泛型变量T
					//         **哪怕运行时已指明T为Long Test test = new Test<>( new TypeVariable1<>());
					//         **但是我们仍然拿不到这个Long,而只能得到T
					//         **至此得出结论,反射能拿到的只是编译时的signature签名信息,也即泛型定义时的样子,不是运行时实际指明T后的样子
					//         **签名信息是个什么玩意呢?就是编译器编译时,为了向前兼容jdk4,将泛型信息保存在签名中,可以通过javap命令查看class文件的字节码,能查看到签名信息
					//         **签名信息只是保存在class文件中,
					//         **代码真正运行的时候,java还是延迟一贯的作风:java中一切都是对象的思路,也是引入泛型后,为了向前兼容jdk4,才有了"伪泛型"一说
					//         **代码真正运行的时候,jvm加载的class类文件是已经擦除泛型后的一切都是对象的运行思路了,只不过是java的api又提供了抽象出来的Type的访问API
					//         **至此得出结论:ParamertizedType,TypeVariable,GenericArrayType处理的都是泛型定义时的签名信息而已
					//         **              如果定义时就指明了泛型变量T(比如:Integer),我们拿到这个Integer还有点实际意义,知道只能传Integer进去来做业务
					//         **              如果定义时没有指明泛型变量T,我们拿到这个T就意义非常小,因为拿到它也不能在运行时强转为其它类型或者new T()也不行,至少到目前我是没看到运行时我们拿到T的意义何在
					//         **              当然:如果有能力在运行时通过处理内存中的字节码,来动态获悉这个运行时到底T是String还是Integer的话,那就强大了,只是这种强大我还在学习中...目前是不会
					//         Integer是传统的Class类型,所以这里可以强转(Class)t
			}

		}catch(Exception e){ e.printStackTrace(); }
	}
}


结论:我们编写的代码,拿到的一定是编译时的泛型签名信息。

A:泛型定义时,已明确泛型变量T是某一种Class类类型,拿到这个Class类类型还有点意义。

B:泛型定义时,尚未明确泛型变量T是哪种Class类类型,拿到的必是T,感觉意义不大。

你可能感兴趣的:(Java,基础,Java基础,Java基础,泛型)