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

JAVA 泛型实现原理及使用详解

2015-12-17 11:54 796 查看
自java 1.5 起,我们可以在java中使用泛型了。关于为什么要使用泛型,可能是很多新手迷茫的地方。用一个Object 类代替不就好了。光说也记不住,练吧,如:

//未使用泛型时

public static void test(){
List l = new ArrayList();
l.add(Integer.valueOf(1));
Integer b = (Integer) l.get(0);
System.out.println(b);
}


//使用泛型后

public static void testG(){
List<Integer> l = new ArrayList<Integer>();
l.add(Integer.valueOf(1));
Integer b = l.get(0);
System.out.println(b);
}


在java泛型出现前,我们使用List都是test 中的方式,其中add方法的参数就是Object , add(Object o){…},只要编码者记住自己add 都List 中的类型,使用时加一个强制转型就好了。 而有了泛型后使用的方式对比之前,发现就是少了转型。无论是编码量和效率都没变化。使不使用泛型是否无所谓了呢。如果你觉得是说明你确实编程经验,并且对软件生命周期理解不清楚。

确实使用泛型并不会增加我们软件的运行速度,但它能在编译期间帮你排除常见的类型转换错误。

未使用泛型, X c = (X)l.get(0); 在编译器不会报错,只有等到程序运行到这段代码时才报错。

而使用泛型后,上面的代码根本通不过编译。也就是在你编码时就可以找出错误。

有编程经验的你肯定知道,bug出现时间越靠后,你修复它所发的时间越长,这也就是使用泛型的最主要原因。

使用泛型很简单,但要使用好我们还需了解其实现原理

可能你也知道java 中泛型使用的是擦除法。怎么个意思。简单的说,就是

List<Integer> l = new ArrayList<Integer>();
l.add(Integer.valueOf(1));
Integer b = l.get(0);


上面的代码经过编译后,再反编译回来的话就是

List<Object> l = new ArrayList<Object>();
l.add(Integer.valueOf(1));
Integer b =(Integer) l.get(0);


没错,这和以前没有泛型时的代码是一致的,我们在代码中定义的泛型被擦掉了。所以之前也说了,它们性能并没有差异。

不过现在又有了新的疑问:

public class GenericTest {

public static void test(List<String> ls){
String s = ls.get(0);
System.out.println("This is String generic");
return;
}

public static void test(List<Integer> ls){
Integer i = (Integer)ls.get(0);
System.out.println("This is Integer generic");
return;
}
}


上面代码能通过编译吗?下面这段呢?为什么?

public class GenericTest {

public static void test(List<String> ls){
String s = ls.get(0);
System.out.println("This is String generic");
return;
}

public static int test(List<Integer> ls){
Integer i = (Integer)ls.get(0);
System.out.println("This is Integer generic");
return 0;
}
}


运行一下,如果你答对了,说明你已经泛型了。如果错了,或猜对的就接着看吧。

第一段代码出错,容易理解,因为JAVA 方法的重载,基本要求是方法签名不同,而我们学习到的方法签名包含方法名及参数类型,个数,及顺序。因为泛型使用了擦除法,所以编译后两个方法签名一致。但为什么第二个通过了呢,返回值并不在代码签名范围啊。可以自己做个试验,不使用泛型类参数,只使用不同的返回值重载一个方法,也通不过。

其实原因就是使用擦除法的缺点。因为如果不是使用擦拭法,如C#,List 和List 是两个不同的类型,可以重载。而使用擦除法后,为了实现重载只能增加一个可能没必要的返回值作为签名的一部分。具体实现可以去看看java 规范。

泛型的定义和使用比较简单,以代码代替说教了。

/*
* 定义泛型类
*/
public class GenericTest<K> {

/*
* 在类方法中使用类定义的泛型
*/
public void test(List<K> ls){
K s = ls.get(0);
System.out.println(s.toString());
}

/*
* 泛型方法
*/
public static <T> void a(T t){
System.out.println(t.toString());
}

public static void main(String[] args) {
GenericTest.a(Integer.valueOf(1));
GenericTest.a("abc");
}
}


最后在提醒一下,擦除法并不代表编译后的字节码中就不包含我们在源代码定义的泛型类型了。而是在字节码中引入新属性Signature 和 LocalVariableTypeTable 来存储泛型。这也是为什么可以通过返回值重载,及通过反射获取到泛型的根本原因。

关于如何通过反射读取泛型,可以参考 http://www.tmser.com/post-12.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  泛型 java