java泛型学习
Posted xdxxdx
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java泛型学习相关的知识,希望对你有一定的参考价值。
泛型也叫做参数化类型,顾名思义的去理解,就是把类型作为一个参数。类比方法的传参,我们举个例子。
class A{ public void getX(int x){ System.out.println(x); } }
在getX这个函数中,x是一个参数,当我们还没有调用这个函数的时候,我们是不知道它的具体值的,只有当我们执行了诸如:A a=new A();a.getX(2);这样的代码,将实参值2作为一个实参传入代替x,这时候x才是确定的。参数的作用就在于此,就是一个占位符,当你还没调用它的时候,它是不确定的,有多种可能性。
那么所谓的参数化类型,也就是说类型(如String,Integer或者我们自定义的A)是可以参数化的,在没有真正的实参传入之前,我们无法确定它的实际类型。这样说有点抽象,我们来看一个例子。
以下是一个泛型类,泛型类的定义很简单,在类名后加上一个<>,并在其中写入泛型的符号,如T,K,V,E等,当然你要写其他的符号也可以。
public class Generic<T> { public static void main(String args[]){ } }
如上所示,在定义泛型类的时候,我们在类名后加了<T>这个标志,表明该类是一个泛型类,在该类中含有一个类型形参T,当我们实例化这个类的时候,我们将T用一个现实的类型来替代,如下所示。
Generic<Integer>g1=new Generic<Integer>(); System.out.println(g1.getClass()); Generic<String>g2=new Generic<String>(); System.out.println(g2.getClass());
这个过程是否和参数传递很像,参数经历由形参被实参赋值的过程。T被分别赋值为Integer和String。
这边需要注意两点。
(1)泛型的实参只能是类类型(如Integer,String 或其他类),不能是简单类型(如int).
(2)上述代码的结果是:class com.xdx.learn.Generic,class com.xdx.learn.Generic。因为上述代码用了反射,而Class对象是在类加载以后的运行阶段才存在的。说明泛型信息不会被保存到.class文件中,不会保留到运行阶段。事实上,泛型只存在编译阶段,在编译阶段,编译器会检查泛型信息,加入你定义一个List<String>的list,而想要调用list.add(1)这样的方法,那么在编译阶段就会失败。
(3)编译完成后,泛型的信息擦除,在运行的时候,ArrayList<String>和ArrayList<Integer>对jvm来说是同一类型的东西。
1.泛型类
先从一个泛型类的例子来看看泛型的用法。
Generic.java
//定义一个泛型类,其含有两个参数化的类型T和K,当实例化该类的时候,必须为T和K传入具体的类型,也可以在继承该类的时候指定T和K的具体类型
1 public class Generic<T, K> {
//此处成员变量t和k的类型T和K对应类声明处的T和K 2 private T t; 3 private K k; 4 //此处构造方法中的形参t和k的类型T和K对应类声明处的T和K 5 public Generic(T t, K k) { 6 this.t = t; 7 this.k = k; 8 } 9 //普通方法,返回值的类型为T,对应着类声明处的T,返回成员变量t。 10 public T getT() { 11 return t; 12 } 13 //普通方法,方法形参t的类型为T,对应类声明处的T 14 public void setT(T t) { 15 this.t = t; 16 } 17 18 public K getK() { 19 return k; 20 } 21 22 public void setK(K k) { 23 this.k = k; 24 } 25
//普通方法,方法形参t的类型为T,对应类声明处的T
public voidnoGeneric(){
System.out.println("no generic");
} 26 public String getString(T t,K k) { 27 return t.toString()+k.toString(); 28 } 29 30 public static void main(String args[]) {
//实例化这个泛型类,传入T和K的具体类型(类型实参),分别是Integer和Integer 31 Generic<Integer, Integer>g1=new Generic<Integer, Integer>(1,2); 32 System.out.println(g1.getString(2,3)); 33 } 34 35 }
在上述例子中,我定义了一个泛型类,它含有两个参数化的类型,分别是T和K。定义了泛型,我们肯定是要用这个泛型来做一些事情的,一个类的信息提现在他的成员变量和方法上,所以我们需要在成员变量或者方法中使用这个泛型,否则的话这个泛型类也毫无意义了。
在上面的类中,我们分别在类的成员变量或者方法中使用到了泛型,我们一旦将一个类声明为泛型类,他所包含的泛型符号就具有一种代表意义,他代表一种类型。比如T和K均代表着一种类型,它们可以是同一种类型,也可以是不同的类型,但是在类中(成员或者方法中)所用到的T则必须跟类声明时候的这个T所代表的是同一种类型。
概括来讲就是,泛型类用一个泛型符号代表一个还不确定的类型,这样我们就可以在类中用这个泛型符号来代表这个不确定的类型,并进行相应的编码。
在实例化中传入实际的类型,如Generic<Integer, Integer>g1=new Generic<Integer, Integer>(1,2);这样T和K就确定了。我们在IDE中输入g1.set,IDE的智能提示会弹出如下提示。
可以看到setK方法中的参数类型已经是Integer了。所以泛型符号的真正类型是在类实例化的时候确定的。
除了在实例化的时候传入泛型的实参,还有一种方法是通过类继承的方式,在子类继承父类的时候指定泛型的实参。如下所示。
//在继承的时候传入泛型的实参,T和K的类型确定为String和String,这是一个普通类 class SonGeneric2 extends Generic<String,String>{ //由于T和K在类声明时已经确定了,所以此时构造函数处的形参类型确定 public SonGeneric2(String t, String k) { super(t, k); System.out.println(t+","+k); } }
public static void main(String args[]) { //构造函数,由于SonGeneric2定义的时候没有泛型,所以不必再传入泛型实参,跟普通类实例化一样。 SonGeneric2 s2=new SonGeneric2("xdx", "lqf"); System.out.println(s2.getK()); System.out.println(s2.getT()); System.out.println(s2.getString("i love ", "java")); }
当然子类在继承的时候也可以不指定泛型的实参,那么则必须在实例化该子类的时候指定泛型的具体类型。如下所示。
1 //继承的时候并不指定泛型实参,该子类继承了父类的两个泛型符号,并且自己也定义了一个泛型符号,注意此时我们Generic的尖括号处用的<M,N>而不是定义Generic类时候的<T,K>。 2 //事实上,你可以用任何符号(除了L).对于继承他的类来说,Generic<T,K>和Generic<M,N>并无二致。泛型符号对类外部来说仅仅是代表一个类型的符号而已,而 3 //真正发挥作用的是类内部的成员和方法中所用到的泛型符号必须与类声明的泛型符号一致。 4 class SonGeneric1<M, N, L> extends Generic<M, N> { 5 private L l; 6 7 public L getL() { 8 return l; 9 } 10 11 public void setL(L l) { 12 this.l = l; 13 } 14 //构造函数,此时你必须用M和N这两个泛型,而不能用T和K。 15 public SonGeneric1(M t,N k, L l) { 16 super(t, k); 17 this.l = l; 18 System.out.println(t.toString() + k.toString() + l.toString()); 19 } 20 //同样的,必须使用类声明处的M和N. 21 public String getString(M t, N k, L l) { 22 return t.toString() + k.toString() + l.toString(); 23 } 24 25 }
1 public static void main(String args[]) { 2 //由于没有在继承的时候指定泛型的实参,所以必须在实例化的时候指定泛型实参。 3 SonGeneric1<String,Integer,String>s1=new SonGeneric1<String, Integer, String>("xdx", 28, "china"); 4 System.out.println(s1.getL()); 5 }
上述代码的运行结果是。
xdx28china
china
2.泛型接口
泛型接口跟泛型类类似,这边不多做阐述,看一个例子。
1 //泛型接口定义 2 interface GenericInterFace<T>{ 3 T func1(T t); 4 } 5 //实现接口时候传入泛型的类型实参,泛型符号确定,是一个普通类。 6 class impGeneric1 implements GenericInterFace<String>{ 7 //实现方法,由于此时泛型已经确定,所以方法的形参类型确定 8 public String func1(String t) { 9 return t; 10 } 11 12 } 13 //实现接口的时候不传入泛型的类型实参,所以这个类依然是一个泛型类。 14 class impGeneric2<T> implements GenericInterFace<T>{ 15 public T func1(T t) { 16 return t; 17 } 18 }
3.泛型方法
首先要明白,方法的返回值或者形参中带有泛型符号的,并不就能说明它是一个泛型方法,如上面例子中的public T func1(T t)就并不是一个泛型方法,它只是用到了泛型类中的泛型符号而已。要知道,泛型方法与泛型类并没有必然的联系。
来看一个泛型方法的例子。在Generic.java中添加一个泛型方法,如下所示。
1 //定义一个泛型类,其含有两个参数化的类型T和K,当实例化该类的时候,必须为T和K传入具体的类型,也可以在继承该类的时候指定T和K的具体类型 2 public class Generic<T, K> { 3 // 这边的类型T对应着泛型类的声明时候<>中的T,K对应着<>中的K. 4 private T t; 5 private K k; 6 7 // 构造方法,方法中的形参t和k的类型分别是T和K,也是对应着类声明时候的T和K 8 public Generic(T t, K k) { 9 this.t = t; 10 this.k = k; 11 } 12 13 // 普通方法,返回值的类型T也是对应着类声明时候的T 14 public T getT() { 15 return t; 16 } 17 18 // 普通方法,方法中的形参t与的类型T类定义处的T一致 19 public void setT(T t) { 20 this.t = t; 21 } 22 23 public K getK() { 24 return k; 25 } 26 27 public void setK(K k) { 28 this.k = k; 29 } 30 31 // 普通方法,形参t和k的类型T和K对应着类声明处的T和K. 32 public String getString(T t, K k) { 33 return t.toString() + k.toString(); 34 } 35 36 // 普通方法,没有用到泛型 37 public void noGeneric() { 38 System.out.println("no generic"); 39 } 40 //泛型方法,<T>是必须的,表明这是一个泛型方法,这边的泛型符号T与类处声明的泛型符号T并不代表同一个类型,这是必须注意的。 41 //事实上,IDE会提示The type parameter T is hiding the type T,即该方法定义的泛型符号T会与类中定义的泛型符号T冲突,并在 42 //该方法中隐藏类所声明的的泛型符号T.一旦声明成一个泛型方法并给定一个泛型符号,则可以在该方法的形参,返回值,或者方法体中使用该泛型符号来代表一种类型。 43 public <T> T genericFunc(T t){ 44 return t; 45 } 46 47 public static void main(String args[]) { 48 //实例化类,类中的T被传入类型实参String. 49 Generic<String, String>g=new Generic<String, String>("xdx","lqf"); 50 //调用泛型方法genericFunc,泛型方法处的T被传入Double类型实参。跟实例化类时候传入的String类型不一样。这也印证了泛型方法中声明的的泛型符号跟所处 51 //的泛型类中声明的泛型符号是没有关联的。 52 Double d=g.genericFunc(3.1415926); 53 System.out.println("pai is "+d); 54 //普通方法,getT()的返回值是String,与类实例化传入的类型实参一致。 55 String t=g.getT();
System.out.println("t is "+t); 56 } 57 58 }
上述代码的执行结果是:
pai is 3.1415926
t is xdx
看例子2:
1 //泛型方法,声明一个泛型符号M,该方法的返回值也是M类型,参数是Generic的泛型类,T和K与类声明处的T和K需一致。 2 public <M> M genericFunc2(Generic<T, K>g){ 3 //将K类型强转为M类型。 4 M n=(M) g.getK(); 5 return n; 6 } 7 8 public static void main(String args[]) { 9 //实例化类,类中的T被传入类型实参String,K被传入Integer. 10 Generic<String, Integer>g=new Generic<String, Integer>("xdx",28); 11 //泛型方法被调用的时候M被赋值为String类型,而g.getK()此时为Integer类型,所以需要先用toString转为String类型,然后才能强转为M(也就是String)类型 12 String str=g.genericFunc2(g).toString(); 13 System.out.println("强转后"+str); 14 15 }
上述例子的结果为:强转后28
更多例子就不举了,总之关于泛型方法,记得如下几点即可。
(1)泛型方法需要在访问限定符和返回类型之间加入<泛型符号>,说明它是一个泛型方法。
(2)泛型方法所声明的泛型符号在该泛型方法中有效,并且若与泛型类中所声明的发型符号相同,会隐藏掉泛型类中的符号。
(3)泛型方法跟泛型类没有必然联系,它是一个独立的东西,在调用该方法的时候指定泛型的实际类型。
4.类型通配符
如下假设,加入Son是Father的子类,People<T>是一个泛型类,那么People<Father>和People<Son>是否具有父子关系呢?看如下例子。
1 public class WildcardDemo { 2 3 public static void main(String[] args) {
WildcardDemo wd=new WildcardDemo(); 4 Son s=new Son(); 5 Father f=s;//将son类型赋值给father类型是没有问题的,因为Father是Son的子类。 6 People<Son>p_son=new People<Son>(s); 7 People<Father>p_father=p_son;//报错,Type mismatch: cannot convert from People<Son> to People<Father>,因为People<Son>并非People<Father>子类 8 wd.wildCard(p_son);//报错:The method wildCard(People<Father>) in the type WildcardDemo is not applicable for the arguments (People<Son>) 9 } 10 public void wildCard(People<Father>p){ 11 System.out.println(p.getT().getClass()); 12 } 13 14 } 15 class Father{ 16 17 } 18 class Son extends Father{ 19 20 } 21 class People<T>{ 22 private T t; 23 public People(T t){
this.t=t;
} 24 public T getT() { 25 return t; 26 } 27 28 public void setT(T t) { 29 this.t = t; 30 } 31 32 }
上述代码说明,即便泛型符号所传入的类型具有父子的关系,但是泛型之间是没有父子关系的。但是现在有个迫切的需求就是,我想wildCard()方法既要能传入People<Father>类型的参数,也要能传入People<Son>类型的参数。类型通配符就是用来这个问题。
将上述wildCard中的People<Father>p改为People<?>p,就可以传入People<Son>类型的参数了,事实上,“?”代表所有的类型,可以说它代表People<T>这个泛型的所有类型实参。我们不但可以传入People<Son>,还可以传入People<Integer>,People<String>等等。
修改后的代码如下所示。
public class WildcardDemo { public static void main(String[] args) { WildcardDemo wd=new WildcardDemo(); Son s=new Son(); People<Son>p_son=new People<Son>(s); wd.wildCard(p_son); People<Integer>p_int=new People<Integer>(1); wd.wildCard(p_int); } public void wildCard(People<?>p){ System.out.println(p.getT().getClass()); } } class Father{ } class Son extends Father{ } class People<T>{ private T t; public People(T t){ this.t=t; } public T getT() { return t; } public void setT(T t) { this.t = t; } }
运行的结果为:
class com.xdx.learn.Son
class java.lang.Integer
关于通配符,常常与泛型混淆,那People<T>和People<?>到底有什么区别呢?
(1)首先看应用的场景,通配符表示的泛型一般情况下用于函数的形参,他表示的是这个People这个泛型的类型实参有无限的可能性,替换掉?的类型可以是所有的类类型,即Object的所有子类。
(2)而People<T>可以出现在很多地方,他可以作为类的声明,表示这是一个泛型类,T的实际类型在实例化的时候传入;也可以作为类中的成员变量或者函数的返回值,函数形参等。此时的T则与其所处的类声明处的泛型符号挂钩;还可以作为泛型方法中的返回值或者参数等,此时的T与方法中声明的泛型符号挂钩。无论是上述三种中的哪一种,T在泛型类实例化(或者泛型函数调用)的时候,只代表一种类型。
比如:public void func1(People<?> p)和public void func2(People<T> p)这两个函数。第一个函数可以在任何类中去定义,而第二个函数则必须在一个泛型类中去定义,且这个泛型类必须要含有泛型标志T,否则它就毫无意义(而且也会报错)。
当然,如果是public<T> void func3(People<T>p),此时T就挂钩了泛型方法中声明的T,此时包含它的类不必是泛型类。然而不管是在泛型类还是泛型方法中,在这个函数中People<T>p只有一种可能,就是与类声明处的T挂钩(泛型类)或者与方法声明处的T(泛型方法)挂钩的那种类型。而People<?> p就不必要跟任何东西挂钩,它有无限的可能。
(3)People<?>也可以作为函数的返回类型,这时候表示返回的类型是People,但是People中的泛型是不确定的,当我们再次要使用到这个?的时候,也只能当成一个不确定的类型来用。比如说Class类是一个泛型类。我截取部分代码。
1 public final class Class<T>{ 2 public static Class<?> forName(String className) 3 throws ClassNotFoundException { 4 Class<?> caller = Reflection.getCallerClass(); 5 return forName0(className, true, ClassLoader.getClassLoader(caller), caller); 6 } 7 public T newInstance() 8 throws InstantiationException, IllegalAccessException 9 { 10 if (System.getSecurityManager() != null) { 11 checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), false); 12 } 13 14 // NOTE: the following code may not be strictly correct under 15 // the current Java memory model. 16 17 // Constructor lookup 18 if (cachedConstructor == null) { 19 if (this == Class.class) { 20 throw new IllegalAccessException( 21 "Can not call newInstance() on the Class for java.lang.Class" 22 ); 23 } 24 try { 25 Class<?>[] empty = {}; 26 final Constructor<T> c = getConstructor0(empty, Member.DECLARED); 27 // Disable accessibility checks on the constructor 28 // since we have to do the security check here anyway 29 // (the stack depth is wrong for the Constructor\'s 30 // security check to work) 31 java.security.AccessController.doPrivileged( 32 new java.security.PrivilegedAction<Void>() { 33 public Void run() { 34 c.setAccessible(true); 35 return null; 36 } 37 }); 38 cachedConstructor = c; 39 } catch (NoSuchMethodException e) { 40 throw (InstantiationException) 41 new InstantiationException(getName()).initCause(e); 42 } 43 } 44 Constructor<T> tmpConstructor = cachedConstructor; 45 // Security check (same as in java.lang.reflect.Constructor) 46 int modifiers = tmpConstructor.getModifiers(); 47 if (!Reflection.quickCheckMemberAccess(this, modifiers)) { 48 Class<?> caller = Reflection.getCallerClass(); 49 if (newInstanceCallerCache != caller) { 50 Reflection.ensureMemberAccess(caller, this, null, modifiers); 51 newInstanceCallerCache = caller; 52 } 53 } 54 // Run constructor 55 try { 56 return tmpConstructor.newInstance((Object[])null); 57 } catch (InvocationTargetException e) { 58 Unsafe.getUnsafe().throwException(e.getTargetException()); 59 // Not reached 60 return null; 61 } 62 } 63 64 }
看下面的代码。
1 Number n = 0; 2 Class<? extends Number> c = n.getClass(); 3 System.out.println(c.getName()); 4 Class<?>clazz=Class.forName("com.xdx.controller.back.TestController"); 5 Object t=clazz.newInstance(); 6 System.out.println(clazz); 7 System.out.println(t);
这里定义的clazz是一个Class<?>类型的,?代表这个类型是不确定的,所以当我们调用clazz的newInstance()方法的时候,它的返回值T也是不确定的,我们可以理解成它为一个不确定的类型,因为任何类型都是Object的子类,根据多态,我们可以使用Object t=clazz.newInstance()来修饰。
当然,我们可以这样写Class<TestController>=(class<TestController>)Class.forName("com.xdx.controller.back.TestController");这时候我们将clsss<?>强制转换为class<TestController>,这样做是可以的,因为class<?>可以当成是所有的Class<Integer>,Class<A>,Class<B>,Class<Object>这些所有的父类。
这里再来比较一下Object ,T,?三者的区别:
Object和?之间的区别在于:Object是一个固定的类型,而?是不固定的,虽然Object是所有类的父类,但是它跟通配符不一样,List<Object>表明这个泛型的类型就是Object,而List<?>表明这个泛型的类型不确定,他有可能是Integer,有可能是String,当然也有可能是Object.
?和T的区别上面已经说了,在泛型类被实例化或者泛型方法在被调用的那瞬间,T就已经被确定了。
5.泛型边界
比如我们可以这样定义一个泛型类:public class Generic<T extends Father>,说明T的类型受到了限制,当我们实例化类的时候,只能传入Father的子类作为其类型实参。同样的public class Generic<T super Father>表示T必须是Father的父类。
同样的用法可以用到泛型方法:如:public <T extends Father> void func1(T);还可以用于类型通配符:如public void func1(People<? extends Father>);就不做过多解释了。
最后来写一个利用泛型和反射来获取一个对象的泛型方法。
(1)首先,我们知道利用反射可以通过类名获取类的Class对象,然后通过Class对象实例化一个对象。代码如下所示。
package com.xdx.learn; public class Generic_Reflex { public static void main(String args[]) throws ClassNotFoundException, InstantiationException, IllegalAccessException{ //通过Class的静态方法forName获取com.xdx.learn.A"的Class对象 Class clz=Class.forName("com.xdx.learn.A"); //clz.newInstance()得到的是一个object对象,需要强转为A. A a=(A) clz.newInstance(); System.out.println(a); } } class A{ }
(2)将上述代码段抽象成一个方法:
public static Object getObject(String className) throws ClassNotFoundException, InstantiationException, IllegalAccessException{ Class clz=Class.forName(className); Object obj= clz.newInstance(); return obj; }
(3)如果我们想将他写成一个泛型方法呢?
//<T>表示这是一个泛型方法,其返回值为T,其形参为一个Class<T>的对象c,由于Class<T>类也是泛型类,其方法newInstance()的返回类型即为T //所以此时限定该c对象的泛型实参必须为T. public static <T> T getObject(Class<T> c) throws InstantiationException, IllegalAccessException{ //通过反射获取到T的实例,Class<T>的对象的newInstance()方法返回的对象是T类型的。 T t=c.newInstance(); return t; }
(4)调用的时候,我们想要得到A对象,则此时T的类型实参应该为A,所以方法的形参的类型为Class<A>,方法实参则为Class.forName("com.xdx.learn.A")
public static void main(String args[]以上是关于java泛型学习的主要内容,如果未能解决你的问题,请参考以下文章