經常聽到這樣的對話:"你會Weblogic, Oracle, xml, Kylix, 等等嗎?不會?你好差。這么先進的技術怎么能不會?"往往我們也能在看到很多簡歷,自稱精通某某,某某某技術, 乍一看下來,簡直是絕頂高手,精通了所有流行的先進技術。但是我經常想,就算是不吃飯不睡覺也不可能在短短時間里精通那么多范圍極廣,博大精深的技術啊。而我自己,卻經常在實際工作中碰到一些問題,讓我不得不想起基礎知識的重要性。我在這里不是要打擊大家學習先進技術的熱情,而是為了強調一下基礎知識的重要。比如,有很多的java程序員在使用JBuilder, WebLogic, WebSphere, SilverStream,寫普通的java程序或者寫j2ee, corba結構的程序。在但是,在把握先進技術的同時,我們也要注重一下基礎的修煉,免得不斷出現本可以避免的錯誤。我將陸續寫一系列的文章,關于java編程常見問題。這些,都是我在日常工作中積累下來的一些筆記,不成體系(我盡量將它們按照范圍不同組織一下), 嚴格的說,不能叫做文章吧。大家隨便看看。呵呵。 (內容) 先來看一個常見的錯誤。 public boolean testAns(String ans, int n){ boolean rslt; if( ans.equalsIgnoreCase("YES") && n > 5) rslt = true; return rslt; } 程序邏輯方面并沒有問題,但是編譯的時候會出現錯誤提示 : variable rslt might not have been initialized return rslt; ^ 這是因為,當if條件為false的時候,rslt可能會沒有被賦予初值,而return的時候則會出錯。java編譯器很聰明的檢查了這一錯誤并在編譯的時候給予了提示。這需要你在聲明rslt的時候或者在返回rslt之前給它賦值。 比如: public boolean testAns(String ans, int n){ boolean rslt = false; if( ans.equalsIgnoreCase("YES") && n > 5) rslt = true; return rslt; } 相關的問題還有: public boolean testAns(String ans, int n){ boolean rslt = true; while(false){rslt = false;} for(;false;){rslt = false;} if(false) {rslt = false;} return rslt; } 則Java編譯器會提示 unreachable statement while(false){rslt = false;} ^ unreachable statement for(;false;){rslt = false;}[/code] ^ 但是if(false)這一段則沒有錯誤提示,編譯通過。這也是要注重的一點。 第二個例子:class Object 中有一個方法equals() public boolean Object.equals(Object) 它檢查object reference是否相同,也就是說是否指向同一個對象。假如是,則返回true, 否則返回false. 而每一個繼續class Object的類都會override這個方法。比如在Long, Integer等class中,equals比較該Ojbect是否相應的是Long, Integer類型。假如類型相同,值比較所包裹的值是否相同。假如相同,則返回true, 否則返回false.要注重的是,返回false并不說明所包裹的值不相同,也可能是類型不同。比如下面代碼:
Long l = new Long(7); Integer j = new Integer(7); if(l.equals(j)) System.out.else System.out.println("Not Equal"); 編譯成功,但是輸出為Not Equal, 這就是因為類型不同, 不是同為Integer或者 同為Long.
再看一下使用instanceof要注重的問題. instanceof是判定一個對象的引用(reference) 是否某一類型。比如 Integer i = new Integer(0); System.out.println( i instanceof Integer); 返回為true,因為i是一個Integer的對象的引用。 Integer i = new Integer(0); System.out.println( i instanceof Long); 則返回為false, 因為i不是一個Long的對象的引用。 但是, Integer i = null; System.out.println( i instanceof Integer); 返回值為false. 這是因為i的值為null, null不是任何對象的引用。這是需要注重的。
第四個問題,是在郵件列表,news groups中提到次數比較多的一個問題,也是很多初學java編程的人經常碰到的一個問題。以下這段代碼,編譯會出現錯誤。 [code]byte x = 100; switch(x) { case 100: case 200: case 300: } 編譯器提示 a1.java:6: possible loss of precision found : int required: byte case 200: ^ a1.java:7: possible loss of precision found : int required: byte case 300: ^ 2 errors
因為x為byte類型,但是300超過了byte類型的最大值127, 所以出現了錯誤。這段代碼相當于是 if(x == 100) ... else if(x == 200) ... else if(x == 300) ... 這樣子看錯誤原因就比較明顯了。 類似的我們還有這樣的代碼: short x = 200; switch(x) { case 70000: case 10: case 1: } 編譯也會出錯,提示case 7000:這一行類型不匹配。
關于primitive類型的賦值問題,還有以下兩個需要注重的問題: 這段代碼 byte b = 1; short s = b; 不會出錯。因為,byte為8 bits, 而short為16 bits,將byte類型的數值賦予給short類型的變量不會引起數值精度問題。但是 short s = 1; byte b = s; 則不能正確編譯。因為這樣賦值可能導致s所含數值的高8 bit被舍棄,因而數值不正確。這樣需要我們在寫程序的時候指定 byte b = (byte)s;
以便通知編譯器,"嘿, 假如有精度的損失,那是我自愿的,你不必擔心!"這樣編譯就不會出錯了。同樣的, short s = 1; byte b = 1; b = b + s; 不能正確編譯。需要我們在寫程序的時候指定 b = (byte)(b + s); 這些錯誤很多人現在都會避免了。但是這樣子的代碼是否會編譯錯誤呢? short s = 1; byte b = 1; b += s; 照以上的解釋,你一定認為這段代碼不能正確編譯。但是假如你實際編譯一下則會發現,它編譯通過了!為什么呢?這是因為, +=, -=, *=, /=, %=這類操作符號比較非凡,對于編譯器來說,他們相當于 b += s; -------> b = (byte)(b + s); 呵呵,有意思吧?
inner class的問題,更多的人感到迷惑。 比如,inner class是否能夠有static的變量? 一般的說法是,static inner class能夠有static的變量,而non-static inner class則不能有static的變量。但是,假如這樣: class outer { class inner { static final int i = 999; } } 編譯通過。而你不能這么寫: static final int i; 就是說,一定要在聲明它的時候賦予初值, 因為i是final的,它的值不能再被改變。 關于inner class, 還有一個代碼也很能說明一些問題。
public class TestClass { public TestClass(int i) { }
public void m1() { TestClass al = new TestClass(10) { public void actionPerformed(ActionEvent e) { } }; } } 這說明了一下四個問題: 1. 類中的方法也可以有inner classes.(但是他們不能有static inner classes). 2. Inner class 能夠繼續包含它的外部class. 3. 匿名 inner class 可以繼續一個 class 或者 implements 一個 interface。 4. 匿名 inner class 可以有初始化參數。(假如這個class繼續的基類有相應的 constrUCtor 的話。)
再來看看動態聯編 ( dyanamic link )的問題。 考慮這段代碼:
class base{ public void whoami() { System.out.println("Base"); } } class derived extends base{ public void whoami() { System.out.println("Derived"); } }
class test{ public static void main(String[] args) { base b = new base(); derived d = new derived(); b.whoami(); d.whoami(); base b2 = d; b2.whoami(); } } 當然大家很清楚,b.whoami()打印Base, 而d.whoami()打印Derived.但是,b2.whoami()打印什么呢?也就是說,b2.whoami()將調用那一個方法呢?是基類的還是派生類的?運行以后看到,調用的是派生類的方法。這是因為java是在運行過程中采用了動態聯編的方法,在運行時刻來決定該reference指向的什么類的對象,從而決定調用哪一個類的方法,而不是根據reference的類型來決定調用哪一個類的方法。從而可以使我們通過這一機制來完成多樣化的程序。打印的結果將是Derived。 再來看另外一個類似的例子: class base{ int i = 100; public void print() { System.out.println(i); } } class derived extends base{ int i = 999; public void print() { System.out.println(i); } }
class test{ public static void main(String[] args) { base b = new base();
關于動態聯編,還有2個要注重的問題: class base{ } class derived extends base{ public void print() { System.out.println("Derived"); } }
class test{ public static void main(String[] args) { base b = new base(); derived d = new derived(); d.print(); base b2 = d; b2.print();//出錯 } } 為什么呢?因為在編譯過程中,編譯器會檢查基類中是否有print()方法,假如沒有,則會報錯。注重:動態聯編出現在運行過程中,而不是編譯過程中。
class base{ int i = 100; public void print() { System.out.println(i); } } class derived extends base{ int i = 999; public void print() { System.out.println(i); } }
class test{ public static void main(String[] args) { base b = new base(); derived d = new derived(); b.print(); System.out.print(b.i); d.print(); System.out.print(d.i); base b2 = d; b2.print(); System.out.print(b2.i); derived d2 = b;//出錯 d2.print();//出錯 System.out.print(d2.i);//出錯 } } 這是因為,在編譯過程中,derived類型的reference可以賦給base類型的reference. 而base類型的reference則不可以賦給derived類型的reference.假如要這么做, 則需要在賦值的過程中指定 derived d2 = (derived)b; 編譯才能通過。這和primitive types的賦值是一樣的道理。而編譯完成后在運行時刻還需要做進一步的檢驗,假如類型不能匹配,則會拋出 ClassCastException. Exception in thread "main" java.lang.ClassCastException: base at test.main(a2.java:12)