目录
泛型概念:
泛型种类:
泛型接口
泛型类
泛型方法
普通泛型方法
静态泛型方法
泛型构造函数
泛型进阶:
泛型通配符
泛型上界
泛型下界
泛型擦除
泛型注意事项
泛型的用法:
泛型的示例:
高阶篇
泛型
泛型
T 类型 的私有数据成员,要求类定义时一定要携带
public class Test {
private T name; // 编译报错
}
public class Test {
private T name; // 编译通过
}
泛型概念:
没有JDK5问世以前的老代码:
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类型的求和,等等...恭喜你,当你对此有个认识的时候,你就开始慢慢摸进了泛型方法编码的领域
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的类型 (比如: MyCls
// 或者 使用?通配符 (比如: MyCls>)
// 或者 使用?有界通配符 (比如: MyCls extends Father>)
// 或者 仅仅用来做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
MyClass
MyClass
........
泛型类想要解决的就是,类内的成员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){}
}
static
方法返回值类型 | 方法有参数的 | 方法无参数的 |
类型T |
|
|
类型XxClass |
|
|
无 |
泛型方法想要解决的就是:
第一种场景:方法除了参数类型和返回类型不一样,整体方法的业务逻辑是一致的,我们将这样的"多胞胎"式的多个方法用一个泛型方法来表示即可,泛型方法内部是想不论T明确后的类型到底是什么类型,最终都是超类中的超类方法。(实现面向接口编程(/多态性)的思想)。这种方法的编写常见结构为:T xxxMtd(T t){ return t; }
第二种场景:写了一个泛型方法,传入参数T,方法内部的逻辑使用instanceof来倒推t的明确类型,进而根据不同的明确类型,执行不同的分支的业务逻辑,最后泛型方法无返回值 或 返回T类型。这种方法的编写常见结构为:T xxxMtd(anyType yyy, Class
一般泛型方法都是结合泛型类一起来使用的,因为这样泛型类在实例化的时候,就已经明确了泛型方法中的泛型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{
泛型进阶:
?问号即是泛型通配符,表示未知类型。可以理解为,比T更广泛的范围。通配符不能用于泛型的定义,一般都是用于作为方法的入参来运用的。MyClass> 可以代表 MyClass
当T想被明确时,只能明确的是SuperClass类型或者SuperClass的子类类型。使用泛型上界时,往集合里add()元素是非法的,此时编译不会通过。
/**
* List extends Date> list 意指:使用时,可以是Date类型或者Date的子类型
*/
public void upperBound(List extends Date> 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类型
}
啥水果都能放的盘子:但是,上界 extends T>不能往里存,只能往外取!
泛型上界的主要设计目的:主要运用于方法入参的限定,是为了运行时,无论你明确T为儿子/父亲/爷爷中的哪一种时,都可以调用爷爷中的方法而达到面向接口编程(/多态性)的思想。示例如下:
T明确为爷爷Grandpa时,想调用的是爷爷中的grandpaMtd()方法。
T明确为父亲Father时,想调用的是父亲中的fatherMtd()方法(这个方法爷爷中不存在)。
T明确为儿子Son时,想调用的是儿子中的sonMtd()方法(这个方法父亲中不存在)。
问题来了,我现在的诉求就是需要调用父亲中的fatherMtd()方法,如果明确T时传进来个爷爷,就坏了。解决此问题有两种办法:
1:业务代码里面,利用instanceof “儿->父->爷”倒退
public void xxxMethod( MyClass extends Grandpa> 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 extends Father> t ) {
t.fatherMtd();
}
当T想被明确时,只能明确的是SubClass类型或者其超类类型。泛型下届的主要设计目的:往集合里add()元素是合法的,但也只能往集合里添加已明确的SubClass的超类型T或者最小后代SubClass类型的元素,当从这样的集合往外读出时,只能用已明确的T类型
public void lowerBound(List super Timestamp> 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是明确指定的类型
}
一个能放水果以及一切是水果基类的盘子:下界 super T>不影响往里存,但往外取只能放在使用时明确的T的超类引用里!
编译时类型检查校验并擦除至上限。当提供了泛型上界
【不能 new T】、【不能异常T】
泛型的用法:
泛型的定义 |
|
MyInterface |
泛型接口 |
MyClass |
泛型类 |
普通泛型方法 | |
static |
静态泛型方法 |
泛型的使用 泛型类实例化T 泛型方法 T、MyClass |
||
类 |
MyClass |
//类接受T为String |
方法 | rtnType xxxMtd(T t){} | //入参T为String |
T xxxMtd(T t){} | //入参/返回值T为String | |
T xxxMtd(MyClass |
//入参为MyClass |
|
T xxxMtd(MyClass> t){} | //入参为MyClass |
|
//入参为MyClass |
||
...... | ||
T xxxMtd(MyClass extends Number> t){} | //入参为MyClass |
|
//入参为MyClass |
||
...... | ||
MyClass |
//入参为MyClass |
泛型的示例:
泛型接口的示例
泛型类的示例
普通泛型方法的示例
静态泛型方法的示例
泛型上界的示例
模范Spring的JdbcTemplate简易版的示例
高阶篇
泛型到底是个啥类型呢?JDK1.5引入Type接口;在此之前,Java中只有原始类型(也就是Java中除了8种基本数据之外,剩下的都是类类型),所有的原始类型都是通过Class进行抽象;有了Type以后,Java的数据类型得到了扩展,用来支持泛型
(参数化的)泛型 | ParameterizedType | List |
(变量式的)泛型 | TypeVariable | T a; |
(上下界的)泛型 | WildcardType | Set extends K> b; |
泛化的数组 | GenericArrayType | T[] arrs; 或者 List |
以上两张图:最终是殊途同归的。
第一张图是从类型本身出发,来知晓,泛型、泛型变量、泛型(/泛型变量)数组、泛型上下界
第二张图是从反射角度出发,来知晓,泛型、泛型变量、泛型(/泛型变量)数组、泛型上下界
以上接口的应用小示例:
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 extends K> sextends;
private Set super E> 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 extends K>的上界
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 extends K>的上界
}
/**
* 演示 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 extends K>的上界
}
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
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变量的签名信息
类在编译的时候,如果类中有使用泛型
jdk5引入泛型的最主要的作用:1:解决多胞胎雷同方法的简易写法;2:解决集合类中的元素编译时的强校验,对于集合类来说,编译时不报错,那么集合内的元素铁定都是统一的一种类型,所以就不需要从集合中取出元素的向下类型强转换逻辑了。
jdk4传统的一切皆是对象,除了8种基本数据类型之外,一切都是Class类类型。那么jdk5引入了泛型,也在编译时给擦除干净了,是为了运行时向前兼容,仍然坚持一切皆是对象的运行思路,那为什么还要扩展出Type接口以及Type的子接口呢?要解释这个现象还要从我们编写代码的诉求来出发,有了诉求,java公司才会提供出相应的解决方法和API。
好,顺着这个思路,我们写代码现在有诉求了,我怎么知道我的某个对象的类型到底Class类类型还是个JDK5中引入的泛型呢?比如:
MyClass
MyClass obj2 = new MyClass();
obj.getClass() == obj2.getClass() //得到true,那么我就啥都不管不顾的认为MyClass
泛型签名信息:就是类在编译的时候,泛型的相关信息都被保留在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,感觉意义不大。