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

java 泛型总结

2015-01-02 22:54 225 查看
看collection源码时会发现大量使用T/E/K/V,以前知道这是泛型,实际工作中使用却不是很多,最近看到侯捷先生的两篇文章《java泛型技术之发展》《JDK
1.5 的泛型實現》, 学习颇丰,在此总结一下学习到的东西;

1. 泛型的出现是为了解决什么问题?

1.1 在java 1.5 之前,java还没正式在framework 引入 泛型之前,想象一下使用容器类,会出现什么问题,如下面例子:

ArrayList list = new ArrayList();
list.add("abd");
list.add("def");

String abc = (String)list.get(0);
String def = (String)list.get(1);

你会发现每次都要强制转型,甚是麻烦;

1.2 每次强制转换类型还不是最主要的问题,主要的问题是 我们没办法每次都记住一个容器里面你放进去的是什么类型的数据,所以就会出现经常转错类型,例如:

Arraylist list = new ArrayList();
list.add("12345");

Integer i = (Integer)list.get(0);


如果你这样使用,无疑会抛出 java.lang.ClassCastException,如果没有泛型那,这样的问题就太常见来,容器类使用起来就心惊肉跳,生怕一下子转错类型;

2. 泛型的实现原理是什么,它是怎样解决这样两个问题的?

java 的泛型使用的是擦除法,意思是,只在编译期间对容器的类型进行标记,编译后就把这样类型除去掉,如:

ArrayList<Sting> names = new ArrayList<name>();//编译时会把<Sting>,或者随便什么类型T,去掉,变成、
ArrayList names  = new ArrayList();
T implements Compariable<T> ; //擦出后变成Compariable 而已


2.1 为什么要使用这种方法呢,它有什么好处呢?

用这种方法其实是有历史原因的,当年java1.0 出来的时候并没有泛型,现在要在加上泛型,最大的问题当然是兼容性,不能影响到以前没有泛型的java代码;

所以java的先贤们就想出了这样一种办法,在编译器限定了你编写容器类时就填入特定的类型,如果不填就警告,如果放入和取出不同类型就报错,不让编译,用一个角括号“<>”来填写特定类型,非常简单。它归结起来有者几个特点:

(1)兼容于 Java 語言。Java 泛型是 Java 的超集。每個 Java 程序在 java 加入泛型之后 都仍合法而且不会影响程序的含义;

(2)兼容于 Java 虛擬機器(JVM)。Java 泛型 被編譯為 JVM 碼。JVM 不需為了它而有任何改變。因此傳統 Java 所能執行之處,Java 泛型都能執行;

(3)兼容现在的程序。既有的程序库都能夠和 Java 泛型共同運作,即使是編譯後的 .class。有時候我們也可以將某个個舊程序庫翻新,加如新的带有泛型的程序,而不需更動其源碼。Java collections framework 就是這樣被翻新而加入泛型的。

(4)高效(efficiency)。泛型相關資訊只在編譯期(而非執行期)才被維護著。這意味編譯後的 Java 泛型 程式碼在目的和效率上幾乎完全和傳統的 Java代码一致;

2.2 有没有其他方法也能实现泛型呢?

当然有,C++ 用的是膨胀法,什么是膨胀法呢,例如:

ArrayList abc = new ArrayList();
abc.add("abc");
abc.add(123);
abc.add(1.23);
这样的容器,java在编译期是将“abc”, 123, 1.23 封装成object ;

而c++编译期 是创建出ArrayList<String>, ArrayList<Integer>, ArrayList<Float>三种类型;

关于其他种方法的探讨由于对它们不很了解,在此不表,只是说明实现泛型的方法不止一种,如有兴趣再进行探索;

3. Java 泛型的缺陷

由于java的泛型用了擦除法,只在编译期做类型标记,所以会导致任何运行时需要知道的确切类型信息的操作都将无法工作:

public class Example<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];//Uncheck warning
}
}


不过倒是可以运用限定条件,例如 T extends View 这样的继承关系把T限定为View的子类,这样即使T运行时被擦除了,但是可以还可以使用View的方法;

在此举一个例子,开发android的人都知道,每碰到ListView 的 Adapter 里面都会创建一个VIewHolder来缓存用到的childview,而且每次findviewbyid(id)都要强制转型一次,甚是烦人,其实大可不必每次都在adapter里创建一个ViewHolder,可以创建一个公共的ViewHolder,例子如下:

public class ViewHolder {
//View的tag只能是SpareArray,由于T继承View,也能确定转型成功,所以这里可以设置去掉检查提示
@SuppressWarnings("unchecked")
public static <T extends View> T getView(View view, int id) {
SparseArray<T> viewArray = (SparseArray<T>) view.getTag();
if (viewArray == null) {
viewArray = new SparseArray<T>();
view.setTag(viewArray);
}

T childView = viewArray.get(id);
if(childView == null) {
childView = (T)view.findViewById(id);
viewArray.put(id, childView);
}
return childView;
}
}


这样以后adapter里面就可以直接这样用了:

public View getView(int position, View convertView, ViewGroup group)
{  if(convertView == null) {
convertView = LayoutInfalter.from(context).inflate(layoutId);
}
TextView textview = ViewHolder.get(convertView, textviewId); // 强制转型已经在里面转好,不用每次都去转

}


以上就是我对泛型的一点学习认识,当然泛型的知识远不止这些,例如 泛型T 和 通配符 ? 又有什么区别呢,等我学完再来写~

最后,贴上侯捷先生的两篇研究:

java泛型技术之发展》《java1.5的泛型实现
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: