Java中泛型使用的簡(jiǎn)單方法介紹
一. 泛型是什么
“泛型”,顧名思義,“泛指的類(lèi)型”。我們提供了泛指的概念,但具體執(zhí)行的時(shí)候卻可以有具體的規(guī)則來(lái)約束,比如我們用的非常多的ArrayList就是個(gè)泛型類(lèi),ArrayList作為集合可以存放各種元素,如Integer, String,自定義的各種類(lèi)型等,但在我們使用的時(shí)候通過(guò)具體的規(guī)則來(lái)約束,如我們可以約束集合中只存放Integer類(lèi)型的元素,如List<Integer> iniData = new ArrayList<>()。
二. 使用泛型有什么好處
以集合來(lái)舉例,使用泛型的好處是我們不必因?yàn)樘砑釉仡?lèi)型的不同而定義不同類(lèi)型的集合,如整型集合類(lèi),浮點(diǎn)型集合類(lèi),字符串集合類(lèi),我們可以定義一個(gè)集合來(lái)存放整型、浮點(diǎn)型,字符串型數(shù)據(jù),而這并不是最重要的,因?yàn)槲覀冎灰训讓哟鎯?chǔ)設(shè)置了Object即可,添加的數(shù)據(jù)全部都可向上轉(zhuǎn)型為Object。 更重要的是我們可以通過(guò)規(guī)則按照自己的想法控制存儲(chǔ)的數(shù)據(jù)類(lèi)型。
我們以ArrayList為例,假如我們要將本月截至今天的日期放到ArrayList中,如果我們不使用泛型,此時(shí)我們定義一個(gè)ArrayList.
List monthDays = new ArrayList();
我們向其中加入1號(hào)到4號(hào)日期
public static List addMonthDays(){ List monthDays = new ArrayList(); monthDays.add(LocalDate.now().withDayOfMonth(1)); monthDays.add(LocalDate.now().withDayOfMonth(2)); monthDays.add(LocalDate.now().withDayOfMonth(3)); monthDays.add(new Date()); return monthDays; }
這樣有沒(méi)有問(wèn)題?大家也看出來(lái)了,當(dāng)然有,雖然都可以表示日期,但卻用了Date,LocalDate,我們調(diào)用方法直接打印出來(lái),就是這樣
public static void main(String[] args) { List monthDays = addMonthDays(); for(Object day : monthDays){ System.out.println(day); } }
2019-08-01
2019-08-02
2019-08-03
Sun Aug 04 10:27:10 CST 2019
我們肯定不想要這樣的結(jié)果,我們想要的是
2019-08-01
2019-08-02
2019-08-03
2019-08-04
如果存儲(chǔ)的元素類(lèi)型只是這兩種(假如我們知道),這個(gè)時(shí)候我們就手動(dòng)判斷一下
public static void main(String[] args) { List monthDays = addMonthDays(); for(Object day : monthDays){ if (day instanceof Date){ Date date = (Date) day; System.out.println(LocalDate.of(date.getYear(), date.getMonth(), date.getDay())); }else { System.out.println(day); } } }
這個(gè)時(shí)候我們就可以達(dá)成上述目的了,但大家也知道,這種寫(xiě)法問(wèn)題問(wèn)題很大
- 我們無(wú)法控制存儲(chǔ)的元素到底是否和日期相關(guān),如我們存儲(chǔ)了“1”,65536等非日期,定義的方法也不會(huì)報(bào)錯(cuò),但在調(diào)用進(jìn)行類(lèi)型轉(zhuǎn)換的時(shí)候必然會(huì)報(bào)錯(cuò);
- 我們不知道集合中到底存儲(chǔ)了哪些類(lèi)型的元素,比如還有“2019/08/04”這種日期字符串、java.sql.Date這種類(lèi)型呢,我們很難保證可以窮盡;
- 代碼過(guò)于復(fù)雜,太難維護(hù);
這時(shí)泛型就提供了很好的解決方案,從源頭上就制定好規(guī)則,只能添加LocalDate類(lèi)型,那么上述的問(wèn)題就都得以解決了。
public static List<LocalDate> addFormatMonthDays(){ List<LocalDate> monthDays = new ArrayList<>(); monthDays.add(LocalDate.now().withDayOfMonth(1)); monthDays.add(LocalDate.now().withDayOfMonth(2)); monthDays.add(LocalDate.now().withDayOfMonth(3)); monthDays.add(new Date());//這個(gè)代碼在編譯期間就無(wú)法通過(guò)了 return monthDays; }
不僅提高了代碼的可讀性,一眼即可看出我們存儲(chǔ)的是LocalDate類(lèi)型。同時(shí)編譯器也可很好的利用該信息,編譯期間就可進(jìn)行類(lèi)型檢查,保證了安全性,在get的時(shí)候,無(wú)需進(jìn)行強(qiáng)制類(lèi)型轉(zhuǎn)換。
三. 泛型類(lèi)
為使類(lèi)適應(yīng)更多的情況,具備更好的擴(kuò)展性,我們可以將其設(shè)置為泛型類(lèi),即具有一個(gè)或多個(gè)類(lèi)型變量的類(lèi),寫(xiě)法如下:
public class ClassName<泛型標(biāo)識(shí),可以是字母、中文字符等,不過(guò)一般用大寫(xiě)英文字符> { private 泛型標(biāo)識(shí) property; }
對(duì)于泛型標(biāo)識(shí)符,一般有一些約定俗稱(chēng)的寫(xiě)法,如果是表示集合的元素類(lèi)型,一般用字母E,如我們常用的ArrayList,public class ArrayList<E>,我們自定義如下:
//車(chē)庫(kù) public class Garage<E> { //向車(chē)庫(kù)中添加車(chē) public void add(E car){ //... } }
我們還可以用字符K和V表示關(guān)鍵字和值的類(lèi)型,如我們常用的HashMap,public class HashMap<K,V>,我們也可以自定義如下:
//映射關(guān)系 //K,V: 蔬菜,是否有機(jī); 水果,產(chǎn)地; 服裝,類(lèi)型; 汽車(chē),品牌 public class Mapping<K, V> { private K key; private V value; }
我們還經(jīng)常用一個(gè)字符T表示類(lèi)型
public class Person<T> { private T t; public Person(T t){ this.t = t; } public void run(){ System.out.println(t); } }
如何使用泛型類(lèi),類(lèi)型如何實(shí)例化,我們只需要保證傳入的實(shí)參類(lèi)型和泛型參數(shù)類(lèi)型相同即可。
//正常用法 Person<String> s1 = new Person<String>("張三"); //jdk7.0后可以省略后面的參數(shù)類(lèi)型 Person<String> s2 = new Person<>("張三"); s2.run(); //當(dāng)然泛型的定義是可以幫助我們按照某種規(guī)則去做事,如果不做限制,也不會(huì)編譯錯(cuò)誤,但泛型就毫無(wú)意義了 Person t = new Person(111); t.run(); //泛型的類(lèi)型不能是八大基本類(lèi)型,下面會(huì)編譯出錯(cuò) Person<int> s = new Person<>(1);
四. 泛型接口
泛型接口和泛型類(lèi)的定義基本一致,定義如下:
public interface Person<T> { public T parent(); public String eat(); }
當(dāng)我們定義了一個(gè)類(lèi)要實(shí)現(xiàn)該接口時(shí),那么該類(lèi)的泛型類(lèi)型必須和接口類(lèi)的泛型類(lèi)型一致,未傳遞實(shí)參的情況下,繼續(xù)使用泛型類(lèi)型T,傳遞了實(shí)參的情況下,泛型類(lèi)型必須使用實(shí)參類(lèi)型
public class Teacher<T> implements Person<T> { @Override public T parent() { return null; } @Override public String eat() { return null; } }
//Teacher不必再定義類(lèi)型了,因?yàn)榉盒皖?lèi)型在Person處已經(jīng)定義好了 public class Teacher implements Person<Integer> { //這里的返回類(lèi)型必須為Integer,否則必須出錯(cuò) @Override public Integer parent() { return null; } @Override public String eat() { return null; } }
五. 泛型方法
泛型方法可以定義在普通類(lèi)和泛型類(lèi)中,比如泛型類(lèi)更為常用,一般能用泛型方法解決的問(wèn)題優(yōu)先使用泛型方法而不使用泛型類(lèi),類(lèi)型變量放在修飾符的后面,如public static ,public final等的后面。
public class Teacher { public static <T> T println(T t){ System.out.println(t); return t; } }
調(diào)用很簡(jiǎn)單,很一般方法調(diào)用是一樣的,更方便的是類(lèi)型不像一般方法做了限定。
String s = Teancher.println("str");
另外需要說(shuō)明的是,定義在泛型類(lèi)中的泛型方法的泛型變量之間是沒(méi)有關(guān)系的,如這樣的代碼
public class Teacher<T> { T teacher; public Teacher(T t){ this.teacher = t; } public <T> T println(T t){ System.out.println(t); return t; } }
Teacher<String> teacher = new Teacher<>("張三"); Integer in = teacher.println(123456);
類(lèi)泛型類(lèi)型為String,方法的泛型類(lèi)型為Integer,雖然都是用T來(lái)表示的。
同時(shí)關(guān)于泛型方法需要說(shuō)明的是:
在修飾符public xx與方法名之間非常重要,有< T >這樣的才算是泛型方法;僅僅使用了泛型變量并不算是泛型方法。
六. 限定類(lèi)型變量
不論是泛型類(lèi)還是泛型方法,目前來(lái)說(shuō)其實(shí)都是沒(méi)有做類(lèi)型限定,無(wú)論我們傳遞什么樣類(lèi)型的變量進(jìn)去都可以,因?yàn)槲覀冊(cè)谔幚磉壿嬛胁](méi)有使用到該類(lèi)型特有的東西(成員變量、方法等)。假如我們想傳遞的參數(shù)類(lèi)型僅僅是某個(gè)大類(lèi)(父類(lèi))下面的一些小類(lèi)(子類(lèi)),那么怎么做呢?
public class ArrayFlag { public static <T> T getMax(T[] array){ if(array == null || array.length == 0){ return null; } T maxValue = array[0]; for(int i = 0; i < array.length; i++){ if(array[i].compareTo(maxValue) > 0){ maxValue = array[i]; } } return maxValue; } }
大家也看到了我們?cè)趃etMax方法中使用了compareTo方法進(jìn)行比較,但如果我們傳入的類(lèi)型T沒(méi)有compareTo方法呢,豈不是要報(bào)錯(cuò),因此我們需要做限定,只要限定了是Comparable接口的必然具備compareTo方法,那么改造后就成了這樣
public class ArrayFlag { public static <T extends Comparable> T getMax(T[] array){ if(array == null || array.length == 0){ return null; } T maxValue = array[0]; for(int i = 0; i < array.length; i++){ if(array[i].compareTo(maxValue) > 0){ maxValue = array[i]; } } return maxValue; } }
同時(shí)需要說(shuō)明的是,此處用的是extends關(guān)鍵字,extends在這里是表示的是綁定了Comparable接口及其子類(lèi)型,是“綁定、限定”的意思,非“繼承”的意思,后面也可以是接口或者類(lèi),如果有多個(gè)限制,可以使用&分隔,如:
public static <T extends Comparable & Serializable> T getMax(T[] array)
七. 泛型通配符
舉個(gè)例子,定義了一個(gè)書(shū)籍類(lèi)和一個(gè)小說(shuō)類(lèi)
//定義了一個(gè)書(shū)籍類(lèi) public class Book {} //定義了一個(gè)小說(shuō)書(shū)籍類(lèi)繼承書(shū)籍類(lèi) public class Novel extends Book {}
我們?cè)俣x一個(gè)書(shū)柜類(lèi)用來(lái)裝書(shū)籍以及小說(shuō)
//定義了一個(gè)書(shū)柜類(lèi)用來(lái)裝書(shū) public class Bookcase<T> { T b; public Bookcase(T t){ this.b = t; } public void set(T t) { b=t; } public T get() { System.out.println(b.getClass()); return b; } }
下面我們就用書(shū)柜來(lái)裝小說(shuō)
//以前的寫(xiě)法,無(wú)法編譯通過(guò),提示Incompatible types, Required Book Found Novel Bookcase<Book> bc = new Bookcase<Novel>(new Novel());
但在jdk7.0后,new Bookcase的時(shí)候是可以不用給出泛型類(lèi)型的,省略的類(lèi)型可以從變量的類(lèi)型推斷得出,因此如果下面這種寫(xiě)法
Bookcase<Book> bc = new Bookcase<>(new Novel()); System.out.println(bc.getClass()); bc.get();
此時(shí)可以編譯通過(guò),我們執(zhí)行后得出的結(jié)果是:
class generic.Bookcase class generic.Novel
當(dāng)然我們還可以通過(guò)通配符來(lái)解決該問(wèn)題,通配符包括以下幾種:
上界通配符、下界通配符、無(wú)限定通配符
7.1 上界通配符
上界通配符定義方式如下:用extends 關(guān)鍵字,含義是該書(shū)柜只能放置小說(shuō)類(lèi)書(shū)籍(如什么都市小說(shuō)、愛(ài)情小說(shuō)、玄幻小說(shuō)都可以),但不能放置父類(lèi)書(shū)籍、其他類(lèi)如史書(shū)、職場(chǎng)類(lèi)書(shū)籍、財(cái)經(jīng)類(lèi)書(shū)籍等,是在使用的時(shí)候進(jìn)行限定,如:
Bookcase<? extends Book> bc = new Bookcase<Novel>(new Novel());
這種定義方式就不會(huì)編譯錯(cuò)誤了。另外關(guān)于上界通配符的特點(diǎn),對(duì)上有限制,根據(jù)java多態(tài)向上造型的原則,不適合頻繁插入數(shù)據(jù),適合頻繁讀取數(shù)據(jù)的場(chǎng)景。
7.2 下界通配符
下界通配符定義方式如下:用super關(guān)鍵字,含義就是書(shū)柜放置設(shè)置了下限,我們只能放置Book書(shū)籍以及Novel書(shū)籍,卻無(wú)法再將細(xì)分的都市小說(shuō)、愛(ài)情小說(shuō)類(lèi)書(shū)籍放進(jìn)去
Bookcase<? super Novel> bc = new Bookcase<Novel>(new Novel());
另外關(guān)于下界通配符的特點(diǎn),和上界通配符正好相反,不適合頻繁讀取數(shù)據(jù),適合頻繁插入數(shù)據(jù)的場(chǎng)景。
7.3 無(wú)限定通配符
無(wú)限定通配符意味著可以使用任何對(duì)象,因此使用它類(lèi)似于使用原生類(lèi)型。但它是有作用的,原生類(lèi)型可以持有任何類(lèi)型,而無(wú)限定通配符修飾的容器持有的是某種具體的類(lèi)型。
舉個(gè)例子:
List<?> list = new ArrayList<>(); //無(wú)法編譯通過(guò) list.add(new Object()); //下面這樣的卻可以添加任何類(lèi)型 List<Object> list = new ArrayList<>(); list.add(new Object());
再說(shuō)一下< T > 和< ? >之間的區(qū)別,初看好像他們都可以表示泛型變量,都可以extends,但它們確實(shí)有不同的使用場(chǎng)景
- 類(lèi)型參數(shù)< T >聲明一個(gè)泛型類(lèi)或泛型方法
- 無(wú)限定通配符< ? >使用泛型類(lèi)或泛型方法
八. 總結(jié)
泛型在java中可以說(shuō)很常用,我們前面提到的集合類(lèi),如ArrayList,HashSet,以及Map都使用到了泛型,泛型也是也是我們?cè)龠M(jìn)行一些組件封裝經(jīng)常用到的,本文主要介紹了泛型基本概念,使用泛型的好處,泛型類(lèi)、接口、方法、通配符的簡(jiǎn)單介紹以及使用方法,最后泛型一般和反射集合使用,通過(guò)泛型可以進(jìn)行類(lèi)型的靈活傳遞,通過(guò)反射可獲取到實(shí)體以及類(lèi)的數(shù)據(jù)信息,從而實(shí)現(xiàn)一些框架、組件的封裝,若有不對(duì)之處,請(qǐng)批評(píng)指正,望共同進(jìn)步,謝謝!
好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
MyBatis-Plus自動(dòng)化填充的踩坑記錄及解決
這篇文章主要介紹了MyBatis-Plus自動(dòng)化填充的踩坑記錄及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03Javassist之一秒理解java動(dòng)態(tài)編程
概述Javassist是一款字節(jié)碼編輯工具,可以直接編輯和生成Java生成的字節(jié)碼,以達(dá)到對(duì).class文件進(jìn)行動(dòng)態(tài)修改的效果。2019-06-06多線程計(jì)數(shù),怎么保持計(jì)數(shù)準(zhǔn)確的方法
這篇文章主要介紹了多線程計(jì)數(shù)的方法,有需要的朋友可以參考一下2014-01-01Java獲取此次請(qǐng)求URL以及服務(wù)器根路徑的方法
這篇文章主要介紹了Java獲取此次請(qǐng)求URL以及服務(wù)器根路徑的方法,需要的朋友可以參考下2015-08-08Java System類(lèi)用法實(shí)戰(zhàn)案例
這篇文章主要介紹了Java System類(lèi)用法,結(jié)合具體實(shí)例形式分析了java使用System類(lèi)獲取系統(tǒng)環(huán)境變量信息相關(guān)操作技巧,需要的朋友可以參考下2019-07-07Springboot JPA 枚舉Enum類(lèi)型存入到數(shù)據(jù)庫(kù)的操作
這篇文章主要介紹了Springboot JPA 枚舉Enum類(lèi)型存入到數(shù)據(jù)庫(kù)的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-01-01Spring @Cacheable自定義緩存過(guò)期時(shí)間的實(shí)現(xiàn)示例
本文主要介紹了Spring @Cacheable自定義緩存過(guò)期時(shí)間的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-05-05