泛型之泛型方法
package com.hoonee.javase.generic;
/**
* 泛型方法
* 首先,泛型的声明,必须在方法的修饰符(public,static,final,abstract等)之后,返回值声明之前。
* 然后,和泛型类一样,可以声明多个泛型,用逗号隔开。
* @author Hoonee
* @mail hoonee@163.com
*/
public class GenericMethodDemo {
public static <T> Class getClass(T t) {
return t.getClass();
}
public static void main(String[] args) {
System.out.println(GenericMethodDemo.getClass(1));
System.out.println(GenericMethodDemo.getClass("1"));
}
}
一个关于泛型类的问题
/**
* 一个关于泛型类的问题
*
* 这段代码的输出为:Type of T is java.lang.Integer
* 问题是,既然JVM在运行时已经将泛型信息擦除,那么就应该只知道ob是Object类型的,那么在创建iObject对象的时候,
* 传递了一个Integer类型的参数88,为什么在showType()方法中 ob.getClass().getName())方法能够得到
* Integer的类型信息呢?在showType()方法中通过ob只能调用Object类所具有的方法。(这与能从ob获取其Integer
* 类型信息是否矛盾呢?)另外,GenClass.class文件经过反编译后,如下:
*
* class GenClass {
* Object ob;
* GenClass(Object o) {
* ob = o;
* }
* Object getOb() {
* return ob;
* }
* void showType() {
* System.out.println((new StringBuilder("Type of T is ")).append(ob.getClass().getName()).toString());
* }
* }
*
* 疑惑解答:
*
* 你这个问题其实不只是泛型了。
* 第一组关键词:引用,实例化。
* 先看下面这个简单的例子:
* Object obj=new Integer();
* System.out.println(obj.getClass());
* obj是引用,引用的类型是Object,实例的类型是Integer
* 我们知道,引用是栈保存的一个类似地址的信息,本身没有任何含义,真正能保存信息的是new Integer()所划分的内存空间。
* 即是说,Integer这个信息是在实例里的
*
* 第二组关键词:继承,父类,子类
* Integer继承自Object
* obj.getClass()调用的是Integer的getClass方法,当然Integer是没有该方法,于是一直回溯到父类Object,找到Object的getClass方法,
* 实现是本地native方法,我们不知道内部怎么构造,但是可以肯定,它是在根据引用地址找到实例位置,确定实例类型。
* 综合第一组可知,在这里,实例类型是Integer
*
* 第三才是泛型。其实在第一部里面已经说明过了。GenClass并没有维护T的信息。维护T信息的是T ob;
* GenClass<Integer> iObject执行这一部的时候,ob就认为自己是个Integer类型了。当它具有了实例之后,你才能调用getClass方法
* 所以综合上两点才表现出了你看到的效果。对于GenClass来说,一旦运行,它就不在乎T是什么类型了,但是ob需要知道自己的类型吧?这个Integer被维护在了实例里。
*
* @author Hoonee
* @mail hoonee@163.com
*/
public class GenDemo {
public static void main(String args[]) {
GenClass<Integer> iObject = new GenClass<Integer>(88);
iObject.showType();
}
}
class GenClass<T> {
T ob;
GenClass(T o) {
this.ob = o;
}
T getOb() {
return this.ob;
}
void showType() {
System.out.println("Type of T is " + ob.getClass().getName());
}
}
只有引用类型才能作为泛型方法的实际参数,不能是基本类型
除了在应用泛型时可以使用extends限定符,在定义泛型时也可以使用extends限定符
可以用来指定多个边界,如<V extends Serializable & cloneable> void method(){},必须实现Serializable 和cloneable两个接口
普通方法、构造方法和静态方法中都可以使用泛型。
也可以用类型变量表示异常,称为参数化的异常,可以用于方法的throws列表中,但是不能用于catch子句中。
例:用下面的代码说明对异常如何采用泛型:
private static <T extends Exception> sayHello() throws T
{
try{
}catch(Exception e){
throw (T)e;
}
}
在泛型中可以同时有多个类型参数,在定义它们的尖括号中用逗号分,例如:
public static <K,V> V getValue(K key) { return map.get(key);}
例:自定义泛型方法
public class GenericsUserDefined{
public static void main(String args[])throws Exception{
Integer x = add(3,5);
Number x1 = add(3.5,5);
//可看成float和int都属于Number类型,如果Float x1 = add(3.5,5);出错
Object x2 = add(3,"abc");
//可看成Integer,String都属Object
}
//自定义泛型方法
private static <T>T add(T x,T y)
//定义一个类型<T>,必须在返回值之前或挨着返回值定义
//声名了一个类型T,调用和返回的参数 都为 T
{
return null;
}
}
泛型方法类型参数的类型推断
编译器判断范型方法的实际类型参数的过程称为类型推断,类型推断是相对于知觉推断的,其实现方法是一种非常复杂的过程。
根据调用泛型方法时实际传递的参数类型或返回值的类型来推断,具体规则如下:
当某个类型变量只在整个参数列表中的所有参数和返回值中的一处被应用了,那么根据调用方法时该处的实际应用类型来确定,这很容易凭着感觉推断出来,即直接根据调用方法时传递的参数类型或返回值来决定泛型参数的类型,例如:
swap(new String[3],3,4) --> static <E> void swap(E[] a, int i, int j)
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型都对应同一种类型来确定,这很容易凭着感觉推断出来,例如:
add(3,5) --> static <T> T add(T a, T b)
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型,且没有使用返回值,这时候取多个参数中的最大交集类型,例如,下面语句实际对应的类型就是Number了,编译没问题,只是运行时出问题:
fill(new Integer[3],3.5f) --> static <T> void fill(T[] a, T v)
当某个类型变量在整个参数列表中的所有参数和返回值中的多处被应用了,如果调用方法时这多处的实际应用类型对应到了不同的类型, 并且使用返回值,这时候优先考虑返回值的类型,例如,下面语句实际对应的类型就是Integer了,编译将报告错误,将变量x的类型改为float,对比eclipse报告的错误提示,接着再将变量x类型改为Number,则没有了错误:
int x =(3,3.5f) --> static <T> T add(T a, T b)
参数类型的类型推断具有传递性,下面第一种情况推断实际参数类型为Object,编译没有问题,而第二种情况则根据参数化的Vector类实例将类型变量直接确定为String类型,编译将出现问题:
copy(new Integer[5],new String[5]) --> static <T> void copy(T[] a,T[] b);
copy(new Vector<String>(), new Integer[5]) --> static <T> void copy(Collection<T> a , T[] b);