Java 面試準備

ImportNew 網站的Java面試專題學習筆記html

1. 非可變性和對象引用

String s = " Hello ";
s += " World ";
s.trim( );
System.out.println(s);

輸出爲「 Hello World 」,先後皆有空格。
字符串是不可變對象。
s.trim()雖然生成了一個新的字符串對象,可是卻沒有變量指向這個心生成的對象,s 仍然指向字符串s += " World "。
下圖說明了,生成對象以及垃圾回收過程。
生成對象過程,以及垃圾回收java

可用StringBuilder來構造,由於其底層使用的是字符數組,全部操做都直接在字符數組上直接操做,並且他不是一個線程安全的類,執行速度上,相比於StringBuffer要快。面試

這一點若是深刻理解了String的Interning機制,就更好理解了。
Java程序在編譯時,會將全部肯定下來的,存在雙引號內的字符串,都存入常量池中,該常量池是類文件(.class)的一部分。常量池中存着許多表,其中 Constant_Utf8_info 表中,記錄着會被初始化爲 String 對象的字符串的字面值(iteral)。 在JVM 中,相應的類被加載運行後,常量池對應的映射到 JVM 的運行時常量池(run time constant pool)中。算法

關於String的intern()方法編程

當 intern 方法被調用,若是字符串池中已經擁有一個與該 String 的字符串值相等(即 equals()調用後爲 true)的 String 對象時,那麼池中的那個 String 對象會被返回。不然,池中會增長這個對象,並返回當前這個 String 對象。數組

現代的 JVM 實現裏,考慮到垃圾回收(Garbage Collection)的方便,將 heap 劃分爲三部分: young generation 、 tenured generation(old generation)和 permanent generation( permgen )。字符串池是爲了解決字符串重複的問題,生命週期長,它存在於 permgen 中。安全

所以,對於框架

String str = new String("abc");

JVM會生成兩個字符串,一個是在常量池中,另一個是new在heap堆中。編程語言

2. equals 和 ==

Object s1 = new String("Hello");
Object s2 = new String("Hello");
 
if(s1 == s2) {
  System.out.println("s1 and s2 are ==");
}else if (s1.equals(s2)) {
  System.out.println("s1 and s2 are equals()");
}

輸出結果爲s1 and s2 are equals()
主要考察對equals和==的理解,==比較引用的地址是否相同,equeal比較對象中真正的值。
詳細的過程可見下圖
ide

另外,若是不是用 new關鍵字強制建立字符串對象的話,而是採用==,那麼Java會默認採用字符串池,減小對象的建立。

String對象會建立一個字符串池(a pool of string),若是當前準備新建立的字符串對象的值在這個池子中已經存在,那麼就不會生成新對象,而是複用池中已有的字符串對象。flyweight 模式的精髓就是對象複用。

3. 重載(overloading)和重寫(overriding)

重寫發生在子類繼承父類時,子類覆蓋父類的方法時發生,是在運行時發生。
重載是在同一個類中,同一個方法,不一樣參數時發生,是在編譯期發生。

在Java 5中使用註解@override,來標示方法重寫,若是編譯時發現沒有重寫,則JVM會拋出編譯異常。

4. 迭代和遞歸

可重入方法(re-entrant method)是能夠安全進入的方法,即便同一個方法正在被執行,深刻到同一個線程的調用棧裏面也不會影響這次執行的安全性。一個非可重入方法則不是能夠安全進入的。例如,加入寫文件或者向文件中寫入日誌的方法不是可重入方法時,有可能會毀壞那個文件。

若是一個方法調用了其自身的話,咱們稱之爲遞歸調用。假定棧空間足夠的話,儘管遞歸調用比較難以調試,在Java語言中實現遞歸調用也是徹底可行的。遞歸方法是衆多算法中替代循環的一個不錯選擇。全部的遞歸方法都是可重入的,可是不是全部可重入的方法都是遞歸的。

棧遵照LIFO(Last In First Out)規則,所以遞歸調用方法可以記住「調用者」而且知道此輪執行結束之返回至當初的被調用位置。遞歸利用系統棧來存儲方法調用的返回地址。 Java是一種基於棧設計的編程語言。

循環的方式能夠達到目的,沒必要採用遞歸。可是在某些狀況下采用遞歸方式則代碼會更加簡短易讀。遞歸方法在循環樹結構以及避免醜陋的嵌套循環的狀況下是很是好用的。

常規遞歸方法(亦稱,頭遞歸)在上面演示了,這種方式會增長調用棧的大小。每次遞歸,其入口須要被記錄在棧中。方法返回以前須要給countA(input.substring(1)的結果加一個count。所以,最後須要作的事實際上是加法運算,而非遞歸自己。

在尾遞歸中,最後要作的是遞歸,加法運算在以前就已經完成了。棧調用減小帶來了內存消耗減小而且程序的性能更好。以下代碼

public class TailRecursiveCall {
 
 public int countA(String input) {
 
  // exit condition – recursive calls must have an exit condition
  if (input == null || input.length() == 0) {
   return 0;
  }
 
  return countA(input, 0) ;
 }
 
 public int countA(String input, int count) {
  if (input.length() == 0) {
   return count;
  }
 
  // check first character of the input
  if (input.substring(0, 1).equals("A")) {
   count = count + 1;
  }
 
  // recursive call is the last call as the count is cumulative
  return countA(input.substring(1), count);
 }
 
 public static void main(String[] args) {
  System.out.println(new TailRecursiveCall().countA("AAA rating"));
 }
}

5. 關於ArrayList

  • ArrayList的大小是如何自動增長的?你能分享一下你的代碼嗎?

使用ensureCapacity, 在進行添加元素時,檢查容量是否足夠,不夠的話,就將容量擴大3/2,並將舊數組中的元素使用Arrays.copyOf拷貝到新數組中。

  • 什麼狀況下你會使用ArrayList?何時你會選擇LinkedList?

ArrayList是在訪問的次數遠大於插入和刪除的次數,使用ArrayList,由於ArrayList底層使用數組,訪問的複雜度爲O(1), 可是插入和刪除就得頻繁使用System.arraycopy複製數組。 LinkList主要在訪問次數遠小於插入和刪除的次數時使用,其刪除和插入的複雜度,但訪問元素時幾乎爲O(n)。

  • 當傳遞ArrayList到某個方法中,或者某個方法返回ArrayList,何時要考慮安全隱患?如何修復安全違規這個問題呢?

當array被當作參數傳遞到某個方法中,若是array在沒有被複制的狀況下直接被分配給了成員變量,那麼就可能發生這種狀況,即當原始的數組被調用的方法改變的時候,傳遞到這個方法中的數組也會改變。

將其副本拷貝出來再進行修改。

  • 如何複製某個ArrayList到另外一個ArrayList中去?寫出你的代碼?

使用clone()方法,好比ArrayList newArray = oldArray.clone();

使用ArrayList構造方法,好比:ArrayList myObject = new ArrayList(myTempObject);
使用Collection的copy方法...
  • 注意1和2是淺拷貝(shallow copy),何爲淺拷貝?

淺拷貝就好比像引用類型,而深拷貝就好比值類型。淺拷貝是指源對象與拷貝對象共用一份實體,僅僅是引用的變量不一樣(名稱不一樣)。對其中任何一個對象的改動都會影響另一個對象。舉個例子,一我的一開始叫張三,後來更名叫李四了,但是仍是同一我的,不論是張三缺胳膊少腿仍是李四缺胳膊少腿,都是這我的倒黴。

深拷貝是指源對象與拷貝對象互相獨立,其中任何一個對象的改動都不會對另一個對象形成影響。舉個例子,一我的名叫張三,後來用他克隆(假設法律容許)了另一我的,叫李四,不論是張三缺胳膊少腿仍是李四缺胳膊少腿都不會影響另一我的。比較典型的就是Value(值)對象,如預約義類型Int32,Double,以及結構(struct),枚舉(Enum)等。

還可用序列化技術來進行深拷貝,對象實現序列化接口,而後寫入流,並讀出來

// 將對象寫到流裏
        ByteArrayOutputStream bo = new ByteArrayOutputStream();
        ObjectOutputStream oo = new ObjectOutputStream(bo);
        oo.writeObject(this);
        // 從流裏讀出來
        ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
        ObjectInputStream oi = new ObjectInputStream(bi);
        return (oi.readObject());

可是串行化卻很耗時,在一些框架中,咱們即可以感覺到,它們每每將對象進行串行化後進行傳遞,耗時較多。

  • 在索引中ArrayList的增長或者刪除某個對象的運行過程?效率很低嗎?解釋一下爲何?

頻繁插入和刪除,會頻繁調用System.arrayCopy....效率低

參考地址
-[1] http://www.importnew.com/2228...
-[2] http://www.importnew.com/2223...
-[3] http://www.importnew.com/2217...
-[4] http://www.importnew.com/2329...
-[5] http://www.importnew.com/9928...
-[6] http://www.cnblogs.com/shuaiw...
-[7] http://blog.csdn.net/biaobiao...

相關文章
相關標籤/搜索