您的位置:首页 > 编程语言 > Java开发

Java泛型学习笔记

2015-09-17 19:31 519 查看

1、什么是泛型

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

2、泛型类与泛型方法

泛型类

public class Container<T> {

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<String> c = new Container<String>("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 <T> List<T> f(T...args) {
List<T> result = new ArrayList<T>();
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<People> 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<People> temp = new Test<People>(p);
p.test();
}
}


可以看到在Java代码中,由于在代码在编译期进行类型检查,编译器无法获知obj是否含有test()方法,因此编译无法通过;而在C++代码中,C++在实例化这个模版时进行检查,Test被实例化的一刻,它看到People拥有一个方法test(),因而程序可以正常运行。

Java泛型是使用“擦除”来实现的,这意味着你在使用泛型时,任何具体的类型信息都被擦除了,你唯一知道的就是你在使用一个对象。由于有了擦除,Java编译器无法将test()必须能够在obj上调用f()这一需求映射到People必须有f()这一事实上。也正是因为擦除,像下面这些在运行时需要知道确切类型信息的操作都将无法工作:

public class Erased<T> {
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<People> temp = new Test<People>(p);
p.test();
}
}


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

如果既想保留代码的泛化能力,又能够实现f()的调用,利用Java反射机制也是可以实现的

public class Test<T > {
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<People> temp = new Test<People>(p);
p.test();
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: