一、泛型概述
--->JDK1.5新特性
1描验、泛型的出現(xiàn):
1、泛型是在JDK1.5以后出現(xiàn)的新特性。泛型是用于解決安全問題的,是一個安全機(jī)制饭豹。
2踱稍、JDK1.5的集合類希望在定義集合時配乱,明確表明你要向集合中裝入那種類型的數(shù)據(jù)阳啥,無法加入指定類型以外的數(shù)據(jù)铐姚。
3证九、泛型是提供給javac編譯器使用的可以限定集合中的輸入類型說明的集合時彭谁,會去掉“類型”信息吸奴,使程序運(yùn)行效率不受影響,對參數(shù)化的泛型類型马靠,getClass()方法的返回值和原始類型完全一樣奄抽。
4、由于編譯生成的字節(jié)碼會去掉泛型的類型信息甩鳄,只要能跳過編譯器逞度,就可以往某個泛型集合中加入其它類型的數(shù)據(jù),如用反射得到集合妙啃,再調(diào)用add方法即可档泽。
2、好處:
1揖赴、使用泛型集合馆匿,可將一個集合中的元素限定為一個特定類型,集合中只能存儲同一個類型的對象燥滑;這樣就將運(yùn)行時期出現(xiàn)的問題ClassCastException轉(zhuǎn)移到了編譯時期渐北,方便與程序員解決問題,讓運(yùn)行時期問題減少铭拧,提高安全性赃蛛。
2、當(dāng)從集合中獲取一個對象時搀菩,編譯器也可知道這個對象的類型呕臂,不需要對對象進(jìn)行強(qiáng)制轉(zhuǎn)化,避免了強(qiáng)制轉(zhuǎn)換的麻煩肪跋,這樣更方便歧蒋。
3、泛型格式:
通過<>來定義要操作的引用數(shù)據(jù)類型
如:
TreeSet<String> //來定義要存入集合中的元素指定為String類型
4州既、泛型定義中的術(shù)語:
如:ArrayList<E>類和ArrayList<Integer>
1谜洽、ArrayList<E>整個稱為泛型類型
2、ArrayList<E>中的E稱為類型變量或類型參數(shù)
3易桃、整個ArrayList<Integer>稱為參數(shù)化類型
4褥琐、ArrayList<Integer>中的Integer稱為類型參數(shù)的實(shí)例或?qū)嶋H類型參數(shù)
5、ArrayList<Integer>中的<>稱為typeof
6晤郑、ArrayList稱為原始類型
參數(shù)化:parametered,已經(jīng)將參數(shù)變?yōu)閷?shí)際類型的狀態(tài)。
5造寝、在使用java提供的對象時磕洪,何時寫泛型?
通常在集合框架中很常見诫龙,只要見到<>就要定義泛型析显,其實(shí)<>就是用來接收類型的,當(dāng)使用集合時签赃,將集合中要存儲的數(shù)據(jù)類型作為參數(shù)傳遞到<>中即可谷异。
6、關(guān)于參數(shù)化類型的幾點(diǎn)說明:
1锦聊、參數(shù)化類型與原始類型的兼容性
第一歹嘹、參數(shù)化類型可引用一個原始類型的對象,編譯只是報(bào)警告孔庭,能不能通過編譯尺上,是編譯器說了算。
如:
Collection<String> coll = new Date();
第二圆到、原始類型可引用一個參數(shù)化類型的對象怎抛,編譯報(bào)告警告
如:
Collection coll = new Vector<String>();
原來的方法接受一個集合參數(shù),新類型也要能傳進(jìn)去芽淡。
2马绝、參數(shù)的類型不考慮類型參數(shù)的繼承關(guān)系:
Vector<String> v = new Vector<Objec>();//錯誤的
不寫Object沒錯,寫了就是明知故犯
Vector<Objec> v = new Vector<String>();//錯誤的
3挣菲、在創(chuàng)建數(shù)組實(shí)例時富稻,數(shù)組的元素不能使用參數(shù)化的類型
如:
Vector<Integer> v[] = newVector<Integer>[10];//錯誤的
示例:
import java.lang.reflect.Constructor;
import java.util.*;
public class Generic {
public static void main(String[] args) throws Exception {
ArrayList<String> al = new ArrayList<String>();
al.add("25");
al.add("b");
System.out.println(al.get(1));
ArrayList<Integer> at = new ArrayList<Integer>();
at.add(23);
at.add(3);
System.out.println(at.get(1));
//編譯器生成的字節(jié)碼會去掉泛型的類型信息
System.out.println((al.getClass() == at.getClass()) +
"-->" + at.getClass().getName());
//at.add("ab")-->報(bào)錯,存儲的應(yīng)為Integer類型
//反射方式己单,由于編譯器生成的字節(jié)碼會去掉泛型的類型信息唉窃,
//所以用反射可跳過編譯器,存入任何類型
at.getClass().getMethod("add",Object.class).invoke(at,"abcd");
at.getClass().getMethod("add",Object.class).invoke(at,5);
System.out.println("反射方式:" + at.get(3));
System.out.println("反射方式:" + at.get(4));
//反射方式獲得new String(new StringBuffer("abc"));
Constructor<String> cons = String.class.getConstructor(StringBuffer.class);
String st = cons.newInstance(new StringBuffer("abc"));
System.out.println(st);
二纹笼、泛型的通配符
1纹份、泛型中的通配符?
當(dāng)傳入的類型不確定時,可以使用通配符?
1廷痘、使用?通配符可引用其他各種類型化的類型蔓涧,通配符的變量主要用作引用,也可調(diào)用與參數(shù)化無關(guān)的方法笋额,但不能調(diào)用與參數(shù)化有關(guān)的方法元暴。
2、可對通配符變量賦任意值:
Collection<?> coll ---> coll = newHashSet<Date>();
如:
public static void printObj(Collection<?> coll){
//coll.add(1);是錯誤的兄猩,如果傳入的是String類型茉盏,就不符合了
for(Object obj : coll){
System.out.println(obj);
}
}
示例:
import java.util.*;
class GenerticDemo
{
public static void main(String[] args)
{
ArrayList<String> p = new ArrayList<String>();
p.add("per20");
p.add("per11");
p.add("per52");
print(p);
ArrayList<Integer> s = new ArrayList<Integer>();
s.add(new Integer(4));
s.add(new Integer(7));
s.add(new Integer(1));
print(s);
}
public static void print(ArrayList<?> al) {
Iterator<?> it = al.listIterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
}
2鉴未、通配符的擴(kuò)展-->泛型的限定:
對于一個范圍內(nèi)的一類事物,可以通過泛型限定的方式定義鸠姨,
有兩種方式:
1铜秆、? extends E:可接收E類型或E類型的子類型;稱之為上限讶迁。
如:
Vector<? extends Number> x = newvector<Integer>();
2连茧、? super E:可接收E類型或E類型的父類型;稱之為下限巍糯。
如:
Vector<? super Integer>x = newvector<Number>();
示例如下:
/*
泛型的限定:
*/
import java.util.*;
class GenerticXian2
{
public static void main(String[] args)
{
TreeSet<Student> s = new TreeSet<Student>(new Comp());
s.add(new Student("stu0"));
s.add(new Student("stu3"));
s.add(new Student("stu1"));
print(s);
System.out.println("Hello World!");
TreeSet<Worker> w = new TreeSet<Worker>(new Comp());
w.add(new Worker("Worker0"));
w.add(new Worker("Worker3"));
w.add(new Worker("Worker1"));
print(w);
}
public static void print(TreeSet<? extends Person> ts) {
Iterator<? extends Person> it = ts.iterator();
while (it.hasNext()){
Person p = it.next();
System.out.println(p.getName());
}
}
}
class Person implements Comparable<Person> {
private String name;
Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public int compareTo(Person p){
return this.getName().compareTo(p.getName());
}
}
class Comp implements Comparator<Person> {
public int compare(Person p1,Person p2){
return p1.getName().compareTo(p2.getName());
}
}
class Student extends Person {
Student(String name){
super(name);
}
}
class Worker extends Person {
Worker(String name){
super(name);
}
}
三啸驯、泛型方法
1、java中泛型方法的定義:
private static <T> T add(T a, T b){
......
return null;
}
········
add(3,5);//自動裝箱和拆箱
Number x1 = add(3.5,5);//取兩個數(shù)的交集類型Number
Object x2 = add(3,"abc");//去最大交集為Object
········
1祟峦、何時定義泛型方法:為了讓不同方法可以操作不同的類型罚斗,而且類型不確定,那么就可以定義泛型方法
2搀愧、特殊之處:靜態(tài)方法不可以訪問類上定義的泛型惰聂,如果靜態(tài)方法操作的引用數(shù)據(jù)類型不確定,可以將泛型定義在方法上咱筛。
2搓幌、泛型方法的特點(diǎn):
1、位置:用于放置泛型的類型參數(shù)的<>應(yīng)出現(xiàn)在方法的其他所有修飾符之后和在方法的返回類型之前迅箩,也就是緊鄰返回值之前溉愁,按照慣例,類型參數(shù)通常用單個大寫字母表示饲趋。
2拐揭、只有引用類型才能作為泛型方法的實(shí)際參數(shù)
3、除了在應(yīng)用泛型時可以使用extends限定符奕塑,在定義泛型時也可以使用extends限定符堂污。
4、普通方法龄砰、構(gòu)造函數(shù)和靜態(tài)方法中都可以使用泛型盟猖。
5、可以用類型變量表示異常换棚,稱之為參數(shù)化的異常式镐,可用于方法的throws列表中,但是不能用于catch子句中固蚤。
6娘汞、在泛型中可同時有多個類型參數(shù),在定義它們的<>中用逗號分開夕玩。
public static <K,V> V getValue(K key){
Map<K, V> map = new HashMap<K, V>();
return map.get(key);
}
private static <T extends Exception> void sayHello() throws T{
try{}
catch(Exception e){
throw (T)e;
}
}
3你弦、這個T和?有什么區(qū)別呢惊豺?
1、T限定了類型鳖目,傳入什么類型即為什么類型扮叨,可以定義變量缤弦,接收賦值的內(nèi)容领迈。
2、?為通配符碍沐,也可以接收任意類型但是不可以定義變量狸捅。
但是這樣定義,雖然提高了擴(kuò)展性累提,可還是有一個局限性尘喝,就是不能使用其他類對象的特有方法。
3斋陪、總結(jié):
通配符方案要比泛型方法更有效朽褪,當(dāng)一個類型變量用來表達(dá)兩個參數(shù)之間或參數(shù)和返回值之間的關(guān)系時,即同一個類型變量在方法簽名的兩處被使用无虚,或者類型變量在方法體代碼中也被使用缔赠,而不是僅在簽名的時候使用,才需要使用泛型方法友题。
四嗤堰、泛型類
1、概述:
1度宦、若類實(shí)例對象中多出要使用到同一泛型參數(shù)踢匣,即這些地方引用類型要保持同一個實(shí)際類型時,這時候就要采用泛型類型的方式進(jìn)行定義戈抄,也就是類級別的泛型离唬。
2、何時定義泛型類:當(dāng)類中要操作的引用數(shù)據(jù)類型不確定時划鸽,在早期定義Object來完成擴(kuò)展输莺,而現(xiàn)在定義泛型。
3漾稀、泛型類定義的泛型模闲,在整個類中都有效,如果被方法調(diào)用崭捍,那么泛型類的對象要明確需要操作的具體類型后尸折,所有要操作的類就已經(jīng)固定了。
4殷蛇、類級別的泛型是根據(jù)引用該類名時指定的類型信息來參數(shù)化類型變量的实夹。
2橄浓、語法格式:
1、定義
public class GenerDao1<T>{
private T field;
public void save(T obj){}
public T getByteId(int Id){}
}
2亮航、舉例:
擴(kuò)展:Dao:Data Access Object荸实,數(shù)據(jù)訪問對象。
對其操作:crud即增上刪改查
c:creat缴淋,創(chuàng)建准给、增加;
r:read重抖,讀取露氮、查詢;
u:update钟沛,更新畔规、修改
d:delete,刪除恨统。
對javaEE的理解:13種技術(shù)叁扫。簡單說就是對數(shù)據(jù)庫的增刪改查。
寫Dao類有五個基本方法:增刪改查畜埋,其中查包含查單個和對同類型集合的查詢莫绣,如同性別或同地區(qū)的集合獲取。
package cn.itcast.text2;
import java.util.*;
public class GenerticDao<T> {
public static <E> void staMethod(E e){}
public void add(T obj){}
public boolean delete(T obj){
return true;
}
public boolean delete(int id){
return true;
}
public T update(T obj){
return null;
}
public T findByUserName(String name){
return null;
}
public Set<T> findByPlace(String place){
Set<T> set = new TreeSet<T>();
//....
return set;
}
}
3由捎、注意:
1兔综、在對泛型進(jìn)行參數(shù)化時,類型參數(shù)的實(shí)例必須是引用類型狞玛,不能是基本類型软驰。
2、當(dāng)一個變量被聲明為參數(shù)時心肪,只能被實(shí)例變量和方法調(diào)用(還有內(nèi)嵌類型)锭亏,而不能被靜態(tài)變量和靜態(tài)方法調(diào)用,因?yàn)殪o態(tài)成員是被所有參數(shù)化的類共享的硬鞍,所以靜態(tài)成員不應(yīng)該有類級別的類型參數(shù)慧瘤。
總結(jié):
對泛型的定義:
第一、定義泛型:當(dāng)又不確定的類型需要傳入到集合中固该,需要定義泛型
第二锅减、定義泛型類:如果類型確定后,所操作的方法都是屬于此類型伐坏,則定義泛型類
第三怔匣、定義泛型方法:如果定義的方法確定了,里面所操作的類型不確定桦沉,則定義泛型方法
示例:
//測試
class GenerticTest {
public static void main(String[] args) {
//創(chuàng)建泛型類對象
GenClass<Worker> g = new GenClass<Worker> ();
g.setTT(new Worker());
Worker w = g.getTT();
g.showC(w);
System.out.println("----------------------");
//泛型方法測試
GenMethod<String> g1 = new GenMethod<String>();
GenMethod.showS("SSS");
g1.show("sesf");
g1.print("heheh");
g1.printY(new Integer(5));
System.out.println("------------------------");
//泛型接口測試
GenInter g2 = new GenInter();
g2.show("haha");
System.out.println("Hello World!");
GenImpl<Integer> g3 = new GenImpl<Integer>();
g3.show(new Integer(95));
}
}
//泛型類
class GenClass<TT> {
//定義私有屬性
private TT t;
//定義公共設(shè)置方法每瞒,設(shè)置屬性
public void setTT(TT t) {
this.t = t;
}
//定義公共訪問方法金闽,訪問屬性
public TT getTT() {
return t;
}
//定義方法
public void showC(TT t) {
System.out.println("GenClass show:" + t);
}
}
//創(chuàng)建Worker類,作為類型傳入泛型類中
class Worker {}
//泛型方法
class GenMethod<T> {
//靜態(tài)的泛型方法
public static <S> void showS(S s) {
System.out.println("static show:" + s);
}
//非靜態(tài)泛型方法
public void show(T t) {
System.out.println("未指定T show:" + t);
}
public void print(T t) {
System.out.println("指定T print:" + t);
}
//指定接受其他類型的泛型方法
public <Y> void printY(Y y) {
System.out.println("和類指定的不同剿骨,為Y print:" + y);
}
}
//泛型接口
interface Inter<T> {
void show(T t);
}
//一般類實(shí)現(xiàn)泛型接口
class GenInter implements Inter<String> {
public void show(String s) {
System.out.println("接口 show:" + s);
}
}
//泛型類實(shí)現(xiàn)泛型接口
class GenImpl<T> implements Inter<T> {
public void show(T t) {
System.out.println("類接收類型不確定的實(shí)現(xiàn)接口 show:" + t);
}
五代芜、參數(shù)的類型推斷
1、概述:
1浓利、定義:編譯器判斷泛型方法的實(shí)際參數(shù)的過程挤庇,稱之為類型推斷。
2荞膘、類型推斷是相對于直覺推斷的罚随,其實(shí)現(xiàn)方法是一種非常復(fù)雜的過程。
2羽资、類型推斷的具體規(guī)則:
根據(jù)調(diào)用泛型方法時,實(shí)際傳遞的參數(shù)類型或返回值的類型來推斷遵班。
1屠升、當(dāng)某個類型變量只在整個參數(shù)列表中的所有參數(shù)和返回值中的一處被應(yīng)用了,那么根據(jù)調(diào)用方法時狭郑,該處的實(shí)際應(yīng)用類型來確定腹暖,這很容易憑著感覺推斷出來,即直接根據(jù)調(diào)用方法時翰萨,傳遞的參數(shù)類型或返回值來決定泛型參數(shù)的類型脏答,如:
swap(newString[3],1,2)
--->
static <E> void swap(E[] a, inti, int j);
2亩鬼、當(dāng)某個類型變量在某個參數(shù)列表中的所有參數(shù)和返回值中的多處被應(yīng)用了殖告,如果調(diào)用方法時,這多處的實(shí)際應(yīng)用類型都對應(yīng)同一種類型來表示雳锋,這很容易憑感覺推斷出來:
add(3,5)
--->
static<T> T add(T a,T b);
3黄绩、若對應(yīng)了不同類型,且沒有使用返回值玷过,這是取多個參數(shù)中的最大交集類型爽丹,如下面的對應(yīng)類型Number,編譯沒問題辛蚊,但是運(yùn)行會出錯:
fill(new Integer[3],3.5f)
--->
static<T> void fill(T[] a,T v);
4粤蝎、若對應(yīng)了不同類型,且使用了返回值袋马,這時候優(yōu)先考慮返回值類型初澎,如下面語句實(shí)際對應(yīng)的類型就是Integer了,編譯將報(bào)錯飞蛹,將變量x類型改為float谤狡,對此eclipse報(bào)錯提示灸眼,接著再將變量x類型改為Number,則就沒了錯誤:
int x = add(3,3.5f)
--->
static<T> T add(T a,T b);
5墓懂、參數(shù)類型的類型推斷具有傳遞性焰宣,下面第一種情況推斷實(shí)際參數(shù)類型為Object,編譯沒問題捕仔,而第二種情況則會根據(jù)參數(shù)化的Vector類實(shí)例將類型變量直接確定為String類型匕积,編譯將出現(xiàn)問題:
copy(newInteger[5],new String[5]);
--->
static<T> T copy(T[] a,T[] b);
六、擴(kuò)展
--> 反射獲得泛型的實(shí)際類型參數(shù)
舉例說明:
package cn.itcast.text2;
import java.lang.reflect.*;
import java.sql.Date;
import java.util.*;
import cn.itcast.text1.ReflectPoint;
public class GenerticTest {
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
Object obj = "abc";
String str = autoContor(obj);
GenerticDao<ReflectPoint> gd = new GenerticDao<ReflectPoint>();
gd.add(new ReflectPoint(3,5));
//通過獲得方法本身的方法
Method applyMethod = GenerticTest.class.getMethod("applyVector", Vector.class);
//通過方法的獲取泛型參數(shù)的方法得到原始參數(shù)類型的集合
Type[] types = applyMethod.getGenericParameterTypes();
//將參數(shù)類型轉(zhuǎn)換為參數(shù)化類型
ParameterizedType pType = (ParameterizedType)types[0];
//得到原始類型
System.out.println(pType.getRawType());
//得到實(shí)際參數(shù)類型
System.out.println(pType.getActualTypeArguments()[0]);
}