java泛型是對Java語言的類型系統的一種擴展,泛型的本質就是將所操作的數據類型參數化。下面我會由淺入深地介紹Java的泛型。
一:泛型出現的背景
在java代碼里,你會經常發現類似下邊的代碼:
public class Test { public static void main(String[] args) { List list = new ArrayList(); list.add("hah"); //list.add(new Test()); // list.add(1); for (Object object : list) { String s1 = (String)object; //.....如果是你你該如何拿出list的值,如果list中放着上邊的不同類型的東西。無解 } } }
編碼的時候,不加泛型是可以的,但是 你從容器中拿出來的時候必須強制類型轉換,第一是多敲很多代碼,第二極容易發生類型轉換錯誤,這個運行時異常 比如你把上邊
注釋的代碼放開,程序在獲取容器的地方就會報運行時異常 ClassCasrException
Java語言的設計者引入了泛型,暫時先不追究它內在是怎么實現的。只需要知道,如果我們像下邊這么寫,我們就不需要強制類型轉換。我們也不需要擔心運行是異常了。
List<String> newList = new ArrayList<String>(); newList.add("hhe"); newList.add("123"); String s1 = newList.get(0);//不需要強制類型轉換,因為我加了泛型,我就認為它里邊一定都是String
二: 泛型的語法使用
1:使用具體的泛型類型: 尖括號內帶有具體的類型。可以限定這個Map的key和value只能是字符串
Map<String, String> map = new HashMap<String, String>(); map.put("key","value"); String value = map.get("key")
從面向對象的角度看,使用對象的時候,泛型內傳入的具體的類型。聲明的時候采用尖括號內加占位符的形式,比如這是HashMap的源碼
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable{ ... public HashMap(Map<? extends K, ? extends V> m) { this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR); putAllForCreate(m); } ... public V remove(Object key) { Entry<K,V> e = removeEntryForKey(key); return (e == null ? null : e.value); } }
2:方法聲明的時候 : public <T> T getValue(){...}
在上邊的代碼中,我們可以看到在類上如何定義泛型,也看到了類上定義的占位符在類的普通方法上可以直接使用。但是如果想在靜態方法上定義泛型,這需要單獨的處理 。下面我們單獨對方法上如何定義
和使用泛型進行介紹(注意:方法上是否定義泛型和類上是否定義沒有必然的聯系)
比如Web項目中,泛型是修飾類型的,在方法上,一般就是返回值和參數列表
- 返回值類型:可以定義為List<String>等形式,但是實際開發中,一般都是不知道具體類型,定義形式如下 <T> List<T> test(T t){...} ,前邊的<T>可以理解為泛型的聲明,你只有聲明了T,你才可以在 方法中用到T,這一具體的類型, List<T>是具體的返回值類型。
- 方法傳參: 可以用占位符限定的容器 比如 List<T>,或者直接是占位符 T
public class BaseServiceImpl implements BaseService { protected <T> List<T> calcPage(String hql, PageContext pageContext, Object... params) { int total = getDataTotalNum(hql, params); pageContext.setTotal(total); List<T> list = (List<T>) getPageDataByHQL(hql, pageContext.getRows(), pageContext.getPage(), pageContext.getTotal(), params); return list; } @Override @Sync public void deleteBatchVO(final List<?> dataList) throws ServiceException { baseDAO.deleteBatchVO(dataList); } @Override public List<?> getPageDataByHQL(final String hql, final Map<String, Object> filter) throws ServiceException { return baseDAO.getPageDataByHQL(hql, filter); } }
簡單的例子:
public <T> T TestG(T t){ return t; }
方法定義的時候,泛型是這樣設計,在使用的時候,代碼如下:
List<TaclUserinfo> list = calcPage(hqluser1.toString(), pageContext, taclRole.getRoleid(), taclRole.getTenantId()); //返回值類型 是<T>List<T>的,j接收的時候,我直接用List<具體類>
3 :類或者接口使用泛型 interface Collection<V> {..}
上邊的HashMap代碼中,也看到了在類上使用泛型的具體例子。在真正的項目上,一些基礎的公共類經常定義泛型,如下:
1 public interface GenericDao<T, ID extends Serializable> { 2 3 public abstract void saveOrUpdate(T t) throws DataAccessException; 4 5 public abstract T get(ID id) throws DataAccessException; 6 7 public abstract List<T> query(String queryString) throws DataAccessException; 8 9 public abstract Serializable save(T t) throws DataAccessException; 10 11 public abstract void saveOrUpdateAll(Collection<T> entities) throws DataAccessException; 12 13 public abstract List<T> loadAll() throws DataAccessException; 14 15 public abstract void merge(T t) throws DataAccessException; 16 17 }
接口的實現類: 傳入參數為T,實現類中也可以繼續用T,返回為T也可以用T;實現的時候 可以用 extends限定泛型的邊界。
public abstract class GenericDaoImpl<T extends BaseEntity, ID extends Serializable> extends HibernateDaoSupport implements GenericDao<T, ID> { public void merge(T t) throws DataAccessException { TenantInterceptor.setTenantInfoToEntity(t); getHibernateTemplate().merge(t); } public T get(ID id) throws DataAccessException { // getHibernateTemplate().setCacheQueries(true); T load = (T) getHibernateTemplate().get(getEntityClass(), id); return load; } }
具體使用的時候:
public class UserDao extends GenericDaoImpl<User, Serializable> { ...//比如get() merge()這些方法不需要在單獨編寫,直接調用 }
4: 聲明帶邊界的泛型 class userDao<T extends BaseEntity>
Java中泛型在運行期是不可見的,會被擦除為它的上級類型。如果你是無界的泛型參數類型,就會被替換為Object.
public class RedColored<T extends Color> { public T t; public void color(){ t.getColor();//T的邊界是Color,所以可以調用getColor(),否則會編譯報錯 } } abstract class Color{ abstract void getColor(); }
類似這樣的定義形式:GenericDaoImpl<T extends BaseEntity, ID extends Serializable> ,java重載了extends,標注T的邊界就是BaseEntity。如果需求是繼承基類,那么邊界定義在子類上
類似
class Colored2<T extends Color> extends RedColor<T>
5:用於通配符 <?>
參考於( Java 通配符解惑 )泛型類型的子類型的不相關性。比如 現在List<Cat>並不是List<Anilmal>是兩種不同的類型
;且無繼承
關系 。那么,我們像想要傳入的參數既可能是List<Cat>
也有可能是List<Annimal>
public class AnimalTrainer { public void act(List<? extends Animal> list) { //備注:如果用 List<Animal> 作為形參列表,是無法傳入List<Cat>for (Animal animal : list) { animal.eat(); } } }
act(List<? extends Animal> list),當中“?”就是通配符,而“? extends Animal”則表示通配符“?”的上界為Animal,換句話說就是,“? extends Animal”可以代表Animal或其子類,可代表不了Animal的父類(如Object),因為通配符的上界是Animal。
所以,泛型內是不存在父子關系,但是利用通配符可以產生類似的效果:
假設給定的泛型類型為G,(如List<E>中的List),兩個具體的泛型參數X、Y,當中Y是X的子類(如上的Animal和Cat))
- G<? extends Y> 是 G<? extends X>的子類型(如List<? extends Cat> 是 List<? extends Animal>的子類型)。
- G<X> 是 G<? extends X>的子類型(如List<Animal> 是 List<? extends Animal>的子類型)
- G<?> 與 G<? extends Object>等同,如List<?> 與List<? extends Objext>等同
三: 泛型可以用到那些地方
泛型可以用到容器,方法,接口,內部類,抽象類
四: Java中泛型獨特之處
泛型是Java1.5之后才引入的,為了兼容。Java采用了C++完全不同的實現思想。Java中的泛型更多的看起來像是編譯期用的,比如我定義一個使用泛型的demo
我在查看它的class文件時,發現class文件並沒有任何泛型信息。
Java會在編輯期把泛型擦除掉
在JAVA的虛擬機中並不存在泛型,泛型只是為了完善java體系,增加程序員編程的便捷性以及安全性而創建的一種機制,在JAVA虛擬機中對應泛型的都是確定的類型,在編寫泛型代碼后,java虛擬中會把這些泛型參數類型都擦除,用相應的確定類型來代替,代替的這一動作叫做類型擦除,而用於替代的類型稱為原始類型,在類型擦除過程中,一般使用第一個限定的類型來替換,若無限定,則使用Object.
擦除的原理以及邊界
關鍵在於從泛型類型中清除類型參數的相關信息,並且再必要的時候添加類型檢查和類型轉換的方法。
可以參考Java泛型-類型擦除。 運行期編譯期會去掉泛型信息,轉換為左邊界,在調用的地方添加類型轉換。
泛型擦除肯可能導致的問題
用泛型不可以區分方法簽名
public void test(List<String> ls){ System.out.println("Sting"); } public void test(List<Integer> li){ System.out.println("Integer"); } //這回報錯,編譯期無法區分這兩個方法
泛型類的靜態變量是共享
public class StaticTest{ public static void main(String[] args){ GT<Integer> gti = new GT<Integer>(); gti.var=1; GT<String> gts = new GT<String>(); gts.var=2; System.out.println(gti.var); } } class GT<T>{ public static int var=0; public void nothing(T x){} }
五: 泛型中特殊使用
java中的泛型不只是上述說的內容,還有一些特殊的地方,如果這些地方也用泛型該怎么設計。比如說“動態類型”,“潛在類型”,“異常”
程序如果運行時需要類型信息
就在調用的地方傳入類型信息
異常中使用泛型
不能拋出也不能捕獲泛型類的對象。事實上,泛型類擴展Throwable都不合法,因為泛型信息會被擦除,相當於catch兩個相同的異常,是不可以的
數組與泛型
不能聲明參數化類型的數組, 數組可以記住自己的元素類型,不能建立一個泛型數組。(當然 你如果用反射還是可以創建的,用Array.newInstance。這里說不能建是不能用普通方法)
泛型的一些其他細節:
1.基本類型無法作為類型參數即ArrayList<int>這樣的代碼是不允許的,如果為我們想要使用必須使用基本類型對應的包裝器類型ArrayList<Integer>
2.在泛型代碼內部,無法獲得任何有關泛型參數類型的信息換句話說,如果傳入的類型參數為T,即你在泛型代碼內部你不知道T有什么方法,屬性,關於T的一切信息都丟失了(類型信息,博文后續)。
3.注,在能夠使用泛型方法的時候,盡量避免使整個類泛化。
六:簡單概括
虛擬機中沒有泛型,只有普通類和普通方法
所有泛型類的類型參數在編譯時都會被擦除
創建泛型對象時請指明類型,讓編譯器盡早的做參數檢查
要忽略編譯器的警告信息,那意味着潛在的ClassCastException等着你。