1.泛型的提出
先來看一段代碼
//創建ArrayList集合,向其中添加字符串
ArrayList al = new ArrayList();
al.add("abc");
al.add("def");
al.add(new Integer(123));//①
Iterator it = al.iterator();
while(it.hasNext()){
String str = (String)it.next();
System.out.println(str);
}
編譯通過,但是運行發現在①處報錯了,因爲在添加Integer對象後,輸出時轉成String對象就會出現類型轉換錯誤。這樣就會造成安全隱患,我們希望能在編譯的時候就提示我們錯誤。所以可以對ArrayList進行類型定義。
ArrayList<String> al = new ArrayList<String>();
al.add("abc");
al.add("def");
//al.add(new Integer(123));編譯報錯,類型錯誤,String類型的ArrayList中無法添加Integer類型的對象
Iterator<String> it = al.iterator();//取出對象也定義String類型,就不需要再做強轉了
while(it.hasNext()){
String str = it.next();
System.out.println(str);
}
這樣就把運行時錯誤轉移到編譯時錯誤,因爲我們已經對ArrayList集合中的元素類型做了定義,所以就無法向其中加入Integer類型的元素。
泛型,即“參數化類型”。一提到參數,最熟悉的就是定義方法時有形參,然後調用此方法時傳遞實參。那麼參數化類型怎麼理解呢?顧名思義,就是將類型由原來的具體的類型參數化,類似於方法中的變量參數,此時類型也定義成參數形式(可以稱之爲類型形參),然後在使用/調用時傳入具體的類型(類型實參)。
//ArrayList接口定義:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public E get(int index) {
rangeCheck(index);
checkForComodification();
return ArrayList.this.elementData(offset + index);
}
//...省略掉其他具體的定義過程
}
在上述例子中,String就是類型實參,而ArrayList接口定義中的E就是類型形參,當給形參傳入String的時候,就確定其具體類型。
2.泛型類、接口、方法
interface Inter<Q> {//泛型接口
public void show1(Q q);
}
public class ClassDemo<T, Q> implements Inter<Q>{//泛型類
private T t;
public void set(T t){
this.t = t;
}
public T get(){
return t;
}
public <W> void show(W w){//泛型方法
System.out.println("show:"+w.toString());
}
public void show1(Q q) {
// TODO Auto-generated method stub
System.out.println("show1:"+q);
}
}
public class Demo {
public static void main(String[] args) {
// TODO Auto-generated method stub
ClassDemo<String,Double> cd = new ClassDemo<String,Double>();
cd.set("abc");
//cd.set(new Integer(12));//類型不匹配
System.out.println(cd.get());
cd.show(new Integer(12));
cd.show1(new Double(123.123));
}
}
輸出結果:
show:12
abc
show1:123.123
3.類型通配符
public static void main(String[] args) {
ArrayList<String> al = new ArrayList<String>();
al.add("abc");
al.add("hehe");
ArrayList<String> al2 = new ArrayList<String>();
al2.add("abc2");
al2.add("hehe2");
printCollection(al);
printCollection(al2);
}
public static void printCollection(Collection<?> al){//使用Collection,LinkedList,HashSet,TreeSet...所有的集合都可以接收
Iterator<String> it = al.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
類型通配符?可以用來表示未知的引用類型
4.泛型的上限和下限
上限
public static void main(String[] args) {
ArrayList<Person> al1 = new ArrayList<Person>();
al1.add(new Person("abc",30));
al1.add(new Person("abc4",34));
ArrayList<Student> al2 = new ArrayList<Student>();
al2.add(new Student("stu1",11));
al2.add(new Student("stu2",22));
ArrayList<Worker> al3 = new ArrayList<Worker>();
al3.add(new Worker("stu1",11));
al3.add(new Worker("stu2",22));
ArrayList<String> al4 = new ArrayList<String>();
al4.add("abcdeef");
//al1.addAll(al4);//錯誤,類型不匹配。
al1.addAll(al2);
al1.addAll(al3);
System.out.println(al1.size());
}
查閱API文檔,添加集合的泛型爲boolean addAll(Collection< ? extends E> c),< ? extends E>表示可以接收E類型或者E的子類型對象,E是類型的上限。因爲即使接收的類型是Student或Worker,但是addAll後都會類型提升爲Person,而Person是可以用來接收Student和Worker的,並且在取出的時候也是Person類型的。
一般情況下,在存儲元素的時候都是用上限,因爲這樣取出都是按照上限類型來運算的。不會出現類型安全隱患。
下限
public static void main(String[] args) {
TreeSet<Person> al1 = new TreeSet<Person>(new CompByName());
al1.add(new Person("abc4",34));
al1.add(new Person("abc1",30));
al1.add(new Person("abc2",38));
TreeSet<Student> al2 = new TreeSet<Student>(new CompByName());
al2.add(new Student("stu1",11));
al2.add(new Student("stu7",20));
al2.add(new Student("stu2",22));
TreeSet<Worker> al3 = new TreeSet<Worker>();
al3.add(new Worker("stu1",11));
al3.add(new Worker("stu2",22));
}
/*
* class TreeSet<Worker>
* {
* Tree(Comparator<? super Worker> comp);
* }
*/
class CompByName implements Comparator<Person>{
public int compare(Person o1, Person o2) {
int temp = o1.getName().compareTo(o2.getName());
return temp==0? o1.getAge()-o2.getAge():temp;
}
}
class CompByStuName implements Comparator<Student>{
public int compare(Student o1, Student o2) {
int temp = o1.getName().compareTo(o2.getName());
return temp==0? o1.getAge()-o2.getAge():temp;
}
}
class CompByWorkerName implements Comparator<Worker>{
public int compare(Worker o1, Worker o2) {
int temp = o1.getName().compareTo(o2.getName());
return temp==0? o1.getAge()-o2.getAge():temp;
}
}
在上面的例子中,我們創建了三個TreeSet集合,並分別向其中存入Person、Student、Worker類型的元素。查看API文檔中關於TreeSet構造函數的說明:public TreeSet(Comparator< ? super E> comparator)構造一個新的空 TreeSet,它根據指定比較器進行排序。所以爲他們分別創建比較器並作爲構造函數的參數進行傳遞。但是觀察發現其實Student和Worker的比較器都是依賴於Person的比較器。而如果我們想要把集合中的元素取出來比較以確定其位置,就要對取出來的元素進行接收,這裏就是由比較器接收。不管我們存放的是什麼類型的元素,我們在接收的時候,可以用該類型來接收,也可以用該類型的父類型來接收。就是說如果我們在al1集合中存入Person和Student類型的元素,在取出來進行比較的時候,接收的時候需要用Person類型來進行接收以保證所有取出來的類型都能接收到。
一般情況下,通常對集合中的元素進行取出操作時,可以使用下限。這樣可以保證所有取出來的類型都能進行接收