前言
從今天開始進入Java基礎(chǔ)的復(fù)習,可能一個星期會有一篇的<十道簡單算法>励幼,我寫博文的未必都是正確的~如果有寫錯的地方請大家多多包涵并指正~
今天要復(fù)習的是泛型苹粟,泛型在Java中也是個很重要的知識點,本文主要講解基礎(chǔ)的概念苛秕,并不是高深的知識,如果基礎(chǔ)好的同學可以當復(fù)習看看~
一店煞、什么是泛型顷蟀?
Java泛型設(shè)計原則:只要在編譯時期沒有出現(xiàn)警告,那么運行時期就不會出現(xiàn)ClassCastException異常.
泛型:把類型明確的工作推遲到創(chuàng)建對象或調(diào)用方法的時候才去明確的特殊的類型
參數(shù)化類型:
- 把類型當作是參數(shù)一樣傳遞
<數(shù)據(jù)類型>
只能是引用類型
相關(guān)術(shù)語:
-
ArrayList<E>
中的E稱為類型參數(shù)變量 -
ArrayList<Integer>
中的Integer稱為實際類型參數(shù) - 整個稱為
ArrayList<E>
泛型類型 - 整個
ArrayList<Integer>
稱為參數(shù)化的類型ParameterizedType
二苟弛、為什么需要泛型
早期Java是使用Object來代表任意類型的,但是向下轉(zhuǎn)型有強轉(zhuǎn)的問題缤削,這樣程序就不太安全
首先亭敢,我們來試想一下:沒有泛型,集合會怎么樣
- Collection扣溺、Map集合對元素的類型是沒有任何限制的。本來我的Collection集合裝載的是全部的Dog對象,但是外邊把Cat對象存儲到集合中,是沒有任何語法錯誤的荠医。
- 把對象扔進集合中,集合是不知道元素的類型是什么的,僅僅知道是Object。因此在get()的時候禁谦,返回的是Object州泊。外邊獲取該對象,還需要強制轉(zhuǎn)換
有了泛型以后:
- 代碼更加簡潔【不用強制轉(zhuǎn)換】
- 程序更加健壯【只要編譯時期沒有警告弟孟,那么運行時期就不會出現(xiàn)ClassCastException異常】
- 可讀性和穩(wěn)定性【在編寫集合的時候,就限定了類型】
2.1有了泛型后使用增強for遍歷集合
在創(chuàng)建集合的時候,我們明確了集合的類型了嘁信,所以我們可以使用增強for來遍歷集合卦溢!
//創(chuàng)建集合對象
ArrayList<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
//遍歷,由于明確了類型.我們可以增強for
for (String s : list) {
System.out.println(s);
}
三、泛型基礎(chǔ)
3.1泛型類
泛型類就是把泛型定義在類上昏苏,用戶使用該類的時候洼专,才把類型明確下來....這樣的話棒掠,用戶明確了什么類型,該類就代表著什么類型...用戶在使用的時候就不用擔心強轉(zhuǎn)的問題屁商,運行時轉(zhuǎn)換異常的問題了句柠。
- 在類上定義的泛型,在類的方法中也可以使用棒假!
/*
1:把泛型定義在類上
2:類型變量定義在類上,方法中也可以使用
*/
public class ObjectTool<T> {
private T obj;
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
}
- 測試代碼:
用戶想要使用哪種類型溯职,就在創(chuàng)建的時候指定類型。使用的時候帽哑,該類就會自動轉(zhuǎn)換成用戶想要使用的類型了谜酒。
public static void main(String[] args) {
//創(chuàng)建對象并指定元素類型
ObjectTool<String> tool = new ObjectTool<>();
tool.setObj(new String("鐘福成"));
String s = tool.getObj();
System.out.println(s);
//創(chuàng)建對象并指定元素類型
ObjectTool<Integer> objectTool = new ObjectTool<>();
/**
* 如果我在這個對象里傳入的是String類型的,它在編譯時期就通過不了了.
*/
objectTool.setObj(10);
int i = objectTool.getObj();
System.out.println(i);
}
3.2泛型方法
前面已經(jīng)介紹了泛型類了,在類上定義的泛型妻枕,在方法中也可以使用.....
現(xiàn)在呢僻族,我們可能就僅僅在某一個方法上需要使用泛型....外界僅僅是關(guān)心該方法,不關(guān)心類其他的屬性...這樣的話屡谐,我們在整個類上定義泛型述么,未免就有些大題小作了。
- 定義泛型方法....泛型是先定義后使用的
//定義泛型方法..
public <T> void show(T t) {
System.out.println(t);
}
- 測試代碼:
用戶傳遞進來的是什么類型愕掏,返回值就是什么類型了
public static void main(String[] args) {
//創(chuàng)建對象
ObjectTool tool = new ObjectTool();
//調(diào)用方法,傳入的參數(shù)是什么類型,返回值就是什么類型
tool.show("hello");
tool.show(12);
tool.show(12.5);
}
3.3泛型類派生出的子類
前面我們已經(jīng)定義了泛型類度秘,泛型類是擁有泛型這個特性的類,它本質(zhì)上還是一個Java類饵撑,那么它就可以被繼承
那它是怎么被繼承的呢剑梳??這里分兩種情況
- 子類明確泛型類的類型參數(shù)變量
- 子類不明確泛型類的類型參數(shù)變量
3.3.1子類明確泛型類的類型參數(shù)變量
- 泛型接口
/*
把泛型定義在接口上
*/
public interface Inter<T> {
public abstract void show(T t);
}
- 實現(xiàn)泛型接口的類.....
/**
* 子類明確泛型類的類型參數(shù)變量:
*/
public class InterImpl implements Inter<String> {
@Override
public void show(String s) {
System.out.println(s);
}
}
3.3.2子類不明確泛型類的類型參數(shù)變量
- 當子類不明確泛型類的類型參數(shù)變量時滑潘,外界使用子類的時候垢乙,也需要傳遞類型參數(shù)變量進來,在實現(xiàn)類上需要定義出類型參數(shù)變量
/**
* 子類不明確泛型類的類型參數(shù)變量:
* 實現(xiàn)類也要定義出<T>類型的
*
*/
public class InterImpl<T> implements Inter<T> {
@Override
public void show(T t) {
System.out.println(t);
}
}
測試代碼:
public static void main(String[] args) {
//測試第一種情況
//Inter<String> i = new InterImpl();
//i.show("hello");
//第二種情況測試
Inter<String> ii = new InterImpl<>();
ii.show("100");
}
值得注意的是:
- 實現(xiàn)類的要是重寫父類的方法语卤,返回值的類型是要和父類一樣的追逮!
- 類上聲明的泛形只對非靜態(tài)成員有效
3.4類型通配符
為什么需要類型通配符?粹舵?钮孵??我們來看一個需求.......
現(xiàn)在有個需求:方法接收一個集合參數(shù)齐婴,遍歷集合并把集合元素打印出來油猫,怎么辦稠茂?
- 按照我們沒有學習泛型之前柠偶,我們可能會這樣做:
public void test(List list){
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
}
上面的代碼是正確的情妖,只不過在編譯的時候會出現(xiàn)警告,說沒有確定集合元素的類型....這樣是不優(yōu)雅的...
- 那我們學習了泛型了诱担,現(xiàn)在要怎么做呢毡证??有的人可能會這樣做:
public void test(List<Object> list){
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
}
這樣做語法是沒毛病的蔫仙,但是這里十分值得注意的是:該test()方法只能遍歷裝載著Object的集合A暇Α!摇邦!
強調(diào):泛型中的<Object>
并不是像以前那樣有繼承關(guān)系的恤煞,也就是說List<Object>
和List<String>
是毫無關(guān)系的!J┘>影恰!
那現(xiàn)在咋辦丑慎?喜喂??我們是不清楚List集合裝載的元素是什么類型的竿裂,List<Objcet>
這樣是行不通的........于是Java泛型提供了類型通配符 ?
所以代碼應(yīng)該改成這樣:
public void test(List<?> list){
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
}
?號通配符表示可以匹配任意類型玉吁,任意的Java類都可以匹配.....
現(xiàn)在非常值得注意的是,當我們使用?號通配符的時候:就只能調(diào)對象與類型無關(guān)的方法腻异,不能調(diào)用對象與類型有關(guān)的方法进副。
記住,只能調(diào)用與對象無關(guān)的方法悔常,不能調(diào)用對象與類型有關(guān)的方法敢会。因為直到外界使用才知道具體的類型是什么。也就是說这嚣,在上面的List集合鸥昏,我是不能使用add()方法的。因為add()方法是把對象丟進集合中姐帚,而現(xiàn)在我是不知道對象的類型是什么吏垮。
3.4.1設(shè)定通配符上限
首先,我們來看一下設(shè)定通配符上限用在哪里....
現(xiàn)在罐旗,我想接收一個List集合膳汪,它只能操作數(shù)字類型的元素【Float、Integer九秀、Double遗嗽、Byte等數(shù)字類型都行】,怎么做鼓蜒?痹换?征字?
我們學習了通配符,但是如果直接使用通配符的話娇豫,該集合就不是只能操作數(shù)字了匙姜。因此我們需要用到設(shè)定通配符上限
List<? extends Number>
上面的代碼表示的是:List集合裝載的元素只能是Number的子類或自身
public static void main(String[] args) {
//List集合裝載的是Integer,可以調(diào)用該方法
List<Integer> integer = new ArrayList<>();
test(integer);
//List集合裝載的是String冯痢,在編譯時期就報錯了
List<String> strings = new ArrayList<>();
test(strings);
}
public static void test(List<? extends Number> list) {
}
3.4.2設(shè)定通配符下限
既然上面我們已經(jīng)說了如何設(shè)定通配符的上限氮昧,那么設(shè)定通配符的下限也不是陌生的事了。直接來看語法吧
//傳遞進來的只能是Type或Type的父類
<? super Type>
設(shè)定通配符的下限這并不少見浦楣,在TreeSet集合中就有....我們來看一下
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
那它有什么用呢袖肥??我們來想一下振劳,當我們想要創(chuàng)建一個TreeSet<String>
類型的變量的時候昭伸,并傳入一個可以比較String大小的Comparator。
那么這個Comparator的選擇就有很多了澎迎,它可以是Comparator<String>
庐杨,還可以是類型參數(shù)是String的父類,比如說Comparator<Objcet>
....
這樣做夹供,就非常靈活了灵份。也就是說,只要它能夠比較字符串大小哮洽,就行了
值得注意的是:無論是設(shè)定通配符上限還是下限填渠,都是不能操作與對象有關(guān)的方法,只要涉及到了通配符鸟辅,它的類型都是不確定的氛什!
3.5通配符和泛型方法
大多時候,我們都可以使用泛型方法來代替通配符的.....
//使用通配符
public static void test(List<?> list) {
}
//使用泛型方法
public <T> void test2(List<T> t) {
}
上面這兩個方法都是可以的.....那么現(xiàn)在問題來了匪凉,我們使用通配符還是使用泛型方法呢枪眉??
原則:
- 如果參數(shù)之間的類型有依賴關(guān)系再层,或者返回值是與參數(shù)之間有依賴關(guān)系的贸铜。那么就使用泛型方法
- 如果沒有依賴關(guān)系的,就使用通配符聂受,通配符會靈活一些.
3.6泛型擦除
泛型是提供給javac編譯器使用的蒿秦,它用于限定集合的輸入類型,讓編譯器在源代碼級別上蛋济,即擋住向集合中插入非法數(shù)據(jù)棍鳖。但編譯器編譯完帶有泛形的java程序后,生成的class文件中將不再帶有泛形信息碗旅,以此使程序運行效率不受到影響渡处,這個過程稱之為“擦除”镜悉。
3.6.1兼容性
JDK5提出了泛型這個概念,但是JDK5以前是沒有泛型的骂蓖。也就是泛型是需要兼容JDK5以下的集合的。
當把帶有泛型特性的集合賦值給老版本的集合時候川尖,會把泛型給擦除了登下。
值得注意的是:它保留的就類型參數(shù)的上限。
List<String> list = new ArrayList<>();
//類型被擦除了叮喳,保留的是類型的上限被芳,String的上限就是Object
List list1 = list;
如果我把沒有類型參數(shù)的集合賦值給帶有類型參數(shù)的集合賦值,這又會怎么樣馍悟?畔濒?
List list = new ArrayList();
List<String> list2 = list;
它也不會報錯,僅僅是提示“未經(jīng)檢查的轉(zhuǎn)換”
四锣咒、泛型的應(yīng)用
當我們寫網(wǎng)頁的時候侵状,常常會有多個DAO,我們要寫每次都要寫好幾個DAO毅整,這樣會有點麻煩趣兄。
那么我們想要的效果是什么呢?悼嫉?只寫一個抽象DAO艇潭,別的DAO只要繼承該抽象DAO,就有對應(yīng)的方法了戏蔑。
要實現(xiàn)這樣的效果蹋凝,肯定是要用到泛型的。因為在抽象DAO中总棵,是不可能知道哪一個DAO會繼承它自己鳍寂,所以是不知道其具體的類型的。而泛型就是在創(chuàng)建的時候才指定其具體的類型情龄。
- 抽象DAO
public abstract class BaseDao<T> {
//模擬hibernate....
private Session session;
private Class clazz;
//哪個子類調(diào)的這個方法伐割,得到的class就是子類處理的類型(非常重要)
public BaseDao(){
Class clazz = this.getClass(); //拿到的是子類
ParameterizedType pt = (ParameterizedType) clazz.getGenericSuperclass(); //BaseDao<Category>
clazz = (Class) pt.getActualTypeArguments()[0];
System.out.println(clazz);
}
public void add(T t){
session.save(t);
}
public T find(String id){
return (T) session.get(clazz, id);
}
public void update(T t){
session.update(t);
}
public void delete(String id){
T t = (T) session.get(clazz, id);
session.delete(t);
}
}
- 繼承抽象DAO,該實現(xiàn)類就有對應(yīng)的增刪改查的方法了刃唤。
CategoryDao
public class CategoryDao extends BaseDao<Category> {
}
BookDao
public class BookDao extends BaseDao<Book> {
}
五隔心、最后
泛型的基礎(chǔ)就介紹到這里了,如果以后有需要的話再進行深入研究吧~如果覺得該文章幫助到你尚胞,不妨點個贊硬霍,關(guān)注公眾號一波~
參考資料:
- Core Java
如果文章有錯的地方歡迎指正,大家互相交流笼裳。習慣在微信看技術(shù)文章唯卖,想要獲取更多的Java資源的同學粱玲,可以關(guān)注微信公眾號:Java3y