Java泛型学习笔记

1、什么是泛型

我们知道一般的类和方法在定义的时候,其成员变量、参数、返回值等都必须指明具体的类型,要么是基本的数据类型,要么是自定义的类。如果需要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。而泛型正是为了解决这个问题而产生的,其含义即“可以应用于许多许多类型”,它是一种方法,通过它可以编写出更“泛化”的代码,这些代码对于它们能够作用的类型具有更少的限制,因此单个的代码段可以应用于更多的类型上。

2、泛型类与泛型方法

泛型类

public class Container {

    private T a;
    public Container(T a) { this.a = a;}
    public void set(T a) { this.a = a;}
    public T get() { return a;}

    public static void main(String[] args) {
        Container c = new Container("Hello World!");
        System.out.println(c.get());
//      c.set(250);     无法编译
    }
}

在Container的定义中,对象a的类型是T,即a可以被申明为任何类型(T),但是系统在编译Container的时候,实际把a当成的是一个Object对象。在我们运行程序实例化Container后,T就被限定为String类型,当我们想传入一个整型的数据时编译器就报错。

泛型方法

public class MultiArgs {    
    /**泛型方法**/
    public static  List f(T...args) {
        List result = new ArrayList();
        for(T item:args) {
            result.add(item);
        }
        return result;
    }

    public static void main(String[] args) {
        System.out.println(f("a","b","d"));
        System.out.println(f(1,3,5,100,23));
    }
}

定义泛型方法,只需要将泛型参数列表置于返回值之前,这里MultiArgs并不是泛型的,只是包含一个泛型方法。泛型方法f()传入不定数量、不定类型的参数,返回一个ArrayList。这里有一个需要注意的地方,在使用泛型类时,必须在创建对象的时候指定类型参数的值,如上面Container实例化时指定参数类型为String类型;而使用泛型方法的时候,通常不必指定参数类型,如f(“a”,”b”,”d”),编译会为我们找出具体的类型,这称为类型参数推断。

3、擦除与边界

泛型是自Java SE5引入的,其引入的最主要目的是为了创造容器类,来确保我们存入容器的对象都是同一种类型,因此Java的泛型相对于C++的泛型来说有很大的局限性,我们通过下面的代码来说明:

/***C++中的泛型***/
class People {
    void name() { cout << "Tom" << endl; }
}

template<class T> class Test {
    T obj;
public:
    Test(T t) { obj = t;}
    void test() { obj.f(); }
};

int main() {
    People p;
    Test temp(p);   //实例化
    temp.test();
}

/***Java中的泛型***/
class People {
    void name() { System.out.println("Kimi"); }
}

public class Test<T> {
    private T obj;
    public Test(T t) { obj = t;}
    void test() {
        //obj.f()   编译无法通过,因为obj是Object对象,并没有f()方法
    }
    public static void main(String[] args) {
        People p = new People();
        Test temp = new Test(p);
        p.test();
    }
}

可以看到在Java代码中,由于在代码在编译期进行类型检查,编译器无法获知obj是否含有test()方法,因此编译无法通过;而在C++代码中,C++在实例化这个模版时进行检查,Test被实例化的一刻,它看到People拥有一个方法test(),因而程序可以正常运行。
Java泛型是使用“擦除”来实现的,这意味着你在使用泛型时,任何具体的类型信息都被擦除了,你唯一知道的就是你在使用一个对象。由于有了擦除,Java编译器无法将test()必须能够在obj上调用f()这一需求映射到People必须有f()这一事实上。也正是因为擦除,像下面这些在运行时需要知道确切类型信息的操作都将无法工作:

public class Erased {
    private final int SIZE = 100;
    public static void f(Object arg) {
        if(arg instanceof T) {}  //Error
        T var = new T();         //Error
        T[] array = new T[SIZE]; //Error
        T[] array = (T)new Object[SIZE]; //Unchecked warning
    }
}

一种折衷的办法是给泛型参数T指定边界,如

public class Test<T extends People> {
    private T obj;
    public Test(T t) { obj = t;}
    void test() {
        obj.f() 
    }
    public static void main(String[] args) {
        People p = new People;
        Test temp = new Test(p);
        p.test();
    }
}

在指定参数T继承至People后,代码中的obj对象将不仅仅是Object对象,而是People对象,而People对象必定包含f()方法,因此编译就可以通过了。指定边界其实变相的降低了代码的通用性,这实际就不能算是真正的泛型了,只是一种多态的运用,相当于在凡是需要说明类型的地方,我们都使用基类进行说明。
如果既想保留代码的泛化能力,又能够实现f()的调用,利用Java反射机制也是可以实现的

public class Test {
    private T obj;
    public Test(T t) { obj = t;}
    void test() {
        Class c = obj.getClass();       
        try{
            Method m = c.getMethod("f");
            m.invoke(obj);
        }catch(Exception e) {
            System.out.println(c.getSimpleName + " doesn't have f()")
        } 
    }
    public static void main(String[] args) {
        People p = new People;
        Test temp = new Test(p);
        p.test();
    }
}

你可能感兴趣的:(Java)