從今天開始進入Java基礎的複習,可能一個星期會有一篇的<十道簡單算法>,我寫博文的未必都是正確的~若是有寫錯的地方請你們多多包涵並指正~java
今天要複習的是泛型,泛型在Java中也是個很重要的知識點,本文主要講解基礎的概念,並非高深的知識,若是基礎好的同窗能夠當複習看看~算法
Java泛型設計原則:只要在編譯時期沒有出現警告,那麼運行時期就不會出現ClassCastException異常.安全
泛型:把類型明確的工做推遲到建立對象或調用方法的時候纔去明確的特殊的類型微信
參數化類型:session
<數據類型>
只能是引用類型相關術語:ide
ArrayList<E>
中的E稱爲類型參數變量 ArrayList<Integer>
中的Integer稱爲實際類型參數 ArrayList<E>
泛型類型ArrayList<Integer>
稱爲參數化的類型ParameterizedType早期Java是使用Object來表明任意類型的,可是向下轉型有強轉的問題,這樣程序就不太安全學習
首先,咱們來試想一下:沒有泛型,集合會怎麼樣測試
有了泛型之後:this
在建立集合的時候,咱們明確了集合的類型了,因此咱們可使用加強for來遍歷集合!spa
//建立集合對象 ArrayList<String> list = new ArrayList<>(); list.add("hello"); list.add("world"); list.add("java"); //遍歷,因爲明確了類型.咱們能夠加強for for (String s : list) { System.out.println(s); }
泛型類就是把泛型定義在類上,用戶使用該類的時候,才把類型明確下來....這樣的話,用戶明確了什麼類型,該類就表明着什麼類型...用戶在使用的時候就不用擔憂強轉的問題,運行時轉換異常的問題了。
/* 1:把泛型定義在類上 2:類型變量定義在類上,方法中也可使用 */ public class ObjectTool<T> { private T obj; public T getObj() { return obj; } public void setObj(T obj) { this.obj = obj; } }
用戶想要使用哪一種類型,就在建立的時候指定類型。使用的時候,該類就會自動轉換成用戶想要使用的類型了。
public static void main(String[] args) { //建立對象並指定元素類型 ObjectTool<String> tool = new ObjectTool<>(); tool.setObj(new String("鍾福成")); String s = tool.getObj(); System.out.println(s); //建立對象並指定元素類型 ObjectTool<Integer> objectTool = new ObjectTool<>(); /** * 若是我在這個對象裏傳入的是String類型的,它在編譯時期就經過不了了. */ objectTool.setObj(10); int i = objectTool.getObj(); System.out.println(i); }
前面已經介紹了泛型類了,在類上定義的泛型,在方法中也可使用.....
如今呢,咱們可能就僅僅在某一個方法上須要使用泛型....外界僅僅是關心該方法,不關心類其餘的屬性...這樣的話,咱們在整個類上定義泛型,未免就有些大題小做了。
//定義泛型方法.. public <T> void show(T t) { System.out.println(t); }
用戶傳遞進來的是什麼類型,返回值就是什麼類型了
public static void main(String[] args) { //建立對象 ObjectTool tool = new ObjectTool(); //調用方法,傳入的參數是什麼類型,返回值就是什麼類型 tool.show("hello"); tool.show(12); tool.show(12.5); }
前面咱們已經定義了泛型類,泛型類是擁有泛型這個特性的類,它本質上仍是一個Java類,那麼它就能夠被繼承
那它是怎麼被繼承的呢??這裏分兩種狀況
/* 把泛型定義在接口上 */ public interface Inter<T> { public abstract void show(T t); }
/** * 子類明確泛型類的類型參數變量: */ public class InterImpl implements Inter<String> { @Override public void show(String s) { System.out.println(s); } }
/** * 子類不明確泛型類的類型參數變量: * 實現類也要定義出<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"); }
值得注意的是:
爲何須要類型通配符????咱們來看一個需求.......
如今有個需求:方法接收一個集合參數,遍歷集合並把集合元素打印出來,怎麼辦?
public void test(List list){ for(int i=0;i<list.size();i++){ System.out.println(list.get(i)); } }
上面的代碼是正確的,只不過在編譯的時候會出現警告,說沒有肯定集合元素的類型....這樣是不優雅的...
public void test(List<Object> list){ for(int i=0;i<list.size();i++){ System.out.println(list.get(i)); } }
這樣作語法是沒毛病的,可是這裏十分值得注意的是:該test()方法只能遍歷裝載着Object的集合!!!
強調:泛型中的<Object>
並非像之前那樣有繼承關係的,也就是說List<Object>
和List<String>
是毫無關係的!!!!
那如今咋辦???咱們是不清楚List集合裝載的元素是什麼類型的,List<Objcet>
這樣是行不通的........因而Java泛型提供了類型通配符 ?
因此代碼應該改爲這樣:
public void test(List<?> list){ for(int i=0;i<list.size();i++){ System.out.println(list.get(i)); } }
?號通配符表示能夠匹配任意類型,任意的Java類均可以匹配.....
如今很是值得注意的是,當咱們使用?號通配符的時候:就只能調對象與類型無關的方法,不能調用對象與類型有關的方法。
記住,只能調用與對象無關的方法,不能調用對象與類型有關的方法。由於直到外界使用才知道具體的類型是什麼。也就是說,在上面的List集合,我是不能使用add()方法的。由於add()方法是把對象丟進集合中,而如今我是不知道對象的類型是什麼。
首先,咱們來看一下設定通配符上限用在哪裏....
如今,我想接收一個List集合,它只能操做數字類型的元素【Float、Integer、Double、Byte等數字類型都行】,怎麼作???
咱們學習了通配符,可是若是直接使用通配符的話,該集合就不是隻能操做數字了。所以咱們須要用到設定通配符上限
List<? extends Number>
上面的代碼表示的是:List集合裝載的元素只能是Number的子類或自身
public static void main(String[] args) { //List集合裝載的是Integer,能夠調用該方法 List<Integer> integer = new ArrayList<>(); test(integer); //List集合裝載的是String,在編譯時期就報錯了 List<String> strings = new ArrayList<>(); test(strings); } public static void test(List<? extends Number> list) { }
既然上面咱們已經說了如何設定通配符的上限,那麼設定通配符的下限也不是陌生的事了。直接來看語法吧
//傳遞進來的只能是Type或Type的父類 <? super Type>
設定通配符的下限這並很多見,在TreeSet集合中就有....咱們來看一下
public TreeSet(Comparator<? super E> comparator) { this(new TreeMap<>(comparator)); }
那它有什麼用呢??咱們來想一下,當咱們想要建立一個TreeSet<String>
類型的變量的時候,並傳入一個能夠比較String大小的Comparator。
那麼這個Comparator的選擇就有不少了,它能夠是Comparator<String>
,還能夠是類型參數是String的父類,好比說Comparator<Objcet>
....
這樣作,就很是靈活了。也就是說,只要它可以比較字符串大小,就好了
經評論去補充:在泛型的上限和下限中有一個原則:PECS(Producer Extends Consumer Super)
書上是這樣寫的:
帶有子類限定的能夠從泛型讀取【也就是--->(? extend T)】-------->Producer Extends
帶有超類限定的能夠從泛型寫入【也就是--->(? super T)】-------->Consumer Super
也有相關博文寫得很好:
http://blog.51cto.com/flyingc...
https://blog.csdn.net/xx32666...
大多時候,咱們均可以使用泛型方法來代替通配符的.....
//使用通配符 public static void test(List<?> list) { } //使用泛型方法 public <T> void test2(List<T> t) { }
上面這兩個方法都是能夠的.....那麼如今問題來了,咱們使用通配符仍是使用泛型方法呢??
原則:
泛型是提供給javac編譯器使用的,它用於限定集合的輸入類型,讓編譯器在源代碼級別上,即擋住向集合中插入非法數據。但編譯器編譯完帶有泛形的java程序後,生成的class文件中將再也不帶有泛形信息,以此使程序運行效率不受到影響,這個過程稱之爲「擦除」。
JDK5提出了泛型這個概念,可是JDK5之前是沒有泛型的。也就是泛型是須要兼容JDK5如下的集合的。
當把帶有泛型特性的集合賦值給老版本的集合時候,會把泛型給擦除了。
值得注意的是:它保留的就類型參數的上限。
List<String> list = new ArrayList<>(); //類型被擦除了,保留的是類型的上限,String的上限就是Object List list1 = list;
若是我把沒有類型參數的集合賦值給帶有類型參數的集合賦值,這又會怎麼樣??
List list = new ArrayList(); List<String> list2 = list;
它也不會報錯,僅僅是提示「未經檢查的轉換」
當咱們寫網頁的時候,經常會有多個DAO,咱們要寫每次都要寫好幾個DAO,這樣會有點麻煩。
那麼咱們想要的效果是什麼呢??只寫一個抽象DAO,別的DAO只要繼承該抽象DAO,就有對應的方法了。
要實現這樣的效果,確定是要用到泛型的。由於在抽象DAO中,是不可能知道哪個DAO會繼承它本身,因此是不知道其具體的類型的。而泛型就是在建立的時候才指定其具體的類型。
public abstract class BaseDao<T> { //模擬hibernate.... private Session session; private Class clazz; //哪一個子類調的這個方法,獲得的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); } }
CategoryDao
public class CategoryDao extends BaseDao<Category> { }
BookDao
public class BookDao extends BaseDao<Book> { }
泛型的基礎就介紹到這裏了,若是之後有須要的話再進行深刻研究吧~若是以爲該文章幫助到你,不妨點個贊,關注公衆號一波~
參考資料:
若是文章有錯的地方歡迎指正,你們互相交流。習慣在微信看技術文章,想要獲取更多的Java資源的同窗,能夠 關注微信公衆號:Java3y