標籤:
一、筆試題目:
1. 簡述類與對象的區別,Java 虛函數的作用。
類是對象的抽象,對象是類的具體執行個體。
類是抽象的,不佔用記憶體,而對象是具體的,佔有記憶體空間。
java中沒有虛函數的概念,普通函數就相當於C++中的虛函數,不過可以在函數前加final使函數不能被重寫。虛函數的作用是允許在衍生類別中重新定義與基類同名的函數,是多態性的一種體現。
2. Database table 寫SQL語句去掉重複的記錄,保留其中ID最小的一條。
delete from tablename where id not in (select min(id) from tablename group by col1,col2,...)
3. 分析兩個for迴圈的優缺點:
(1)
1 for(i=0; i<N; i++)2 {3 if(condition)4 DoSomething();5 else6 DoOtherthing();7 }
(2)
1 if(condition) 2 { 3 for(i=0; i<N; i++) 4 DoSomething(); 5 } 6 else 7 { 8 for(i=0; i<N; i++) 9 DoOtherthing();10 }
程式(1)(前者):
優點:程式簡潔
條件判斷出現在for裡面,意味著,即使我在DoSomething()或DoOtherthing()這2個函數中改變了condition的值,for迴圈也能正確執行我的意圖,因為它在每次迴圈中都會重新檢測conditon的值並針對condition的值做不同動作,所謂以不變應萬變,這是難能可貴的.
缺點:多執行了N-1次邏輯判斷,並且打斷了迴圈“流水線”作業,使得編譯器不能對迴圈進行最佳化處理,降低了效率。
如果condition一直未曾改變,我們可憐的if必須每次迴圈都判斷一下condition的真假.犧牲了運行時效率.
程式(2)(後者):
優點:迴圈的效率高。只進行一次判斷,運行時效率高.適合那種condition的值不會改變的情況.
缺點:由於只在一開始進行一次判斷,所以失去了改變condition的值的機會,也就是說,即使我在DoSomething()中改變了condition的值為false,這個程式也不會改變它的判斷,它依然執行著DoSomething()的迴圈.我們不能隨時更換我們需要進行的動作。這是犧牲了彈性。
N較大時,建議採用後面這種寫法,由於前者老要進行邏輯判斷,打斷了迴圈“流水線”作業,使得編譯器不能對迴圈進行最佳化處理,降低了效率。
4. 1-100的自然數,放到長度為99的數組a[]中,設計一個好的方法,能夠方便地找出未被放入的數。
1 int total = 0;2 for (int i=0; i<99; i++) {3 total += a[i];4 }5 System.out.println("未被放入的數是:"+(5050-total));
其他方法:
1).先將數組中的99個數字進行排序,然後將1-100個數字依次在數組中折半尋找,可以借用java中已有的
Arrays.binarySearch
2).其實如果數組可以換成集合的話,可以直接判斷集合中是否contain這1-100個數字就可以了
3).用HashMap實現,把數字1-100都放入HashMap中,key為數字,value隨便,比如為1,for迴圈遍曆長度為99的數組,對HashMap移除當前數位key,最後剩下的1個數即是所求。
5. 用遞迴實現100!。
1 import java.math.BigInteger; 2 3 public class factorial { 4 5 public static BigInteger callFactorial(int n) { 6 if (n < 1) 7 throw new IllegalArgumentException(); 8 9 if (n == 1)10 return BigInteger.ONE;11 12 return callFactorial(n - 1).multiply(BigInteger.valueOf(n));13 }14 15 public static void main(String[] args) {16 System.out.println(callFactorial(100));17 }18 }
6. 設計模式:Singleton、Factory(代碼)
【轉】Java:單例模式的七種寫法
第一種(懶漢,線程不安全):
1 public class Singleton { 2 private static Singleton instance; 3 4 public static Singleton getInstance() { 5 if (instance == null) { 6 instance = new Singleton(); 7 } 8 return instance; 9 } 10 }
這種寫法lazy loading很明顯,但是致命的是在多線程不能正常工作。
第二種(懶漢,安全執行緒):
1 public class Singleton { 2 private static Singleton instance; 3 4 public static synchronized Singleton getInstance() { 5 if (instance == null) { 6 instance = new Singleton(); 7 } 8 return instance; 9 } 10 }
這種寫法能夠在多線程中很好的工作,而且看起來它也具備很好的lazy loading,但是,遺憾的是,效率很低,99%情況下不需要同步。
第三種(餓漢):
1 public class Singleton { 2 private static Singleton instance = new Singleton(); 3 4 public static Singleton getInstance() { 5 return instance; 6 } 7 }
這種方式基於classloder機制避免了多線程的同步問題,不過,instance在類裝載時就執行個體化,雖然導致類裝載的原因有很多種,在單例模式中大多數都是調用getInstance方法, 但是也不能確定有其他的方式(或者其他的靜態方法)導致類裝載,這時候初始化instance顯然沒有達到lazy loading的效果。
第四種(餓漢,變種):
1 public class Singleton { 2 private Singleton instance = null; 3 static { 4 instance = new Singleton(); 5 } 6 7 public static Singleton getInstance() { 8 return this.instance; 9 } 10 }
表面上看起來差別挺大,其實更第三種方式差不多,都是在類初始化即執行個體化instance。
第五種(靜態內部類):
1 public class Singleton { 2 private static class SingletonHolder { 3 private static final Singleton INSTANCE = new Singleton(); 4 } 5 6 public static final Singleton getInstance() { 7 return SingletonHolder.INSTANCE; 8 } 9 }
這種方式同樣利用了classloder的機制來保證初始化instance時只有一個線程,它跟第三種和第四種方式不同的是(很細微的差別):第三種和第四種方式是只要Singleton類被裝載了,那麼instance就會被執行個體化(沒有達到lazy loading效果),而這種方式是Singleton類被裝載了,instance不一定被初始化。因為SingletonHolder類沒有被主動使用,只有顯示通過調用getInstance方法時,才會顯示裝載SingletonHolder類,從而執行個體化instance。想象一下,如果執行個體化instance很消耗資源,我想讓他消極式載入,另外一方面,我不希望在Singleton類載入時就執行個體化,因為我不能確保Singleton類還可能在其他的地方被主動使用從而被載入,那麼這個時候執行個體化instance顯然是不合適的。這個時候,這種方式相比第三和第四種方式就顯得很合理。
第六種(枚舉):
1 public enum Singleton { 2 INSTANCE; 3 public void whateverMethod() { 4 } 5 }
這種方式是Effective Java作者Josh Bloch 提倡的方式,它不僅能避免多線程同步問題,而且還能防止還原序列化重新建立新的對象,可謂是很堅強的壁壘啊,不過,個人認為由於1.5中才加入enum特性,用這種方式寫不免讓人感覺生疏,在實際工作中,我也很少看見有人這麼寫過。
第七種(雙重校正鎖):
1 public class Singleton { 2 private volatile static Singleton singleton; 3 4 public static Singleton getSingleton() { 5 if (singleton == null) { 6 synchronized (Singleton.class) { 7 if (singleton == null) { 8 singleton = new Singleton(); 9 } 10 } 11 } 12 return singleton; 13 } 14 }
這個是第二種方式的升級版,俗稱雙重檢查鎖定,詳細介紹請查看:http://www.ibm.com/developerworks/cn/java/j-dcl.html
在JDK1.5之後,雙重檢查鎖定才能夠正常達到單例效果。
總結
有兩個問題需要注意:
1.如果單例由不同的類裝載器裝入,那便有可能存在多個單例類的執行個體。假定不是遠端存取,例如一些servlet容器對每個servlet使用完全不同的類裝載器,這樣的話如果有兩個servlet訪問一個單例類,它們就都會有各自的執行個體。
2.如果Singleton實現了java.io.Serializable介面,那麼這個類的執行個體就可能被序列化和複原。不管怎樣,如果你序列化一個單例類的對象,接下來複原多個那個對象,那你就會有多個單例類的執行個體。
對第一個問題修複的辦法是:
1 private static Class getClass(String classname) 2 throws ClassNotFoundException { 3 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 4 5 if(classLoader == null) 6 classLoader = Singleton.class.getClassLoader(); 7 8 return (classLoader.loadClass(classname)); 9 } 10 }
對第二個問題修複的辦法是:
1 public class Singleton implements java.io.Serializable { 2 public static Singleton INSTANCE = new Singleton(); 3 4 protected Singleton() { 5 6 } 7 private Object readResolve() { 8 return INSTANCE; 9 } 10 }
對我來說,我比較喜歡第三種和第五種方式,簡單易懂,而且在JVM層實現了安全執行緒(如果不是多個類載入器環境),一般的情況下,我會使用第三種方式,只有在要明確實現lazy loading效果時才會使用第五種方式,另外,如果涉及到還原序列化建立對象時我會試著使用枚舉的方式來實現單例,不過,我一直會保證我的程式是安全執行緒的,而且我永遠不會使用第一種和第二種方式,如果有其他特殊的需求,我可能會使用第七種方式,畢竟,JDK1.5已經沒有雙重檢查鎖定的問題了。
Factory模式:待續。
Java工程師筆試面試題集 一