泛型
1.泛型是在JDK1.5以後出現的新特性。泛型是用於解決安全問題的,是一個安全機制。
2.JDK1.5的集合類希望在定義集合時,明確表明你要向集合中裝入那種類型的數據,無法加入指定類型以外的數據。
3.泛型是提供給javac編譯器使用的可以限定集合中的輸入類型說明的集合時,會去掉“類型”信息,使程序運行效率不受影響,對參數化的泛型類型,getClass()方法的返回值和原始類型完全一樣。
4.由於編譯生成的字節碼會去掉泛型的類型信息,只要能跳過編譯器,就可以往某個泛型集合中加入其它類型的數據,如用反射得到集合,再調用add方法即可。
泛型的好處
1.使用泛型集合,可將一個集合中的元素限定爲一個特定類型,集合中只能存儲同一個類型的對象;這樣就將運行時期出現的問題ClassCastException轉移到了編譯時期,方便與程序員解決問題,讓運行時期問題減少,提高安全性。
2.當從集合中獲取一個對象時,編譯器也可知道這個對象的類型,不需要對對象進行強制轉化,避免了強制轉換的麻煩,這樣更方便。
格式:
通過<>來定義要操作的引用數據類型
如:ArrayList<String> 來定義要存入集合中的元素指定爲String類型
有關泛型的術語
1.ArrayList<E>整個稱爲泛型類型
2.ArrayList<E>中的E稱爲類型變量或類型參數
3.整個ArrayList<Integer>稱爲參數化類型
4.ArrayList<Integer>中的Integer稱爲類型參數的實例或實際類型參數
5.ArrayList<Integer>中的<>稱爲typeof
6.ArrayList稱爲原始類型
參數化:parametered,已經將參數變爲實際類型的狀態。
在使用java提供的對象時,何時寫泛型?
通常在集合框架中很常見,只要見到<>就要定義泛型,其實<>就是用來接收類型的,當使用集合時,將集合中要存儲的數據類型作爲參數傳遞到<>中即可。
關於參數化類型的幾點說明:
1.參數化類型與原始類型的兼容性
(1)參數化類型可引用一個原始類型的對象,編譯只是報警告,能不能通過編譯,是編譯器說了算。
如:Collection<String> coll = new Date();
(2)原始類型可引用一個參數化類型的對象,編譯報告警告
如:Collection coll = new Vector<String>();
原來的方法接受一個集合參數,新類型也要能傳進去。
2.參數的類型不考慮類型參數的繼承關係:
Vector<String> v = new Vector<Objec>();//錯誤的
不寫Object沒錯,寫了就是明知故犯
Vector<Objec> v = new Vector<String>();//錯誤的
3.在創建數組實例時,數組的元素不能使用參數化的類型
如:Vector<Integer> v[] = newVector<Integer>[10];//錯誤的
代碼演示:
泛型中的通配符
當傳入的類型不確定時,可以使用通配符?
1.使用?通配符可引用其他各種類型化的類型,通配符的變量主要用作引用,也可調用與參數化無關的方法,但不能調用與參數化有關的方法。
2.可對通配符變量賦任意值:
如:Collection<?> coll ---> coll = newHashSet<Date>();
如:
public static void printObj(Collection<?> coll){
//coll.add(1);是錯誤的,如果傳入的是String類型,就不符合了
for(Object obj : coll){
System.out.println(obj);
}
}
代碼演示:
通配符的擴展------泛型的限定
對於一個範圍內的一類事物,可以通過泛型限定的方式定義,有兩種方式:
1.? extends E:可接收E類型或E類型的子類型;稱之爲上限。
如:Vector<? extends Number> x = newvector<Integer>();
2.? super E:可接收E類型或E類型的父類型;稱之爲下限。
如:Vector<? super Integer>x = newvector<Number>();
代碼示例:
泛型方法
1.java中泛型方法的定義:
private static <T> T add(T a, T b){
......
return null;
}
add(3,5);//自動裝箱和拆箱
Number x1 = add(3.5,5);//取兩個數的交集類型Number
Object x2 = add(3,"abc");//去最大交集爲Object
2.何時定義泛型方法:爲了讓不同方法可以操作不同的類型,而且類型不確定,那麼就可以定義泛型方法
3.特殊之處:靜態方法不可以訪問類上定義的泛型,如果靜態方法操作的引用數據類型不確定,可以將泛型定義在方法上。
泛型方法的特點:
1.位置:用於放置泛型的類型參數的<>應出現在方法的其他所有修飾符之後和在方法的返回類型之前,也就是緊鄰返回值之前,按照慣例,類型參數通常用單個大寫字母表示。
2.只有引用類型才能作爲泛型方法的實際參數
3.除了在應用泛型時可以使用extends限定符,在定義泛型時也可以使用extends限定符。
4.普通方法、構造函數和靜態方法中都可以使用泛型。
5.可以用類型變量表示異常,稱之爲參數化的異常,可用於方法的throws列表中,但是不能用於catch子句中。
6.在泛型中可同時有多個類型參數,在定義它們的<>中用逗號分開。
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;
}
}
這個T和?有什麼區別呢?
1.T限定了類型,傳入什麼類型即爲什麼類型,可以定義變量,接收賦值的內容。
2.?爲通配符,也可以接收任意類型但是不可以定義變量。
但是這樣定義,雖然提高了擴展性,可還是有一個侷限性,就是不能使用其他類對象的特有方法。
泛型類
概述:
1.若類實例對象中多出要使用到同一泛型參數,即這些地方引用類型要保持同一個實際類型時,這時候就要採用泛型類型的方式進行定義,也就是類級別的泛型。
2.何時定義泛型類:當類中要操作的引用數據類型不確定時,在早期定義Object來完成擴展,而現在定義泛型。
3.泛型類定義的泛型,在整個類中都有效,如果被方法調用,那麼泛型類的對象要明確需要操作的具體類型後,所有要操作的類就已經固定了。
4.類級別的泛型是根據引用該類名時指定的類型信息來參數化類型變量的。
語法格式:
1.定義
public class GenerDao1<T>{
private T field;
public void save(T obj){}
public T getByteId(int Id){}
}
2.舉例:
擴展:Dao:Data Access Object,數據訪問對象。
對其操作:crud即增上刪改查
c:creat,創建、增加; r:read,讀取、查詢;
u:update,更新、修改 d:delete,刪除。
對javaEE的理解:13種技術。簡單說就是對數據庫的增刪改查。
寫Dao類有五個基本方法:增刪改查,其中查包含查單個和對同類型集合的查詢,如同性別或同地區的集合獲取。
代碼演示:
1.在對泛型進行參數化時,類型參數的實例必須是引用類型,不能是基本類型。
2.當一個變量被聲明爲參數時,只能被實例變量和方法調用(還有內嵌類型),而不能被靜態變量和靜態方法調用,因爲靜態成員是被所有參數化的類共享的,所以靜態成員不應該有類級別的類型參數。
總結:
對泛型的定義:
1.定義泛型:當又不確定的類型需要傳入到集合中,需要定義泛型
2.定義泛型類:如果類型確定後,所操作的方法都是屬於此類型,則定義泛型類
3.定義泛型方法:如果定義的方法確定了,裏面所操作的類型不確定,則定義泛型方法
代碼演示:
參數的類型推斷
1.定義:編譯器判斷泛型方法的實際參數的過程,稱之爲類型推斷。
2.類型推斷是相對於直覺推斷的,其實現方法是一種非常複雜的過程。
類型推斷的具體規則:
根據調用泛型方法時,實際傳遞的參數類型或返回值的類型來推斷。
1.當某個類型變量只在整個參數列表中的所有參數和返回值中的一處被應用了,那麼根據調用方法時,該處的實際應用類型來確定,這很容易憑着感覺推斷出來,即直接根據調用方法時,傳遞的參數類型或返回值來決定泛型參數的類型,如:
swap(newString[3],1,2)
---> static <E> void swap(E[] a, inti, int j);
2.當某個類型變量在某個參數列表中的所有參數和返回值中的多處被應用了,如果調用方法時,這多處的實際應用類型都對應同一種類型來表示,這很容易憑感覺推斷出來:
add(3,5)
---> static<T> T add(T a,T b);
3.若對應了不同類型,且沒有使用返回值,這是取多個參數中的最大交集類型,如下面的對應類型Number,編譯沒問題,但是運行會出錯:
fill(new Integer[3],3.5f)
---> static<T> void fill(T[] a,T v);
4.若對應了不同類型,且使用了返回值,這時候優先考慮返回值類型,如下面語句實際對應的類型就是Integer了,編譯將報錯,將變量x類型改爲float,對此eclipse報錯提示,接着再將變量x類型改爲Number,則就沒了錯誤:
int x = add(3,3.5f)
---> static<T> T add(T a,T b);
5.參數類型的類型推斷具有傳遞性,下面第一種情況推斷實際參數類型爲Object,編譯沒問題,而第二種情況則會根據參數化的Vector類實例將類型變量直接確定爲String類型,編譯將出現問題:
copy(newInteger[5],new String[5]);
---> static<T> T copy(T[] a,T[] b);
類加載器
1.定義:簡單說,類加載器就是加載類的工具。
當出現一個類,用到此類的時候,Java虛擬機首先將類字節碼加載進內存,通常字節碼的原始信息放在硬盤上的classpath指定的目錄下。
2.類加載器作用:將.class文件中的內容加載進內存進行處理,處理完後的結果就是字節碼。
3.默認類加載器:
(1)Java虛擬機中可安裝多個類加載器,系統默認的有三個主要的,每個類負責加載特定位置的類:BootStrap、ExtClassLoader、AppClassLoader
(2)BootStrap--頂級類加載器:
類加載器本身也是Java類,因爲它是Java類,本身也需要加載器加載,顯然必須有第一個類加載器而不是java類的,這正是BootStrap。它是嵌套在Java虛擬機內核中的,已啓動即出現在虛擬機中,是用c++寫的一段二進制代碼。所以不能通過java程序獲取其名字,獲得的只能是null。
4.Java虛擬機中的所有類加載器採用子父關係的樹形結構進行組織,在實例化每個類加載器對象或默認採用系統類加載器作爲其父級類加載器。
代碼演示
類加載器的委託機制:
1.加載類的方式
當Java虛擬機要加載一個類時,到底要用哪個類加載器加載呢?
(1)首先,當前線程的類加載器去加載線程中的第一個類。
(2)若A引用類B(繼承或者使用了B),Java虛擬機將使用加載類的類加載器來加載類B。
(3)還可直接調用ClassLoader的LoaderClass()方法,來制定某個類加載器去加載某個類。
2.加載器的委託機制:每個類加載器加載類時,又先委託給上級類加載器。
每個ClassLoader本身只能分別加載特定位置和目錄中的類,但他們可以委託其他類的加載器去加載,這就是類加載器的委託模式,類加載器一級級委託到BootStrap類加載器,當BootStrap在指定目錄中沒有找到要加載的類時,無法加載當前所要加載的類,就會一級級返回子孫類加載器,進行真正的加載,每級都會先到自己相應指定的目錄中去找,有沒有當前的類;直到退回到最初的類裝載器的發起者時,如果它自身還未找到,未完成類的加載,那就報告ClassNoFoundException的異常。
簡單說,就是先由發起者將類一級級委託爲BootStrap,從父級開始找,找到了直接返回,沒找到再返回給其子級找,直到發起者,再沒找到就報異常。
3.委託機制的優點:可以集中管理,不會產生多字節碼重複的現象。
面試題
可不可以自己寫個類爲:java.lang.System呢?
回答:第一、通常是不可以的,由於類加載器的委託機制,會先將System這個類一級級委託給最頂級的BootStrap,由於BootStrap在其指定的目錄中加載的是rt.jar中的類,且其中有System這個類,那麼就會直接加載自己目錄中的,也就是Java已經定義好的System這個類,而不會加載自定義的這個System。
第二、但是還是有辦法加載這個自定義的System類的,此時就不能交給上級加載了,需要用自定義的類加載器加載,這就需要有特殊的寫法才能去加載這個自定義的System類的。
自定義類加載器
1.自定義的類加載器必須繼承抽象類ClassLoader,要覆寫其中的findClass(String name)方法,而不用覆寫loadClass()方法。
2.覆寫findClass(String name)方法的原因:
(1)是要保留loadClass()方法中的流程,因爲loadClass()中調用了findClass(String name)這個方法,此方法返回的就是去尋找父級的類加載器。
(2)在loadClass()內部是會先委託給父級,當父級找到後就會調用findClass(String name)方法,而找不到時就會用子級的類加載器,再找不到就報異常了,所以只需要覆寫findClass方法,那麼就具有了實現用自定義的類加載器加載類的目的。
流程:
父級-->loadClass-->findClass-->得到Class文件後轉化成字節碼-->defind()。
3.編程步驟:
(1)編寫一個對文件內容進行簡單加盟的程序
(2)編寫好了一個自己的類加載器,可實現對加密過來的類進行裝載和解密。
(3)編寫一個程序,調用類加載器加載類,在源程序中不能用該類名定義引用變量,因爲編譯器無法識別這個類,程序中除了可使用ClassLoader的load方法外,還能使用放置線程的上線文類加載器加載或系統類加載器,然後在使用forName得到字節碼文件。
代碼演示:
1.泛型是在JDK1.5以後出現的新特性。泛型是用於解決安全問題的,是一個安全機制。
2.JDK1.5的集合類希望在定義集合時,明確表明你要向集合中裝入那種類型的數據,無法加入指定類型以外的數據。
3.泛型是提供給javac編譯器使用的可以限定集合中的輸入類型說明的集合時,會去掉“類型”信息,使程序運行效率不受影響,對參數化的泛型類型,getClass()方法的返回值和原始類型完全一樣。
4.由於編譯生成的字節碼會去掉泛型的類型信息,只要能跳過編譯器,就可以往某個泛型集合中加入其它類型的數據,如用反射得到集合,再調用add方法即可。
泛型的好處
1.使用泛型集合,可將一個集合中的元素限定爲一個特定類型,集合中只能存儲同一個類型的對象;這樣就將運行時期出現的問題ClassCastException轉移到了編譯時期,方便與程序員解決問題,讓運行時期問題減少,提高安全性。
2.當從集合中獲取一個對象時,編譯器也可知道這個對象的類型,不需要對對象進行強制轉化,避免了強制轉換的麻煩,這樣更方便。
格式:
通過<>來定義要操作的引用數據類型
如:ArrayList<String> 來定義要存入集合中的元素指定爲String類型
有關泛型的術語
1.ArrayList<E>整個稱爲泛型類型
2.ArrayList<E>中的E稱爲類型變量或類型參數
3.整個ArrayList<Integer>稱爲參數化類型
4.ArrayList<Integer>中的Integer稱爲類型參數的實例或實際類型參數
5.ArrayList<Integer>中的<>稱爲typeof
6.ArrayList稱爲原始類型
參數化:parametered,已經將參數變爲實際類型的狀態。
在使用java提供的對象時,何時寫泛型?
通常在集合框架中很常見,只要見到<>就要定義泛型,其實<>就是用來接收類型的,當使用集合時,將集合中要存儲的數據類型作爲參數傳遞到<>中即可。
關於參數化類型的幾點說明:
1.參數化類型與原始類型的兼容性
(1)參數化類型可引用一個原始類型的對象,編譯只是報警告,能不能通過編譯,是編譯器說了算。
如:Collection<String> coll = new Date();
(2)原始類型可引用一個參數化類型的對象,編譯報告警告
如:Collection coll = new Vector<String>();
原來的方法接受一個集合參數,新類型也要能傳進去。
2.參數的類型不考慮類型參數的繼承關係:
Vector<String> v = new Vector<Objec>();//錯誤的
不寫Object沒錯,寫了就是明知故犯
Vector<Objec> v = new Vector<String>();//錯誤的
3.在創建數組實例時,數組的元素不能使用參數化的類型
如:Vector<Integer> v[] = newVector<Integer>[10];//錯誤的
代碼演示:
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));
//編譯器生成的字節碼會去掉泛型的類型信息
System.out.println((al.getClass() == at.getClass()) +
"-->" + at.getClass().getName());
//at.add("ab")-->報錯,存儲的應爲Integer類型
//反射方式,由於編譯器生成的字節碼會去掉泛型的類型信息,
//所以用反射可跳過編譯器,存入任何類型
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.使用?通配符可引用其他各種類型化的類型,通配符的變量主要用作引用,也可調用與參數化無關的方法,但不能調用與參數化有關的方法。
2.可對通配符變量賦任意值:
如:Collection<?> coll ---> coll = newHashSet<Date>();
如:
public static void printObj(Collection<?> coll){
//coll.add(1);是錯誤的,如果傳入的是String類型,就不符合了
for(Object obj : coll){
System.out.println(obj);
}
}
代碼演示:
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());
}
}
}
通配符的擴展------泛型的限定
對於一個範圍內的一類事物,可以通過泛型限定的方式定義,有兩種方式:
1.? extends E:可接收E類型或E類型的子類型;稱之爲上限。
如:Vector<? extends Number> x = newvector<Integer>();
2.? super E:可接收E類型或E類型的父類型;稱之爲下限。
如:Vector<? super Integer>x = newvector<Number>();
代碼示例:
/*
泛型的限定:
*/
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);//取兩個數的交集類型Number
Object x2 = add(3,"abc");//去最大交集爲Object
2.何時定義泛型方法:爲了讓不同方法可以操作不同的類型,而且類型不確定,那麼就可以定義泛型方法
3.特殊之處:靜態方法不可以訪問類上定義的泛型,如果靜態方法操作的引用數據類型不確定,可以將泛型定義在方法上。
泛型方法的特點:
1.位置:用於放置泛型的類型參數的<>應出現在方法的其他所有修飾符之後和在方法的返回類型之前,也就是緊鄰返回值之前,按照慣例,類型參數通常用單個大寫字母表示。
2.只有引用類型才能作爲泛型方法的實際參數
3.除了在應用泛型時可以使用extends限定符,在定義泛型時也可以使用extends限定符。
4.普通方法、構造函數和靜態方法中都可以使用泛型。
5.可以用類型變量表示異常,稱之爲參數化的異常,可用於方法的throws列表中,但是不能用於catch子句中。
6.在泛型中可同時有多個類型參數,在定義它們的<>中用逗號分開。
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;
}
}
這個T和?有什麼區別呢?
1.T限定了類型,傳入什麼類型即爲什麼類型,可以定義變量,接收賦值的內容。
2.?爲通配符,也可以接收任意類型但是不可以定義變量。
但是這樣定義,雖然提高了擴展性,可還是有一個侷限性,就是不能使用其他類對象的特有方法。
泛型類
概述:
1.若類實例對象中多出要使用到同一泛型參數,即這些地方引用類型要保持同一個實際類型時,這時候就要採用泛型類型的方式進行定義,也就是類級別的泛型。
2.何時定義泛型類:當類中要操作的引用數據類型不確定時,在早期定義Object來完成擴展,而現在定義泛型。
3.泛型類定義的泛型,在整個類中都有效,如果被方法調用,那麼泛型類的對象要明確需要操作的具體類型後,所有要操作的類就已經固定了。
4.類級別的泛型是根據引用該類名時指定的類型信息來參數化類型變量的。
語法格式:
1.定義
public class GenerDao1<T>{
private T field;
public void save(T obj){}
public T getByteId(int Id){}
}
2.舉例:
擴展:Dao:Data Access Object,數據訪問對象。
對其操作:crud即增上刪改查
c:creat,創建、增加; r:read,讀取、查詢;
u:update,更新、修改 d:delete,刪除。
對javaEE的理解:13種技術。簡單說就是對數據庫的增刪改查。
寫Dao類有五個基本方法:增刪改查,其中查包含查單個和對同類型集合的查詢,如同性別或同地區的集合獲取。
代碼演示:
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;
}
}
注意:1.在對泛型進行參數化時,類型參數的實例必須是引用類型,不能是基本類型。
2.當一個變量被聲明爲參數時,只能被實例變量和方法調用(還有內嵌類型),而不能被靜態變量和靜態方法調用,因爲靜態成員是被所有參數化的類共享的,所以靜態成員不應該有類級別的類型參數。
總結:
對泛型的定義:
1.定義泛型:當又不確定的類型需要傳入到集合中,需要定義泛型
2.定義泛型類:如果類型確定後,所操作的方法都是屬於此類型,則定義泛型類
3.定義泛型方法:如果定義的方法確定了,裏面所操作的類型不確定,則定義泛型方法
代碼演示:
//測試
class GenerticTest {
public static void main(String[] args) {
//創建泛型類對象
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;
//定義公共設置方法,設置屬性
public void setTT(TT t) {
this.t = t;
}
//定義公共訪問方法,訪問屬性
public TT getTT() {
return t;
}
//定義方法
public void showC(TT t) {
System.out.println("GenClass show:" + t);
}
}
//創建Worker類,作爲類型傳入泛型類中
class Worker {}
//泛型方法
class GenMethod<T> {
//靜態的泛型方法
public static <S> void showS(S s) {
System.out.println("static show:" + s);
}
//非靜態泛型方法
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);
}
//一般類實現泛型接口
class GenInter implements Inter<String> {
public void show(String s) {
System.out.println("接口 show:" + s);
}
}
//泛型類實現泛型接口
class GenImpl<T> implements Inter<T> {
public void show(T t) {
System.out.println("類接收類型不確定的實現接口 show:" + t);
}<span style="font-family: Arial, Helvetica, sans-serif;"> </span><pre name="code" class="java">}<span style="font-family: Arial, Helvetica, sans-serif;"> </span>
參數的類型推斷
1.定義:編譯器判斷泛型方法的實際參數的過程,稱之爲類型推斷。
2.類型推斷是相對於直覺推斷的,其實現方法是一種非常複雜的過程。
類型推斷的具體規則:
根據調用泛型方法時,實際傳遞的參數類型或返回值的類型來推斷。
1.當某個類型變量只在整個參數列表中的所有參數和返回值中的一處被應用了,那麼根據調用方法時,該處的實際應用類型來確定,這很容易憑着感覺推斷出來,即直接根據調用方法時,傳遞的參數類型或返回值來決定泛型參數的類型,如:
swap(newString[3],1,2)
---> static <E> void swap(E[] a, inti, int j);
2.當某個類型變量在某個參數列表中的所有參數和返回值中的多處被應用了,如果調用方法時,這多處的實際應用類型都對應同一種類型來表示,這很容易憑感覺推斷出來:
add(3,5)
---> static<T> T add(T a,T b);
3.若對應了不同類型,且沒有使用返回值,這是取多個參數中的最大交集類型,如下面的對應類型Number,編譯沒問題,但是運行會出錯:
fill(new Integer[3],3.5f)
---> static<T> void fill(T[] a,T v);
4.若對應了不同類型,且使用了返回值,這時候優先考慮返回值類型,如下面語句實際對應的類型就是Integer了,編譯將報錯,將變量x類型改爲float,對此eclipse報錯提示,接着再將變量x類型改爲Number,則就沒了錯誤:
int x = add(3,3.5f)
---> static<T> T add(T a,T b);
5.參數類型的類型推斷具有傳遞性,下面第一種情況推斷實際參數類型爲Object,編譯沒問題,而第二種情況則會根據參數化的Vector類實例將類型變量直接確定爲String類型,編譯將出現問題:
copy(newInteger[5],new String[5]);
---> static<T> T copy(T[] a,T[] b);
類加載器
1.定義:簡單說,類加載器就是加載類的工具。
當出現一個類,用到此類的時候,Java虛擬機首先將類字節碼加載進內存,通常字節碼的原始信息放在硬盤上的classpath指定的目錄下。
2.類加載器作用:將.class文件中的內容加載進內存進行處理,處理完後的結果就是字節碼。
3.默認類加載器:
(1)Java虛擬機中可安裝多個類加載器,系統默認的有三個主要的,每個類負責加載特定位置的類:BootStrap、ExtClassLoader、AppClassLoader
(2)BootStrap--頂級類加載器:
類加載器本身也是Java類,因爲它是Java類,本身也需要加載器加載,顯然必須有第一個類加載器而不是java類的,這正是BootStrap。它是嵌套在Java虛擬機內核中的,已啓動即出現在虛擬機中,是用c++寫的一段二進制代碼。所以不能通過java程序獲取其名字,獲得的只能是null。
4.Java虛擬機中的所有類加載器採用子父關係的樹形結構進行組織,在實例化每個類加載器對象或默認採用系統類加載器作爲其父級類加載器。
代碼演示
import java.util.Date;
public class ClassLoadTest{
public static void main(String[] args) throws Exception{
System.out.println(
ClassLoadTest.class.getClassLoader().
getClass().getName());//爲AppClassLoader
System.out.println(
System.class.getClassLoader());//爲null
}
}
類加載器的委託機制:
1.加載類的方式
當Java虛擬機要加載一個類時,到底要用哪個類加載器加載呢?
(1)首先,當前線程的類加載器去加載線程中的第一個類。
(2)若A引用類B(繼承或者使用了B),Java虛擬機將使用加載類的類加載器來加載類B。
(3)還可直接調用ClassLoader的LoaderClass()方法,來制定某個類加載器去加載某個類。
2.加載器的委託機制:每個類加載器加載類時,又先委託給上級類加載器。
每個ClassLoader本身只能分別加載特定位置和目錄中的類,但他們可以委託其他類的加載器去加載,這就是類加載器的委託模式,類加載器一級級委託到BootStrap類加載器,當BootStrap在指定目錄中沒有找到要加載的類時,無法加載當前所要加載的類,就會一級級返回子孫類加載器,進行真正的加載,每級都會先到自己相應指定的目錄中去找,有沒有當前的類;直到退回到最初的類裝載器的發起者時,如果它自身還未找到,未完成類的加載,那就報告ClassNoFoundException的異常。
簡單說,就是先由發起者將類一級級委託爲BootStrap,從父級開始找,找到了直接返回,沒找到再返回給其子級找,直到發起者,再沒找到就報異常。
3.委託機制的優點:可以集中管理,不會產生多字節碼重複的現象。
面試題
可不可以自己寫個類爲:java.lang.System呢?
回答:第一、通常是不可以的,由於類加載器的委託機制,會先將System這個類一級級委託給最頂級的BootStrap,由於BootStrap在其指定的目錄中加載的是rt.jar中的類,且其中有System這個類,那麼就會直接加載自己目錄中的,也就是Java已經定義好的System這個類,而不會加載自定義的這個System。
第二、但是還是有辦法加載這個自定義的System類的,此時就不能交給上級加載了,需要用自定義的類加載器加載,這就需要有特殊的寫法才能去加載這個自定義的System類的。
自定義類加載器
1.自定義的類加載器必須繼承抽象類ClassLoader,要覆寫其中的findClass(String name)方法,而不用覆寫loadClass()方法。
2.覆寫findClass(String name)方法的原因:
(1)是要保留loadClass()方法中的流程,因爲loadClass()中調用了findClass(String name)這個方法,此方法返回的就是去尋找父級的類加載器。
(2)在loadClass()內部是會先委託給父級,當父級找到後就會調用findClass(String name)方法,而找不到時就會用子級的類加載器,再找不到就報異常了,所以只需要覆寫findClass方法,那麼就具有了實現用自定義的類加載器加載類的目的。
流程:
父級-->loadClass-->findClass-->得到Class文件後轉化成字節碼-->defind()。
3.編程步驟:
(1)編寫一個對文件內容進行簡單加盟的程序
(2)編寫好了一個自己的類加載器,可實現對加密過來的類進行裝載和解密。
(3)編寫一個程序,調用類加載器加載類,在源程序中不能用該類名定義引用變量,因爲編譯器無法識別這個類,程序中除了可使用ClassLoader的load方法外,還能使用放置線程的上線文類加載器加載或系統類加載器,然後在使用forName得到字節碼文件。
代碼演示:
//自定義類加載器
import java.io.*;
//繼承抽象類ClassLoader
public class MyClassLoader extends ClassLoader {
public static void main(String[] args) throws Exception {
//傳入兩個參數,源和目標
String scrPath = args[0];
String destDir = args[1];
//將數據讀取到輸入流中,並寫入到輸出流中
FileInputStream fis = new FileInputStream(scrPath);
String destFileName =
scrPath.substring(scrPath.lastIndexOf('\\')+1);
String destPath = destDir + "\\" + destFileName;
FileOutputStream fos = new FileOutputStream(destPath);
//加密數據
cypher(fis,fos);
fis.close();
fos.close();
}
//定義加密數據的方法
private static void cypher(InputStream ips,OutputStream ops)throws Exception{
int b = 0;
while((b=ips.read())!=-1){
ops.write(b ^ 0xff);
}
}
//定義全局變量
private String classDir;
@Override//覆寫findClass方法,自定義類加載器
protected Class<?> findClass(String name) throws ClassNotFoundException {
String classFileName = classDir + "\\" + name + ".class";
try {
//將要加載的文件讀取到流中,並寫入字節流中
FileInputStream fis = new FileInputStream(classFileName);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
cypher(fis,bos);
fis.close();
byte[] bytes = bos.toByteArray();
return defineClass(bytes, 0, bytes.length);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//如果沒找到類,則用父級類加載器加載
return super.findClass(name);
}
//構造函數
public MyClassLoader(){}
public MyClassLoader(String classDir){
this.classDir = classDir;
}
}