讀書(shū)赡若,收獲蓝厌,分享
建議后面的五角星僅代表筆者個(gè)人需要注意的程度牍汹。
Talk is cheap.Show me the code
建議93:Java的泛型是類(lèi)型擦除的★☆☆☆☆
Java的泛型在編譯期有效唇聘,在運(yùn)行期被刪除介却,也就是說(shuō)所有的泛型參數(shù)類(lèi)型在編譯后都會(huì)被清除掉砰识。
//下面這種方法的重載能扒,編輯器會(huì)報(bào)錯(cuò),提示方法沖突....
//'listMethod(List<String>)' clashes with 'listMethod(List<Integer>)'; both methods have same erasure
public void listMethod(List<String> stringList){
}
public void listMethod(List<Integer> intList) {
}
這就是Java泛型擦除引起的問(wèn)題:在編譯后所有的泛型類(lèi)型都會(huì)做相應(yīng)的轉(zhuǎn)化辫狼。轉(zhuǎn)換規(guī)則如下:
-
List<String>
初斑、List<Integer>
、List<T>
擦除后的類(lèi)型為List
膨处。 -
List<String>[]
擦除后的類(lèi)型為List[]
见秤。 -
List<? extends E>
、List<? super E>
擦除后的類(lèi)型為List<E>
真椿。 -
List<T extends Serializable&Cloneable>
擦除后為List<Serializable>
鹃答。
Java之所以如此處理,有兩個(gè)原因:
- 避免
JVM
的大換血突硝。C++
的泛型生命期延續(xù)到了運(yùn)行期测摔,而Java是在編譯器擦除掉的,如果JVM
也把泛型類(lèi)型延續(xù)到運(yùn)行期解恰,那么JVM
就需要進(jìn)行大量的重構(gòu)工作了锋八。 - 版本兼容。在編譯期擦除可以更好地支持原生類(lèi)型(Raw Type)修噪,在Java 1.5或1.6平臺(tái)上查库,即使聲明一個(gè)
List
這樣的原生類(lèi)型也是可以正常編譯通過(guò)的,只是會(huì)產(chǎn)生警告信息而已黄琼。
我們就可以解釋類(lèi)似如下的問(wèn)題了:
-
泛型的
class
對(duì)象是相同的public static void main(String[] args) { List<String> ls = new ArrayList<String>(); List<Integer> li = new ArrayList<Integer>(); System.out.println(ls.getClass() == li.getClass()); //運(yùn)行結(jié)果:true }
每個(gè)類(lèi)都有一個(gè)class屬性樊销,泛型化不會(huì)改變
class
屬性的返回值 -
泛型數(shù)組初始化時(shí)不能聲明泛型類(lèi)型
//如下代碼編譯時(shí)通不過(guò): List<String>[] list = new List<String>[];
可以聲明一個(gè)帶有泛型參數(shù)的數(shù)組,但是不能初始化該數(shù)組脏款,因?yàn)閳?zhí)行了類(lèi)型擦除操作围苫,
List<Object>[]
與List<String>[]
就是同一回事了,編譯器拒絕如此聲明撤师。 -
instanceof
不允許存在泛型參數(shù)//以下代碼不能通過(guò)編譯剂府,原因一樣,泛型類(lèi)型被擦除了: List<String> list = new ArrayList<String>(); System.out.println(list instanceof List<String>)
建議94:不能初始化泛型參數(shù)和數(shù)組★☆☆☆☆
示例如下:
//這段代碼是編譯通不過(guò)的剃盾,因?yàn)榫幾g器在編譯時(shí)需要獲得T類(lèi)型腺占,但泛型在編譯期類(lèi)型已經(jīng)被擦除了
//所以new T()和new T[5]都會(huì)報(bào)錯(cuò)
public class Client {
private T t = new T();
private T[] tArray = new T[5];
private List<T> list = new ArrayList<T>();
}
在某些情況下淤袜,我們確實(shí)需要泛型數(shù)組,那該如何處理呢衰伯?代碼如下:
public class Client<T> {
//不在初始化铡羡,由構(gòu)造函數(shù)初始化
private T t ;
private T[] tArray;
private List<T> list = new ArrayList<T>();
public Client() {
try {
Class<?> tType = Class.forName("");
t = (T) tType.newInstance();
tArray = (T[]) Array.newInstance(tType,5);
}catch (Exception e){
e.printStackTrace();
}
}
類(lèi)的成員變量是在類(lèi)初始化前初始化的,所以要求在初始化前它必須具有明確的類(lèi)型意鲸,否則就只能聲明烦周,不能初始化。
建議95:強(qiáng)制聲明泛型的實(shí)際類(lèi)型★☆☆☆☆
示例:
class ArrayUtils{
//把一個(gè)變長(zhǎng)參數(shù)轉(zhuǎn)變?yōu)榱斜? public static <T> List<T> asList(T...t){
List<T> list = new ArrayList<T>();
Collections.addAll(list, t);
return list;
}
}
public class Client {
public static void main(String[] args) {
//強(qiáng)制聲明泛型類(lèi)型
//asList方法要求的是一個(gè)泛型參數(shù)怎顾,在輸入前定義這是一個(gè)Integer類(lèi)型的參數(shù)读慎,當(dāng)然,輸出也是Integer類(lèi)型的集合了
List<Integer> list = ArrayUtils.<Integer>asList();
}
}
注意:無(wú)法從代碼中推斷出泛型類(lèi)型的情況下槐雾,即可強(qiáng)制聲明泛型類(lèi)型夭委。
建議96:不同的場(chǎng)景使用不同的泛型通配符★★☆☆☆
Java泛型支持通配符(Wildcard),可以單獨(dú)使用一個(gè)“蚜退?”
表示任意類(lèi)闰靴,也可以使用extends
關(guān)鍵字表示某一個(gè)類(lèi)(接口)的子類(lèi)型,還可以使用super
關(guān)鍵字表示某一個(gè)類(lèi)(接口)的父類(lèi)型钻注,但問(wèn)題是什么時(shí)候該用extends
,什么時(shí)候該用super
呢配猫?
-
泛型結(jié)構(gòu)只參與“讀”操作則限定上界(使用
extends
關(guān)鍵字)public static <E> void read(List<? extends E> list){ for(E e:list){ System.out.println(e.getClass()); //業(yè)務(wù)邏輯處理 } }
-
泛型結(jié)構(gòu)只參與“寫(xiě)”操作則限定下界(使用
super
關(guān)鍵字)public static void write(List<? super Number> list) { list.add(123); list.add(3.14); }
對(duì)于是要限定上界還是限定下界幅恋,JDK
的Collections.copy
方法是一個(gè)非常好的例子,它實(shí)現(xiàn)了把源列表中的所有元素拷貝到目標(biāo)列表中對(duì)應(yīng)的索引位置上泵肄,代碼如下:
//源列表是用來(lái)提供數(shù)據(jù)的捆交,所以src變量需要限定上界,帶有extends關(guān)鍵字腐巢。
//目標(biāo)列表是用來(lái)寫(xiě)入數(shù)據(jù)的品追,所以dest變量需要界定上界,帶有super關(guān)鍵字冯丙。
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
int srcSize = src.size();
if (srcSize > dest.size())
throw new IndexOutOfBoundsException("Source does not fit in dest");
if (srcSize < COPY_THRESHOLD ||
(src instanceof RandomAccess && dest instanceof RandomAccess)) {
for (int i=0; i<srcSize; i++)
dest.set(i, src.get(i));
} else {
ListIterator<? super T> di=dest.listIterator();
ListIterator<? extends T> si=src.listIterator();
for (int i=0; i<srcSize; i++) {
di.next();
di.set(si.next());
}
}
}
如果一個(gè)泛型結(jié)構(gòu)即用作“讀”操作又用作“寫(xiě)”操作肉瓦,那該如何進(jìn)行限定呢?不限定胃惜,使用確定的泛型類(lèi)型即可泞莉,如
List<E>
。
建議97:警惕泛型是不能協(xié)變和逆變的★★☆☆☆
什么叫協(xié)變(covariance
)和逆變(contravariance
)船殉?
在編程語(yǔ)言的類(lèi)型框架中鲫趁,協(xié)變和逆變是指寬類(lèi)型和窄類(lèi)型在某種情況下(如參數(shù)、泛型利虫、返回值)替換或交換的特性挨厚,簡(jiǎn)單地說(shuō)堡僻,協(xié)變是用一個(gè)窄類(lèi)型替換寬類(lèi)型,而逆變則是用寬類(lèi)型覆蓋窄類(lèi)型疫剃。
泛型既不支持協(xié)變钉疫,也不支持逆變:
public static void main(String[] args) {
//數(shù)組支持協(xié)變
Number[] n = new Integer[10];
//編譯不通過(guò),泛型不支持協(xié)變
List<Number> ln = new ArrayList<Integer>();
//報(bào)錯(cuò):Type mismatch: cannot convert from ArrayList<Integer> to List<Number>
}
-
可以使用通配符(Wildcard)模擬協(xié)變慌申,代碼如下所示:
//Number的子類(lèi)型都可以是泛型參數(shù)類(lèi)型 List<? extends Number> ln = new ArrayList<Integer>();
-
可以使用super關(guān)鍵字來(lái)模擬逆變陌选,代碼如下所示:
//Integer的父類(lèi)型(包括Integer)都可以是泛型參數(shù)類(lèi)型 List<? super Integer> li = new ArrayList<Number>();
注意:Java的泛型是不支持協(xié)變和逆變的,只是能夠?qū)崿F(xiàn)協(xié)變和逆變蹄溉。
建議98:建議采用的順序是List<T>
咨油、List<?>
、List<Object>
★★☆☆☆
原因如下:
-
List<T>
是確定的某一個(gè)類(lèi)型List<T>
表示的是List集合中的元素都為T
類(lèi)型柒爵,具體類(lèi)型在運(yùn)行期決定役电;List<?>
表示的是任意類(lèi)型,與List<T>
類(lèi)似棉胀,而
List<Object>
則表示List
集合中的所有元素為Object
類(lèi)型法瑟,因?yàn)?code>Object是所有類(lèi)的父類(lèi),所以List<Object>
也可以容納所有的類(lèi)類(lèi)型唁奢,從這一字面意義上分析霎挟,
List<T>
更符合習(xí)慣:編碼者知道它是某一個(gè)類(lèi)型,只是在運(yùn)行期才確定而已麻掸。 -
List<T>
可以進(jìn)行讀寫(xiě)操作List<T>
可以進(jìn)行如add
酥夭、remove
等操作,因?yàn)樗念?lèi)型是固定的T
類(lèi)型脊奋,在編碼期不需要進(jìn)行任何的轉(zhuǎn)型操作熬北。List<?>
是只讀類(lèi)型的,不能進(jìn)行增加诚隙、修改操作讶隐,因?yàn)榫幾g器不知道List
中容納的是什么類(lèi)型的元素,也就無(wú)法校驗(yàn)類(lèi)型是否安全了久又,而且List<?>
讀取出的元素都是Object
類(lèi)型的巫延,需要主動(dòng)轉(zhuǎn)型,所以它經(jīng)常用于泛型方法的返回值籽孙。注意烈评,List<?>
雖然無(wú)法增加、修改元素犯建,但是卻可以刪除元素讲冠,比如執(zhí)行remove
、clear
等方法适瓦,那是因?yàn)樗膭h除動(dòng)作與泛型類(lèi)型無(wú)關(guān)竿开。List<Object>
也可以讀寫(xiě)操作谱仪,但是它執(zhí)行寫(xiě)入操作時(shí)需要向上轉(zhuǎn)型(Up cast),在讀取數(shù)據(jù)后需要向下轉(zhuǎn)型(Downcast)否彩,而此時(shí)已經(jīng)失去了泛型存在的意義了疯攒。
建議99:嚴(yán)格限定泛型類(lèi)型采用多重界限★★★☆☆
比如在公交車(chē)費(fèi)優(yōu)惠系統(tǒng)中,對(duì)部分人員(如工資低于2500元的上班族并且是站立著的乘客)車(chē)費(fèi)打8折列荔,該如何實(shí)現(xiàn)呢敬尺?
//職員
interface Staff{
//工資
public int getSalary();
}
//乘客
interface Passenger{
//是否是站立狀態(tài)
public boolean isStanding();
}
class Me implements Staff,Passenger{
public boolean isStanding(){
return true;
}
public int getSalary() {
return 2000;
}
}
//使用多重限定
public class Client {
//工資低于2500元的上斑族并且站立的乘客車(chē)票打8折
public static <T extends Staff & Passenger> void discount(T t){
if(t.getSalary()<2500 && t.isStanding()){
System.out.println("恭喜你!您的車(chē)票打八折贴浙!");
}
}
public static void main(String[] args) {
discount(new Me());
}
}
在Java的泛型中砂吞,可以使用“&”
符號(hào)關(guān)聯(lián)多個(gè)上界并實(shí)現(xiàn)多個(gè)邊界限定,而且只有上界才有此限定崎溃,下界沒(méi)有多重限定的情況蜻直。
使用多重邊界可以很方便地解決問(wèn)題,而且非常優(yōu)雅袁串,建議在開(kāi)發(fā)中考慮使用多重限定
建議100:數(shù)組的真實(shí)類(lèi)型必須是泛型類(lèi)型的子類(lèi)型★★★☆☆
期望輸入的是一個(gè)泛型化的List概而,轉(zhuǎn)化為泛型數(shù)組,代碼如下:
public class Client<T> {
public static <T> T[] toArray(List<T> list) {
T[] t = (T[]) new Object[list.size()];
for (int i = 0, n = list.size(); i < n; i++) {
t[i] = list.get(i);
}
return t;
}
public static void main(String[] args) {
List<String> list = Arrays.asList("A", "B");
for (String str : toArray(list)) {//這一句報(bào)錯(cuò)囱修,Object數(shù)組不能向下轉(zhuǎn)型為String數(shù)組
System.out.println(str);
}
}
}
運(yùn)行異常如下:
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
at com.jyswm.demo.Client.main(Client.java:17)
因?yàn)榉盒褪穷?lèi)型擦除的赎瑰,toArray
方法經(jīng)過(guò)編譯后與如下代碼相同:
public static Object[] toArray(List list){
//此處的強(qiáng)制類(lèi)型沒(méi)必要存在,只是為了保持與源代碼對(duì)比
Object[] t = (Object[])new Object[list.size()];
for(int i=0,n=list.size();i<n;i++){
t[i] = list.get(i);
}
return t;
}
那該如何解決呢破镰?
其實(shí)要想把一個(gè)Obejct
數(shù)組轉(zhuǎn)換為String
數(shù)組乡范,只要Object
數(shù)組的實(shí)際類(lèi)型(Actual Type)也是String
就可以了,例如:
public class Client<T> {
public static void main(String[] args) {
//objArray的實(shí)際類(lèi)型和表面類(lèi)型都是String數(shù)組
Object[] objArray = {"A","B"};
//拋出ClassCastException
String[] strArray = (String[])objArray;
String[] ss = {"A","B"};
//objs的真實(shí)類(lèi)型是String數(shù)組啤咽,顯示類(lèi)型為Object數(shù)組
Object[] objs = ss;
//順利轉(zhuǎn)換為String數(shù)組
String[] strs = (String[])objs;
}
}
如此,那就把泛型數(shù)組聲明為泛型類(lèi)的子類(lèi)型吧渠脉!代碼如下:
public class Client<T> {
public static <T> T[] toArray(List<T> list, Class<T> tClass) {
//聲明并初始化一個(gè)T類(lèi)型的數(shù)組
//通過(guò)反射類(lèi)Array聲明了一個(gè)T類(lèi)型的數(shù)組宇整,
//由于我們無(wú)法在運(yùn)行期獲得泛型類(lèi)型的參數(shù),因此就需要調(diào)用者主動(dòng)傳入T參數(shù)類(lèi)型
T[] t = (T[]) Array.newInstance(tClass, list.size());
for(int i=0,n=list.size();i<n;i++){
t[i] = list.get(i);
}
return t;
}
public static void main(String[] args) {
List<String> list = Arrays.asList("A", "B");
for (String str : toArray(list,String.class)) {
System.out.println(str);
}
}
}
注意:當(dāng)一個(gè)泛型類(lèi)(特別是泛型集合)轉(zhuǎn)變?yōu)榉盒蛿?shù)組時(shí)芋膘,泛型數(shù)組的真實(shí)類(lèi)型不能是泛型類(lèi)型的父類(lèi)型(比如頂層類(lèi)Object)鳞青,只能是泛型類(lèi)型的子類(lèi)型(當(dāng)然包括自身類(lèi)型),否則就會(huì)出現(xiàn)類(lèi)型轉(zhuǎn)換異常为朋。
建議101:注意Class類(lèi)的特殊性★☆☆☆☆
Java語(yǔ)言是先把Java源文件編譯成后綴為class
的字節(jié)碼文件臂拓,然后再通過(guò)ClassLoader
機(jī)制把這些類(lèi)文件加載到內(nèi)存中,最后生成實(shí)例執(zhí)行的习寸,這是Java處理的基本機(jī)制胶惰,但是加載到內(nèi)存中的數(shù)據(jù)是如何描述一個(gè)類(lèi)的呢?
Java使用一個(gè)元類(lèi)(MetaClass
)來(lái)描述加載到內(nèi)存中的類(lèi)數(shù)據(jù)霞溪,這就是Class
類(lèi)孵滞,它是一個(gè)描述類(lèi)的類(lèi)對(duì)象中捆。
Class
類(lèi)特殊的地方:
無(wú)構(gòu)造函數(shù)。Java中的類(lèi)一般都有構(gòu)造函數(shù)坊饶,但是
Class
類(lèi)卻沒(méi)有構(gòu)造函數(shù)泄伪,不能實(shí)例化,Class
對(duì)象是在加載類(lèi)時(shí)由 Java 虛擬機(jī)通過(guò)調(diào)用類(lèi)加載器中的defineClass
方法自動(dòng)構(gòu)造的匿级。可以描述基本類(lèi)型蟋滴。雖然8個(gè)基本類(lèi)型在
JVM
中并不是一個(gè)對(duì)象,它們一般存在于棧內(nèi)存中痘绎,但是Class
類(lèi)仍然可以描述它們津函,例如可以使用int.class
表示int
類(lèi)型的類(lèi)對(duì)象。-
其對(duì)象都是單例模式简逮。一個(gè)
Class
的實(shí)例對(duì)象描述一個(gè)類(lèi)球散,并且只描述一個(gè)類(lèi),反過(guò)來(lái)也成立散庶,一個(gè)類(lèi)只有一個(gè)Class
實(shí)例對(duì)象蕉堰,如下代碼返回的結(jié)果都為true
:public class Client { public static void main(String[] args) throws Exception { //類(lèi)的屬性class所引用的對(duì)象與實(shí)例對(duì)象的getClass返回值相同 String.class.equals(new String().getClass()); "ABC".getClass().equals(String.class); //class實(shí)例對(duì)象不區(qū)分泛型 ArrayList.class.equals(new ArrayList<String>().getClass()); } }
建議102:適時(shí)選擇getDeclared×××
和get×××
★☆☆☆☆
Java的Class
類(lèi)提供了很多的getDeclared×××
方法和get×××
方法,如下:
public static void main(String[] args) throws Exception {
//方法名稱(chēng)
String methodName = "doStuff";
Method m1 = Foo.class.getDeclaredMethod(methodName);
Method m2 = Foo.class.getMethod(methodName);
}
getMethod
方法獲得的是所有public
訪問(wèn)級(jí)別的方法悲龟,包括從父類(lèi)繼承的方法屋讶,而getDeclaredMethod
獲得是自身類(lèi)的所有方法,包括公用(public
)方法须教、私有(private
)方法等皿渗,而且不受限于訪問(wèn)權(quán)限。
其他的getDeclaredConstructors
和getConstructors
轻腺、getDeclaredFields
和getFields
等與此相似乐疆。
建議103:反射訪問(wèn)屬性或方法時(shí)將Accessible
設(shè)置為true
★★☆☆☆
Java中通過(guò)反射執(zhí)行一個(gè)方法的過(guò)程如下:獲取一個(gè)方法對(duì)象,然后根據(jù)isAccessible
返回值確定是否能夠執(zhí)行贬养,如果返回值為false
則需要調(diào)用setAccessible(true)
挤土,最后再調(diào)用invoke執(zhí)行方法。如下:
Method method= ...;
//檢查是否可以訪問(wèn)
if(!method.isAccessible()){
method.setAccessible(true);
}
//執(zhí)行方法
method.invoke(obj, args);
那為什么要這么寫(xiě)呢误算?
首先仰美,Accessible
的屬性并不是訪問(wèn)權(quán)限,而是指是否要更容易獲得儿礼,是否進(jìn)行安全檢查咖杂。
AccessibleObject
類(lèi)的源代碼如下:
//它提供了取消默認(rèn)訪問(wèn)控制檢查的功能
public class AccessibleObject implements AnnotatedElement {
//定義反射的默認(rèn)操作權(quán)限suppressAccessChecks
static final private java.security.Permission ACCESS_PERMISSION =
new ReflectPermission("suppressAccessChecks");
//是否重置了安全檢查,默認(rèn)為false
boolean override;
//構(gòu)造函數(shù)
protected AccessibleObject() {}
//是否可以快速獲取蚊夫,默認(rèn)是不能
public boolean isAccessible() {
return override;
}
}
Accessible
屬性只是用來(lái)判斷是否需要進(jìn)行安全檢查的诉字,如果不需要?jiǎng)t直接執(zhí)行,這就可以大幅度的提升系統(tǒng)性能了(注意:取消了安全檢查,也可以運(yùn)行private
方法奏窑、訪問(wèn)private
屬性的)导披。經(jīng)過(guò)測(cè)試,在大量的反射情況下埃唯,設(shè)置Accessible
為true
可以提高性能20倍左右撩匕。
建議104:使用forName
動(dòng)態(tài)加載類(lèi)文件★★☆☆☆
動(dòng)態(tài)加載(Dynamic Loading)是指在程序運(yùn)行時(shí)加載需要的類(lèi)庫(kù)文件。
對(duì)Java程序來(lái)說(shuō)墨叛,一般情況下止毕,一個(gè)類(lèi)文件在啟動(dòng)時(shí)或首次初始化時(shí)會(huì)被加載到內(nèi)存中,而反射則可以在運(yùn)行時(shí)再?zèng)Q定是否要加載一個(gè)類(lèi)漠趁。
比如從Web上接收一個(gè)String
參數(shù)作為類(lèi)名扁凛,然后在JVM
中加載并初始化,這就是動(dòng)態(tài)加載闯传,此動(dòng)態(tài)加載通常是通過(guò)Class.forName(String)
實(shí)現(xiàn)的谨朝,只是為什么要使用forName
方法動(dòng)態(tài)加載一個(gè)類(lèi)文件呢?
因?yàn)槲覀儾恢缹⒁傻膶?shí)例對(duì)象是什么類(lèi)型(如果知道就不用動(dòng)態(tài)加載)甥绿,而且方法和屬性都不可訪問(wèn)字币。
動(dòng)態(tài)加載的意義在什么地方呢?示例如下:
class Utils{
//靜態(tài)代碼塊
static{
System.out.println("Do Something.....");
}
}
public class Client {
public static void main(String[] args) throws ClassNotFoundException {
//動(dòng)態(tài)加載
Class.forName("Utils");
//此時(shí)輸出了:Do Something.....
}
}
如上共缕,并沒(méi)有對(duì)Utils
做任何初始化洗出,只是通過(guò)forName
方法加載了Utils
類(lèi),但是卻產(chǎn)生了一個(gè)Do Something
的輸出图谷,這就是因?yàn)?code>Utils類(lèi)被加載后翩活,JVM
會(huì)自動(dòng)初始化其static
變量和static
代碼塊,這是類(lèi)加載機(jī)制所決定的便贵。
經(jīng)典的應(yīng)用:數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序的加載片段
//加載驅(qū)動(dòng)
Class.forName("com.mysql..jdbc.Driver");
String url="jdbc:mysql://localhost:3306/db?user=&password=";
Connection conn =DriverManager.getConnection(url);
Statement stmt =conn.createStatement();
Class.forName("com.mysql..jdbc.Driver");
這一句的意義菠镇,示例如下:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
//靜態(tài)代碼塊
static {
try {
//把自己注冊(cè)到DriverManager中
DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
//異常處理
throw new RuntimeException("Can't register driver!");
}
}
//構(gòu)造函數(shù)
public Driver() throws SQLException {
}
}
程序邏輯如下:當(dāng)程序動(dòng)態(tài)加載該驅(qū)動(dòng)時(shí),也就是執(zhí)行到Class.forName("com.mysql.jdbc.Driver")
時(shí)承璃,Driver
類(lèi)會(huì)被加載到內(nèi)存中辟犀,也就是把自己注冊(cè)到DriverManager
中绸硕。
forName
只是把一個(gè)類(lèi)加載到內(nèi)存中,并不保證由此產(chǎn)生一個(gè)實(shí)例對(duì)象魂毁,也不會(huì)執(zhí)行任何方法,之所以會(huì)初始化static
代碼,那是由類(lèi)加載機(jī)制所決定的,而不是forName
方法決定的晶府。也就是說(shuō)桂躏,如果沒(méi)有static
屬性或static
代碼塊,forName
就只是加載類(lèi)川陆,沒(méi)有任何的執(zhí)行行為剂习。
注意:
forName
只是加載類(lèi),并不執(zhí)行任何代碼较沪。
建議105:動(dòng)態(tài)加載不適合數(shù)組★☆☆☆☆
在Java中鳞绕,數(shù)組是一個(gè)非常特殊的類(lèi),雖然它是一個(gè)類(lèi)购对,但沒(méi)有定義類(lèi)路徑猾昆。
示例:
public static void main(String[] args) throws ClassNotFoundException {
String [] strs = new String[10];
Class.forName("java.lang.String[]");
}
運(yùn)行異常,如下:
Exception in thread "main" java.lang.ClassNotFoundException: java/lang/String[]
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:186)
因?yàn)榫幾g器編譯后為不同的數(shù)組類(lèi)型生成不同的類(lèi)骡苞,具體如下表所示:
所以動(dòng)態(tài)加載一個(gè)對(duì)象數(shù)組只要加載編譯后的數(shù)組對(duì)象就可以了垂蜗,修改代碼如下:
//加載一個(gè)String數(shù)組
Class.forName("[Ljava.lang.String;");
//加載一個(gè)Long數(shù)組
Class.forName("[J");
但是這種操作沒(méi)有什么意義,因?yàn)樗荒苌梢粋€(gè)數(shù)組對(duì)象解幽,只是把一個(gè)String
類(lèi)型的數(shù)組類(lèi)和long
類(lèi)型的數(shù)組類(lèi)加載到了內(nèi)存中贴见,它沒(méi)有定義數(shù)組的長(zhǎng)度,在Java中數(shù)組是定長(zhǎng)的躲株,沒(méi)有長(zhǎng)度的數(shù)組是不允許存在的片部。
因?yàn)閿?shù)組的特殊性,所以Java專(zhuān)門(mén)定義了一個(gè)Array
數(shù)組反射工具類(lèi)來(lái)實(shí)現(xiàn)動(dòng)態(tài)探知數(shù)組的功能霜定,如下:
// 動(dòng)態(tài)創(chuàng)建數(shù)組
String[] strs = (String[]) Array.newInstance(String.class, 8);
// 創(chuàng)建一個(gè)多維數(shù)組
int[][] ints = (int[][]) Array.newInstance(int.class, 2, 3);
注意:通過(guò)反射操作數(shù)組使用
Array
類(lèi)档悠,不要采用通用的反射處理API
。
建議106:動(dòng)態(tài)代理可以使代理模式更加靈活★★★☆☆
Java的反射框架提供了動(dòng)態(tài)代理(Dynamic Proxy)機(jī)制望浩,允許在運(yùn)行期對(duì)目標(biāo)類(lèi)生成代理辖所,避免重復(fù)開(kāi)發(fā)。
首先磨德,簡(jiǎn)單的靜態(tài)代理實(shí)現(xiàn)示例如下:
/**
* 抽象角色-廚師
*/
interface Chef {
/**
* 提供餃子
*/
String dumplings();
/**
* 提供面條
*/
String noodles();
}
/**
* 具體角色-廚師老張
*/
class RealChef implements Chef {
@Override
public String dumplings() {
return "老張秘制酸湯水餃";
}
@Override
public String noodles() {
return "老張秘制蘭州牛肉面";
}
}
/**
* 代理角色(proxy)-幸福餐廳
*/
public class HappyRestaurant implements Chef {
/**
* 要代理哪個(gè)實(shí)現(xiàn)類(lèi)(要讓哪個(gè)廚師做)
*/
private Chef chef = null;
/**
* 默認(rèn)被代理者(默認(rèn)的廚師老張)
*/
public HappyRestaurant() {
chef = new RealChef();
}
/**
* 通過(guò)構(gòu)造函數(shù)傳遞被代理者(客戶點(diǎn)名哪個(gè)廚師做)
*/
public HappyRestaurant(Chef _chef) {
chef = _chef;
}
@Override
public String dumplings() {
before();
return chef.dumplings();
}
@Override
public String noodles() {
before();
return chef.noodles();
}
/**
* 預(yù)處理
*/
private void before() {
// 先收銀
}
}
//調(diào)用
public static void main(String[] args) {
//來(lái)到幸福餐廳
HappyRestaurant happyRestaurant = new HappyRestaurant();
//點(diǎn)了一份餃子
String food = happyRestaurant.dumplings();
System.out.println(food);
//得到:老張秘制酸湯水餃
}
代理:"你去餐廳吃飯缘回,并沒(méi)有見(jiàn)給你真正做飯的廚師老張吆视,而是由餐廳的服務(wù)人員端到你面前的。"
改為動(dòng)態(tài)代理示例如下:
/**
* 抽象角色-廚師
*/
interface Chef {
/**
* 提供餃子
*/
String dumplings();
/**
* 提供面條
*/
String noodles();
}
/**
* 具體角色-廚師老張
*/
class RealChef implements Chef {
@Override
public String dumplings() {
return "老張秘制酸湯水餃";
}
@Override
public String noodles() {
return "老張秘制蘭州牛肉面";
}
}
/**
* 委托處理(不是具體的哪一家餐廳酥宴,而是美團(tuán)了)
*/
public class ChefHandler implements InvocationHandler {
/**
* 被代理的對(duì)象(廚師)
*/
private Chef chef;
public ChefHandler(Chef _chef) {
chef = _chef;
}
/**
* 委托處理方法(點(diǎn)外賣(mài))
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 預(yù)處理
System.out.println("預(yù)處理...");
//直接調(diào)用被代理的方法
Object obj = method.invoke(chef, args);
// 后處理
System.out.println("后處理...");
return obj;
}
}
注意看啦吧,這里沒(méi)有了餐廳這個(gè)角色,取而代之的是ChefHandler
作為主要的邏輯委托處理拙寡,其中invoke
方法是接口InvocationHandler
定義必須實(shí)現(xiàn)的授滓,它完成了對(duì)真實(shí)方法的調(diào)用。
InvocationHanlder
接口:動(dòng)態(tài)代理是根據(jù)被代理的接口生成所有方法的倒庵,也就是說(shuō)給定一個(gè)(或多個(gè))接口褒墨,動(dòng)態(tài)代理會(huì)宣稱(chēng)“我已經(jīng)實(shí)現(xiàn)該接口下的所有方法了”
動(dòng)態(tài)代理的場(chǎng)景類(lèi),代碼如下:
public static void main(String[] args) {
//被代理類(lèi)(想吃老張做的飯擎宝,確定目標(biāo))
Chef chef = new RealChef();
//代理實(shí)例的處理Handler(打開(kāi)美團(tuán)app搜索老張)
InvocationHandler handler = new ChefHandler(chef);
//當(dāng)前加載器(美團(tuán)開(kāi)始搜索并加載老張的信息)
ClassLoader classLoader = chef.getClass().getClassLoader();
//動(dòng)態(tài)代理(美團(tuán)已經(jīng)擁有了老張的所有能力郁妈,比如提供一份水餃等)
Chef proxy = (Chef) Proxy.newProxyInstance(classLoader, chef.getClass().getInterfaces(), handler);
//調(diào)用具體方法(點(diǎn)一份酸湯水餃)
String food = proxy.dumplings();
System.out.println(food);
//得到: 老張秘制酸湯水餃
}
此時(shí)就實(shí)現(xiàn)了不用顯式創(chuàng)建代理類(lèi)即實(shí)現(xiàn)代理的功能。例如可以在被代理角色執(zhí)行前進(jìn)行權(quán)限判斷绍申,或者執(zhí)行后進(jìn)行數(shù)據(jù)校驗(yàn)噩咪。
建議107:使用反射增加裝飾模式的普適性★★★☆☆
裝飾模式(Decorator Pattern)的定義是 動(dòng)態(tài)地給一個(gè)對(duì)象添加一些額外的職責(zé)。就增加功能來(lái)說(shuō)极阅,裝飾模式相比于生成子類(lèi)更為靈活胃碾,
使用Java的動(dòng)態(tài)代理也可以實(shí)現(xiàn)裝飾模式的效果,而且其靈活性筋搏、適應(yīng)性都會(huì)更強(qiáng)仆百。
裝飾一只小老鼠,讓它更強(qiáng)大奔脐,示例如下:
interface Animal{
public void doStuff();
}
class Rat implements Animal{
@Override
public void doStuff() {
System.out.println("Jerry will play with Tom ......");
}
}
/**
* 使用裝飾模式俄周,給老鼠增加一些能力,比如飛行髓迎,鉆地等能力
*/
//定義某種能力
interface Feature{
//加載特性
public void load();
}
//飛行能力
class FlyFeature implements Feature{
@Override
public void load() {
System.out.println("增加一對(duì)翅膀...");
}
}
//鉆地能力
class DigFeature implements Feature{
@Override
public void load() {
System.out.println("增加鉆地能力...");
}
}
/**
* 要把這兩種屬性賦予到老鼠身上峦朗,那需要一個(gè)包裝動(dòng)作類(lèi)
*/
class DecorateAnimal implements Animal {
// 被包裝的動(dòng)物
private Animal animal;
// 使用哪一個(gè)包裝器
private Class<? extends Feature> clz;
public DecorateAnimal(Animal _animal, Class<? extends Feature> _clz) {
animal = _animal;
clz = _clz;
}
@Override
public void doStuff() {
InvocationHandler handler = new InvocationHandler() {
// 具體包裝行為
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object obj = null;
if (Modifier.isPublic(method.getModifiers())) {
obj = method.invoke(clz.newInstance(), args);
}
animal.doStuff();
return obj;
}
};
//當(dāng)前加載器
ClassLoader cl = getClass().getClassLoader();
//動(dòng)態(tài)代理,又handler決定如何包裝
Feature proxy = (Feature) Proxy.newProxyInstance(cl, clz.getInterfaces(), handler);
proxy.load();
}
}
/**
* 注意看doStuff方法排龄,
* 一個(gè)裝飾類(lèi)型必然是抽象構(gòu)建(Component)的子類(lèi)型波势,它必須實(shí)現(xiàn)doStuff方法,此處的doStuff方法委托給了動(dòng)態(tài)代理執(zhí)行橄维,
* 并且在動(dòng)態(tài)代理的控制器Handler中還設(shè)置了決定裝飾方式和行為的條件(即代碼中InvocationHandler匿名類(lèi)中的if判斷語(yǔ)句)尺铣,
* 當(dāng)然,此處也可以通過(guò)讀取持久化數(shù)據(jù)的方式進(jìn)行判斷争舞,這樣就更加靈活了迄埃。
*/
/**
* 客戶端進(jìn)行調(diào)
*/
public static void main(String[] args) {
//定義Jerry這只老鼠
Animal jerry = new Rat();
//為Jerry增加飛行能力
jerry = new DecorateAnimal(jerry, FlyFeature.class);
//jerry增加挖掘能力
jerry = new DecorateAnimal(jerry, DigFeature.class);
//Jerry開(kāi)始戲弄貓了
jerry.doStuff();
}
// 裝飾行為由動(dòng)態(tài)代理實(shí)現(xiàn),實(shí)現(xiàn)了對(duì)裝飾類(lèi)和被裝飾類(lèi)的完全解耦兑障,提供了系統(tǒng)的擴(kuò)展性。
建議108:反射讓模板方法模式更強(qiáng)大★★★☆☆
模板方法模式(Template Method Pattern
)的定義是:定義一個(gè)操作中的算法骨架,將一些步驟延遲到子類(lèi)中流译,使子類(lèi)不改變一個(gè)算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟逞怨。簡(jiǎn)單地說(shuō),就是父類(lèi)定義抽象模板作為骨架福澡,其中包括基本方法(是由子類(lèi)實(shí)現(xiàn)的方法叠赦,并且在模板方法被調(diào)用)和模板方法(實(shí)現(xiàn)對(duì)基本方法的調(diào)度,完成固定的邏輯)革砸,它使用了簡(jiǎn)單的繼承和覆寫(xiě)機(jī)制除秀。
普通模板方法,示例如下:
public abstract class AbsPopulator {
// 模板方法
public final void dataInitialing() throws Exception {
// 調(diào)用基本方法
doInit();
}
// 基本方法
protected abstract void doInit();
}
//子類(lèi)實(shí)現(xiàn)
public class UserPopulator extends AbsPopulator{
@Override
protected void doInit() {
//初始化用戶表算利,如創(chuàng)建册踩、加載數(shù)據(jù)等
}
}
改造,使用反射增強(qiáng)模板方法模式效拭,使模板方法實(shí)現(xiàn)對(duì)一批固定的規(guī)則的基本方法的調(diào)用暂吉。如下:
public abstract class AbsPopulator {
// 模板方法
public final void dataInitialing() throws Exception {
// 獲得所有的public方法
Method[] methods = getClass().getMethods();
for (Method m : methods) {
// 判斷是否是數(shù)據(jù)初始化方法
if (isInitDataMethod(m)) {
m.invoke(this);
}
}
}
// 判斷是否是數(shù)據(jù)初始化方法,基本方法鑒定器
private boolean isInitDataMethod(Method m) {
return m.getName().startsWith("init")// init開(kāi)始
&& Modifier.isPublic(m.getModifiers())// 公開(kāi)方法
&& m.getReturnType().equals(Void.TYPE)// 返回值是void
&& !m.isVarArgs()// 輸出參數(shù)為空
&& !Modifier.isAbstract(m.getModifiers());// 不能是抽象方法
}
}
//子類(lèi)實(shí)現(xiàn)
public class UserPopulator extends AbsPopulator {
public void initUser() {
/* 初始化用戶表缎患,如創(chuàng)建慕的、加載數(shù)據(jù)等 */
}
public void initPassword() {
/* 初始化密碼 */
}
public void initJobs() {
/* 初始化工作任務(wù) */
}
}
在一般的模板方法模式中,抽象模板(這里是AbsPopulator
類(lèi))需要定義一系列的基本方法挤渔,一般都是protected
訪問(wèn)級(jí)別的肮街,并且是抽象方法,這標(biāo)志著子類(lèi)必須實(shí)現(xiàn)這些基本方法判导,這對(duì)子類(lèi)來(lái)說(shuō)既是一個(gè)約束也是一個(gè)負(fù)擔(dān)嫉父。但是使用了反射后,不需要定義任何抽象方法骡楼,只需定義一個(gè)基本方法鑒別器(例子中isInitDataMethod
)即可加載符合規(guī)則的基本方法熔号。鑒別器在此處的作用是鑒別子類(lèi)方法中哪些是基本方法,模板方法(例子中的dataInitialing
)則根據(jù)基本方法鑒別器返回的結(jié)果通過(guò)反射執(zhí)行相應(yīng)的方法鸟整。
注意:決定使用模板方法模式時(shí)引镊,請(qǐng)嘗試使用反射方式實(shí)現(xiàn),它會(huì)讓你的程序更靈活篮条、更強(qiáng)大弟头。
建議109:不需要太多關(guān)注反射效率★★☆☆☆
反射的效率相對(duì)于正常的代碼執(zhí)行確實(shí)低很多(經(jīng)過(guò)測(cè)試,相差15倍左右)涉茧,但是它是一個(gè)非常有效的運(yùn)行期工具類(lèi)赴恨,只要代碼結(jié)構(gòu)清晰、可讀性好那就先開(kāi)發(fā)起來(lái)伴栓,等到進(jìn)行性能測(cè)試時(shí)證明此處性能確實(shí)有問(wèn)題時(shí)再修改也不遲(一般情況下反射并不是性能的終極殺手伦连,而代碼結(jié)構(gòu)混亂雨饺、可讀性差則很可能會(huì)埋下性能隱患)。
對(duì)于反射效率問(wèn)題,不要做任何的提前優(yōu)化和預(yù)期,這基本上是杞人憂天蒿涎,很少有項(xiàng)目是因?yàn)榉瓷鋯?wèn)題引起系統(tǒng)效率故障的,而且根據(jù)二八原則移斩,80%的性能消耗在20%的代碼上,這20%的代碼才是我們關(guān)注的重點(diǎn)绢馍,不要單單把反射作為重點(diǎn)關(guān)注對(duì)象向瓷。
注意:反射效率低是個(gè)真命題,但因?yàn)檫@一點(diǎn)而不使用它就是個(gè)假命題舰涌。