第三十四講 初次認識泛型
泛型的簡單概述
泛型是JDK1.5版本以後出現的新特性。它用於解決安全問題,是一個型別安全機制。
泛型的由來
概念說完之後,我們來看看Java語言是如何引入泛型的。在JDK1.4版本之前,容器什麼型別的物件都可以儲存,但是在取出時,需要用到物件的特有內容時,這時需要做向下轉型。比如下面的程式:
public class MyGenericDemo {
public static void main(String[] args) {
List list = new ArrayList();
list.add("abc");
list. add(4); // list.add(Integer.valueOf(4)); 由於集合沒有做任何限定,任何型別都可以存放在其中
for (Iterator it = list.iterator(); it.hasNext();) {
String str = (String) it.next();
System.out.println(str.length());
}
}
}
程式在執行的時候發生了異常:java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
為了避免這個問題,所以程式設計師們只能主觀上控制,往集合中儲存的物件型別保持一致。JDK1.5以後解決了該問題,即在定義集合時,就直接明確集合中儲存元素的具體型別,這樣,編譯器在編譯時,就可以對集合中儲存的物件型別進行檢查,一旦發現型別不匹配,就編譯失敗。這個技術就是泛型技術。
泛型的格式
泛型的格式是通過<>
來定義要操作的引用資料型別。
泛型的好處
泛型的好處:
- 將執行時期出現的問題(例如ClassCastException異常),轉移到了編譯時期,方便於程式設計師解決問題,讓執行時期問題減少,安全;
- 避免了向下轉型的麻煩;
- 優化了程式設計,解決了黃色警告線。
總結:泛型就是應用在編譯時期的一項安全機制。
泛型的擦除
泛型的擦除用一句話來說就是:編譯器通過泛型對元素型別進行檢查,只要檢查通過,就會生成class檔案,但在class檔案中,就將泛型標識去掉了。
泛型的表現
泛型技術在集合框架中應用的範圍很大。那麼什麼時候需要寫泛型呢?只要看到類或介面在描述的時候右邊有定義<>
,就需要泛型了。其實是容器在不明確操作元素的型別的情況下,對外提供了一個引數<>
,使用容器時,只要將具體的型別實參傳遞給該引數即可。說白了,泛型就是傳遞型別引數。
使用泛型技術儲存整數
例,建立一個List集合,用於儲存整數,使用泛型技術。
package cn.liayun.generic.demo;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class GenericDemo2 {
public static void main(String[] args) {
//建立一個List集合,儲存整數。List介面和ArrayList類
List<Integer> list = new ArrayList<Integer>();
list.add(5);
list.add(6);
for (Iterator<Integer> it = list.iterator(); it.hasNext();) {
Integer integer = it.next();
System.out.println(integer);
}
}
}
使用泛型技術儲存自定義物件
例,將Person物件儲存到TreeSet集合中,同姓名同年齡的視為同一個人,不存;按照學生的姓名進行升序排序,而且當姓名相同時,需要按照學生的年齡進行升序排序。
分析:此題主旨就是複習一下TreeSet集合和Comparator介面,我們定義一個比較器實現Comparator介面,覆蓋compare方法,將Comparator介面的實現類作為引數傳遞給TreeSet集合的建構函式。
先描述Person類,由於Person類的物件有可能存放到HashSet集合中,所以我們最好還是覆蓋hashCode()和equals()方法。而且我們還讓Person類實現了Comparable介面,強制讓Person類具備比較性。
package cn.liayun.domain;
public class Person implements Comparable<Person> {
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int hashCode() {
final int NUMBER = 38;
return this.name.hashCode() + age * NUMBER;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Person)) {
throw new ClassCastException("型別錯誤");
}
Person per = (Person)obj;
return this.name.equals(per.name) && this.age == per.age;
}
@Override
public int compareTo(Person o) {
int temp = this.age - o.age;
return temp == 0 ? this.name.compareTo(o.name) : temp;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
接著自定義一個比較器。
package cn.liayun.comparator;
import java.util.Comparator;
import cn.liayun.domain.Person;
public class ComparatorByName implements Comparator<Person> {
@Override
public int compare(Person o1, Person o2) {
int temp = o1.getName().compareTo(o2.getName());
return temp == 0 ? o1.getAge() - o2.getAge() : temp;
}
}
最後編寫一個測試類,進行測試。
package cn.liayun.generic.demo;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;
import cn.liayun.comparator.ComparatorByName;
import cn.liayun.domain.Person;
public class GenericDemo3 {
public static void main(String[] args) {
Set<Person> set = new TreeSet<Person>(new ComparatorByName());
// set = new HashSet<Person>();
set.add(new Person("abcd", 20));
set.add(new Person("abcd", 20));
set.add(new Person("aa", 26));
set.add(new Person("nba", 22));
set.add(new Person("nba", 22));
set.add(new Person("cba", 24));
for (Person person : set) {
System.out.println(person);
}
}
}
自定義泛型類
現在有兩個類,分別為Student和Worker,如下:
-
Student.java
package cn.liayun.domain; public class Student extends Person { public Student() { super(); } public Student(String name, int age) { super(name, age); } @Override public String toString() { return "Student [name = " + getName() + ", age = " + getAge() + "]"; } }
-
Worker.java
package cn.liayun.domain; public class Worker extends Person { public Worker() { super(); } public Worker(String name, int age) { super(name, age); } @Override public String toString() { return "Worker [name=" + getName() + ", age=" + getAge() + "]"; } }
現在我們需要建立一個用於操作Student物件的工具類,對物件進行設定和獲取。其實想都不會想,我們就會這麼做:
class Tool {
private Student stu;
public Student getStu() {
return stu;
}
public void setStu(Student stu) {
this.stu = stu;
}
}
可是發現太有侷限性了,這時我們就要想可不可以定義一個可以操作所有物件的工具呢?答案是可以的,當要操作的物件型別不確定的時候,為了擴充套件,可以使用Object型別來完成,但是這種方式有一些小弊端,會出現向下轉型,向下轉型容易在執行時期發生java.lang.ClassCastException。下面就是JDK1.4版本時(此時泛型技術還沒出現)的程式碼:
//JDK1.4
class Tool {
private Object obj;
public Object getObj() {
return obj;
}
public void setObj(Object obj) {
this.obj = obj;
}
}
JDK1.5以後,新的解決方案出現了。當型別不確定時,可以對外提供引數,由使用者通過傳遞引數的形式完成型別的確定。這樣工具類的程式碼就可這樣寫為:
//在類定義時就明確引數,由使用該類的呼叫者來傳遞具體的型別。
class Util<W> {//泛型類
private W obj;
public W getObj() {
return obj;
}
public void setObj(W obj) {
this.obj = obj;
}
}
以上就是一個自定義的泛型類。
小結
什麼時候定義泛型類呢?當類中要操作的引用資料型別不確定的時候,早期定義Object來完成擴充套件,現在定義泛型來完成擴充套件。
泛型方法
泛型類定義的泛型,在整個類中有效。如果被方法使用,那麼泛型類的物件明確要操作的具體型別後,所有要操作的型別就已經固定了,為了讓不同方法可以操作不同型別,而且型別還不確定,那麼可以將泛型定義在方法上。特殊之處:靜態方法不可以訪問類上定義的泛型。如果靜態方法操作的引用資料型別不確定,可以將泛型定義在方法上。
package cn.liayun.generic.demo;
public class GenericDemo {
public static void main(String[] args) {
Demo<String> d = new Demo<String>();
d.show("abc");
d.print("abc");
d.print(6);
}
}
class Demo<W> {
public /*static*/ void show(W w) {//靜態方法是無法訪問類上定義的泛型的。如果靜態方法需要定義泛型,泛型只能定義在方法上。
System.out.println("show: " + w);
}
public static <A> void staticShow(A a) {
System.out.println("static show: " + a);
}
public <Q> void print(Q w) {//泛型方法
System.out.println("print: " + w);
}
}
泛型介面
泛型可定義在介面上。如:
interface Inter<T> {//泛型介面
public void show(T t);
}
泛型介面有兩種實現方式,其中一種實現方式為:
package cn.liayun.generic.demo;
public class GenericDemo {
public static void main(String[] args) {
InterImpl in = new InterImpl();
in.show("hello generic type");
}
}
interface Inter<T> {//泛型介面
public void show(T t);
}
class InterImpl implements Inter<String> {
@Override
public void show(String t) {
System.out.println(t);
}
}
第二種實現方式為:
package cn.liayun.generic.demo;
public class GenericDemo6 {
public static void main(String[] args) {
SubDemo d = new SubDemo();
d.show("abc");
}
}
interface Inter<T> {//泛型介面
public void show(T t);
}
class InterImpl<W> implements Inter<W> {
@Override
public void show(W t) {
System.out.println("show: " + t);
}
}
class SubDemo extends InterImpl<String> {
}
泛型萬用字元
泛型是在限定資料型別,當在集合或者其他地方使用到泛型後,那麼這時一旦明確泛型的資料型別,那麼在使用的時候只能給其傳遞與資料型別相匹配的型別,否則就會報錯。
上面呼叫方法語句屬於語法錯誤,因為泛型限定不一致。方法要的是Collection<Object>
型別,但傳入的是List<Student>
或者Set<String>
,二者型別不匹配。
上述定義的printCollection方法中,由於定義的是列印集合的功能,應該是可以列印任意集合中的元素的。但定義方法時,根本無法確定具體集合中的元素型別是什麼。為了解決這個”無法確定具體集合中的元素型別”問題,Java中為我們提供了泛型的萬用字元<?>
。對上面的方法,進行修改後,實現了可迭代任意元素型別集合的方法。
private static void printCollection(Collection<?> coll) {//在不明確具體型別的情況下,可以使用萬用字元來表示。
for (Iterator<?> it = coll.iterator(); it.hasNext();) {
Object obj = it.next();
System.out.println(obj);
}
}
總結一下:當使用泛型類或者介面時,傳遞的資料中,泛型型別不確定,可以通過萬用字元<?>
表示。但是一旦使用泛型的萬用字元後,只能使用Object類中的共性方法,集合中元素自身方法無法使用。
泛型的上限
泛型的限定有兩種表現形式,上限和下限。其中上限的格式為:
? extends E:可以接收E型別或者E型別的子型別,泛型的上限
現在我們來演示泛型的上限在API中的體現,以TreeSet的建構函式——TreeSet(Collection<? extends E> c)
為例來詳細講解泛型的上限。
TreeSet集合中有一個這樣的構造方法:
public TreeSet(Collection<? extends E> c) { }
根據該構造方法可以構造一個帶有內容的TreeSet集合,該例中所涉及到的3個類——Person、Student、Worker前文都有描述,繼承體系為:
package cn.liayun.generic.demo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.TreeSet;
import cn.liayun.domain.Person;
import cn.liayun.domain.Student;
public class GenericDemo9 {
public static void main(String[] args) {
/*
* 演示泛型限定在api中的體現。
* TreeSet的建構函式。
* TreeSet(Collection<? extends E> coll);
*
* 什麼時候會用到上限呢?
* 一般往集合中儲存元素時,如果集合中定義了E型別,通常情況下應該儲存E型別的物件。
* 對於E的子型別的物件,E型別也可以接受,所以這時可以將泛型從E改為? extends E。
*/
Collection<Student> coll = new ArrayList<Student>();
coll.add(new Student("abc1", 21));
coll.add(new Student("abc2", 22));
coll.add(new Student("abc3", 23));
coll.add(new Student("abc4", 24));
TreeSet<Person> ts = new TreeSet<Person>(coll);
ts.add(new Person("abc8", 21));
for (Iterator<Person> it = ts.iterator(); it.hasNext();) {
Person person = it.next();
System.out.println(person.getName());
}
}
}
class MyTreeSet<E> {
MyTreeSet() {
}
MyTreeSet(Collection<? extends E> c) {
}
}
小結
什麼時候會用到泛型上限呢?一般往集合中儲存元素時,如果集合定義了E型別,通常情況下應該儲存E型別的物件。對E的子型別的物件也可以接收,所以這時可以將泛型從E改為? extends E。
泛型的下限
下限的格式為:
? super E:可以接收E型別或者E的父型別,泛型的下限
現在我們來演示泛型的下限在API中的體現,以TreeSet的建構函式——TreeSet(Comparator<? super E> comparator)
為例來詳細講解泛型的下限。
TreeSet集合中有一個這樣的構造方法: