2020-08-11

Java面試筆試題大彙總(最全+詳細答案)css

Java-碼農進階之路 2018-05-28 09:08:36 165699 收藏 555
分類專欄: Java面試題 文章標籤: Java面試題
聲明:有人說, 有些面試題很變態,我的認爲實際上是由於咱們基礎不紮實或者沒有深刻。本篇文章來自一位很資深的前輩對於最近java面試題目所作的總結概括,有170道題目 ,知識面很廣 ,並且這位前輩對於每一個題都本身測試給出了答案 ,若是你對某個題有疑問或者不明白,能夠電腦端登陸把題目複製下來而後發表評論,你們一塊兒探討,也能夠電腦端登陸後關注我給我發私信,咱們一塊兒進步!html

如下內容來自這位前輩前端

2013年年末的時候,我看到了網上流傳的一個叫作《Java面試題大全》的東西,認真的閱讀了之後發現裏面的不少題目是重複且沒有價值的題目,還有很多的參考答案也是錯誤的,因而我花了半個月時間對這個所謂的《Java面試大全》進行了全面的修訂並從新發布在個人CSDN博客。在修訂的過程當中,參照了當時JDK最新版本(Java 7)給出了題目的答案和相關代碼,去掉了EJB 2.x、JSF等無用內容或過期內容,補充了數據結構和算法、大型網站技術架構、設計模式、UML、Spring MVC等內容並對不少知識點進行了深刻的剖析,例如hashCode方法的設計、垃圾收集、併發編程、數據庫事務等。當時我甚至但願把面試中常常出現的操做系統、數據庫、軟件測試等內容也補充進去,可是因爲各類緣由,最終只整理出了150道面試題。讓我欣慰的是,這150道題仍是幫助到了不少人,並且在我CSDN博客上的總訪問量超過了5萬次,最終還被不少網站和我的以原創的方式轉載了。最近一年內,用百度搜索"Java面試"我寫的這些東西基本上都排在搜索結果的前5名,這讓我以爲"亞歷山大",由於我寫的這些東西一旦不許確就可能誤導不少人。2014年的時候我又整理了30道題,但願把以前遺漏的面試題和知識點補充上去,可是仍然感受掛一漏萬,並且Java 8問世後不少新的東西又須要去總結和整理。爲此,我不止一次的修改了以前的180題,修改到本身已經感受有些疲憊或者厭煩了。2014年至今,本身帶的學生又有不少走上了Java程序員、Java工程師的工做崗位,他們的面試經驗也還沒來得及跟你們分享,冥冥之中彷佛有一股力量在刺激我要從新寫一篇《Java面試題全集》,因而這篇文章就誕生了。請不要責備我把那些出現過的內容又寫了一次,由於每次寫東西就算是重複的內容,我也須要對編程語言和相關技術進行從新思考,不只字斟句酌更是力求至臻完美,因此請相信我分享的必定是更新的、更好的、更有益的東西,這些內容也訴說着一個職業程序員和培訓師的思想、精神和情感。java

一、面向對象的特徵有哪些方面?
答:面向對象的特徵主要有如下幾個方面:mysql

  • 抽象:抽象是將一類對象的共同特徵總結出來構造類的過程,包括數據抽象和行爲抽象兩方面。抽象只關注對象有哪些屬性和行爲,並不關注這些行爲的細節是什麼。
  • 繼承:繼承是從已有類獲得繼承信息建立新類的過程。提供繼承信息的類被稱爲父類(超類、基類);獲得繼承信息的類被稱爲子類(派生類)。繼承讓變化中的軟件系統有了必定的延續性,同時繼承也是封裝程序中可變因素的重要手段(若是不能理解請閱讀閻宏博士的《Java與模式》或《設計模式精解》中關於橋樑模式的部分)。
  • 封裝:一般認爲封裝是把數據和操做數據的方法綁定起來,對數據的訪問只能經過已定義的接口。面向對象的本質就是將現實世界描繪成一系列徹底自治、封閉的對象。咱們在類中編寫的方法就是對實現細節的一種封裝;咱們編寫一個類就是對數據和數據操做的封裝。能夠說,封裝就是隱藏一切可隱藏的東西,只向外界提供最簡單的編程接口(能夠想一想普通洗衣機和全自動洗衣機的差異,明顯全自動洗衣機封裝更好所以操做起來更簡單;咱們如今使用的智能手機也是封裝得足夠好的,由於幾個按鍵就搞定了全部的事情)。
  • 多態性:多態性是指容許不一樣子類型的對象對同一消息做出不一樣的響應。簡單的說就是用一樣的對象引用調用一樣的方法可是作了不一樣的事情。多態性分爲編譯時的多態性和運行時的多態性。若是將對象的方法視爲對象向外界提供的服務,那麼運行時的多態性能夠解釋爲:當A系統訪問B系統提供的服務時,B系統有多種提供服務的方式,但一切對A系統來講都是透明的(就像電動剃鬚刀是A系統,它的供電系統是B系統,B系統能夠使用電池供電或者用交流電,甚至還有多是太陽能,A系統只會經過B類對象調用供電的方法,但並不知道供電系統的底層實現是什麼,究竟經過何種方式得到了動力)。方法重載(overload)實現的是編譯時的多態性(也稱爲前綁定),而方法重寫(override)實現的是運行時的多態性(也稱爲後綁定)。運行時的多態是面向對象最精髓的東西,要實現多態須要作兩件事:1). 方法重寫(子類繼承父類並重寫父類中已有的或抽象的方法);2). 對象造型(用父類型引用引用子類型對象,這樣一樣的引用調用一樣的方法就會根據子類對象的不一樣而表現出不一樣的行爲)。

二、訪問修飾符public,private,protected,以及不寫(默認)時的區別?
答:程序員

修飾符 當前類 同 包 子 類 其餘包
public √ √ √ √
protected √ √ √ ×
default √ √ × ×
private √ × × ×
類的成員不寫訪問修飾時默認爲default。默認對於同一個包中的其餘類至關於公開(public),對於不是同一個包中的其餘類至關於私有(private)。受保護(protected)對子類至關於公開,對不是同一包中的沒有父子關係的類至關於私有。Java中,外部類的修飾符只能是public或默認,類的成員(包括內部類)的修飾符能夠是以上四種。web

三、String 是最基本的數據類型嗎?
答:不是。Java中的基本數據類型只有8個:byte、short、int、long、float、double、char、boolean;除了基本類型(primitive type)和枚舉類型(enumeration type),剩下的都是引用類型(reference type)。面試

四、float f=3.4;是否正確?
答:不正確。3.4是雙精度數,將雙精度型(double)賦值給浮點型(float)屬於下轉型(down-casting,也稱爲窄化)會形成精度損失,所以須要強制類型轉換float f =(float)3.4; 或者寫成float f =3.4F;。正則表達式

五、short s1 = 1; s1 = s1 + 1;有錯嗎?short s1 = 1; s1 += 1;有錯嗎?
答:對於short s1 = 1; s1 = s1 + 1;因爲1是int類型,所以s1+1運算結果也是int 型,須要強制轉換類型才能賦值給short型。而short s1 = 1; s1 += 1;能夠正確編譯,由於s1+= 1;至關於s1 = (short)(s1 + 1);其中有隱含的強制類型轉換。算法

六、Java有沒有goto?
答:goto 是Java中的保留字,在目前版本的Java中沒有使用。(根據James Gosling(Java之父)編寫的《The Java Programming Language》一書的附錄中給出了一個Java關鍵字列表,其中有goto和const,可是這兩個是目前沒法使用的關鍵字,所以有些地方將其稱之爲保留字,其實保留字這個詞應該有更普遍的意義,由於熟悉C語言的程序員都知道,在系統類庫中使用過的有特殊意義的單詞或單詞的組合都被視爲保留字)

七、int和Integer有什麼區別?
答:Java是一個近乎純潔的面向對象編程語言,可是爲了編程的方便仍是引入了基本數據類型,可是爲了可以將這些基本數據類型當成對象操做,Java爲每個基本數據類型都引入了對應的包裝類型(wrapper class),int的包裝類就是Integer,從Java 5開始引入了自動裝箱/拆箱機制,使得兩者能夠相互轉換。
Java 爲每一個原始類型提供了包裝類型:

  • 原始類型: boolean,char,byte,short,int,long,float,double
  • 包裝類型:Boolean,Character,Byte,Short,Integer,Long,Float,Double

[java] view plain copy
class AutoUnboxingTest {

public static void main(String[] args) {  
    Integer a = new Integer(3);  
    Integer b = 3;                  // 將3自動裝箱成Integer類型  
    int c = 3;  
    System.out.println(a == b);     // false 兩個引用沒有引用同一對象  
    System.out.println(a == c);     // true a自動拆箱成int類型再和c比較  
}

}

最近還遇到一個面試題,也是和自動裝箱和拆箱有點關係的,代碼以下所示:

public class Test03 {

public static void main(String[] args) {
    Integer f1 = 100, f2 = 100, f3 = 150, f4 = 150;

    System.out.println(f1 == f2);
    System.out.println(f3 == f4);
}

}

若是不明就裏很容易認爲兩個輸出要麼都是true要麼都是false。首先須要注意的是f一、f二、f三、f4四個變量都是Integer對象引用,因此下面的==運算比較的不是值而是引用。裝箱的本質是什麼呢?當咱們給一個Integer對象賦一個int值的時候,會調用Integer類的靜態方法valueOf,若是看看valueOf的源代碼就知道發生了什麼。

public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

IntegerCache是Integer的內部類,其代碼以下所示:

private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];

static {
        // high value may be configured by property
        int h = 127;
        String integerCacheHighPropValue =
            sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        if (integerCacheHighPropValue != null) {
            try {
                int i = parseInt(integerCacheHighPropValue);
                i = Math.max(i, 127);
                // Maximum array size is Integer.MAX_VALUE
                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
            } catch( NumberFormatException nfe) {
                // If the property cannot be parsed into an int, ignore it.
            }
        }
        high = h;

        cache = new Integer[(high - low) + 1];
        int j = low;
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);

        // range [-128, 127] must be interned (JLS7 5.1.7)
        assert IntegerCache.high >= 127;
    }

    private IntegerCache() {}
}

簡單的說,若是整型字面量的值在-128到127之間,那麼不會new新的Integer對象,而是直接引用常量池中的Integer對象,因此上面的面試題中f1f2的結果是true,而f3f4的結果是false。

提醒:越是貌似簡單的面試題其中的玄機就越多,須要面試者有至關深厚的功力。

八、&和&&的區別?
答:&運算符有兩種用法:(1)按位與;(2)邏輯與。&&運算符是短路與運算。邏輯與跟短路與的差異是很是巨大的,雖然兩者都要求運算符左右兩端的布爾值都是true整個表達式的值纔是true。&&之因此稱爲短路運算是由於,若是&&左邊的表達式的值是false,右邊的表達式會被直接短路掉,不會進行運算。不少時候咱們可能都須要用&&而不是&,例如在驗證用戶登陸時斷定用戶名不是null並且不是空字符串,應當寫爲:username != null &&!username.equals(""),兩者的順序不能交換,更不能用&運算符,由於第一個條件若是不成立,根本不能進行字符串的equals比較,不然會產生NullPointerException異常。注意:邏輯或運算符(|)和短路或運算符(||)的差異也是如此。

補充:若是你熟悉JavaScript,那你可能更能感覺到短路運算的強大,想成爲JavaScript的高手就先從玩轉短路運算開始吧。

九、解釋內存中的棧(stack)、堆(heap)和靜態區(static area)的用法。
答:一般咱們定義一個基本數據類型的變量,一個對象的引用,還有就是函數調用的現場保存都使用內存中的棧空間;而經過new關鍵字和構造器建立的對象放在堆空間;程序中的字面量(literal)如直接書寫的100、"hello"和常量都是放在靜態區中。棧空間操做起來最快可是棧很小,一般大量的對象都是放在堆空間,理論上整個內存沒有被其餘進程使用的空間甚至硬盤上的虛擬內存均可以被當成堆空間來使用。

String str = new String(「hello」);

上面的語句中變量str放在棧上,用new建立出來的字符串對象放在堆上,而"hello"這個字面量放在靜態區。

補充:較新版本的Java(從Java 6的某個更新開始)中使用了一項叫"逃逸分析"的技術,能夠將一些局部對象放在棧上以提高對象的操做性能。

十、Math.round(11.5) 等於多少?Math.round(-11.5)等於多少?
答:Math.round(11.5)的返回值是12,Math.round(-11.5)的返回值是-11。四捨五入的原理是在參數上加0.5而後進行下取整。

十一、swtich 是否能做用在byte 上,是否能做用在long 上,是否能做用在String上?
答:在Java 5之前,switch(expr)中,expr只能是byte、short、char、int。從Java 5開始,Java中引入了枚舉類型,expr也能夠是enum類型,從Java 7開始,expr還能夠是字符串(String),可是長整型(long)在目前全部的版本中都是不能夠的。

十二、用最有效率的方法計算2乘以8?
答: 2 << 3(左移3位至關於乘以2的3次方,右移3位至關於除以2的3次方)。

補充:咱們爲編寫的類重寫hashCode方法時,可能會看到以下所示的代碼,其實咱們不太理解爲何要使用這樣的乘法運算來產生哈希碼(散列碼),並且爲何這個數是個素數,爲何一般選擇31這個數?前兩個問題的答案你能夠本身百度一下,選擇31是由於能夠用移位和減法運算來代替乘法,從而獲得更好的性能。說到這裏你可能已經想到了:31 * num 等價於(num << 5) - num,左移5位至關於乘以2的5次方再減去自身就至關於乘以31,如今的VM都能自動完成這個優化。

public class PhoneNumber {
private int areaCode;
private String prefix;
private String lineNumber;

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + areaCode;
    result = prime * result
            + ((lineNumber == null) ? 0 : lineNumber.hashCode());
    result = prime * result + ((prefix == null) ? 0 : prefix.hashCode());
    return result;
}

@Override
public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;
    PhoneNumber other = (PhoneNumber) obj;
    if (areaCode != other.areaCode)
        return false;
    if (lineNumber == null) {
        if (other.lineNumber != null)
            return false;
    } else if (!lineNumber.equals(other.lineNumber))
        return false;
    if (prefix == null) {
        if (other.prefix != null)
            return false;
    } else if (!prefix.equals(other.prefix))
        return false;
    return true;
}

}

1三、數組有沒有length()方法?String有沒有length()方法?
答:數組沒有length()方法,有length 的屬性。String 有length()方法。JavaScript中,得到字符串的長度是經過length屬性獲得的,這一點容易和Java混淆。

1四、在Java中,如何跳出當前的多重嵌套循環?
答:在最外層循環前加一個標記如A,而後用break A;能夠跳出多重循環。(Java中支持帶標籤的break和continue語句,做用有點相似於C和C++中的goto語句,可是就像要避免使用goto同樣,應該避免使用帶標籤的break和continue,由於它不會讓你的程序變得更優雅,不少時候甚至有相反的做用,因此這種語法其實不知道更好)

1五、構造器(constructor)是否可被重寫(override)?
答:構造器不能被繼承,所以不能被重寫,但能夠被重載。

1六、兩個對象值相同(x.equals(y) == true),但卻可有不一樣的hash code,這句話對不對?
答:不對,若是兩個對象x和y知足x.equals(y) == true,它們的哈希碼(hash code)應當相同。Java對於eqauls方法和hashCode方法是這樣規定的:(1)若是兩個對象相同(equals方法返回true),那麼它們的hashCode值必定要相同;(2)若是兩個對象的hashCode相同,它們並不必定相同。固然,你未必要按照要求去作,可是若是你違背了上述原則就會發如今使用容器時,相同的對象能夠出如今Set集合中,同時增長新元素的效率會大大降低(對於使用哈希存儲的系統,若是哈希碼頻繁的衝突將會形成存取性能急劇降低)。

補充:關於equals和hashCode方法,不少Java程序都知道,但不少人也就是僅僅知道而已,在Joshua Bloch的大做《Effective Java》(不少軟件公司,《Effective Java》、《Java編程思想》以及《重構:改善既有代碼質量》是Java程序員必看書籍,若是你還沒看過,那就趕忙去亞馬遜買一本吧)中是這樣介紹equals方法的:首先equals方法必須知足自反性(x.equals(x)必須返回true)、對稱性(x.equals(y)返回true時,y.equals(x)也必須返回true)、傳遞性(x.equals(y)和y.equals(z)都返回true時,x.equals(z)也必須返回true)和一致性(當x和y引用的對象信息沒有被修改時,屢次調用x.equals(y)應該獲得一樣的返回值),並且對於任何非null值的引用x,x.equals(null)必須返回false。實現高質量的equals方法的訣竅包括:1. 使用==操做符檢查"參數是否爲這個對象的引用";2. 使用instanceof操做符檢查"參數是否爲正確的類型";3. 對於類中的關鍵屬性,檢查參數傳入對象的屬性是否與之相匹配;4. 編寫完equals方法後,問本身它是否知足對稱性、傳遞性、一致性;5. 重寫equals時老是要重寫hashCode;6. 不要將equals方法參數中的Object對象替換爲其餘的類型,在重寫時不要忘掉@Override註解。

1七、是否能夠繼承String類?
答:String 類是final類,不能夠被繼承。

補充:繼承String自己就是一個錯誤的行爲,對String類型最好的重用方式是關聯關係(Has-A)和依賴關係(Use-A)而不是繼承關係(Is-A)。

1八、當一個對象被看成參數傳遞到一個方法後,此方法可改變這個對象的屬性,並可返回變化後的結果,那麼這裏究竟是值傳遞仍是引用傳遞?
答:是值傳遞。Java語言的方法調用只支持參數的值傳遞。當一個對象實例做爲一個參數被傳遞到方法中時,參數的值就是對該對象的引用。對象的屬性能夠在被調用過程當中被改變,但對對象引用的改變是不會影響到調用者的。C++和C#中能夠經過傳引用或傳輸出參數來改變傳入的參數的值。在C#中能夠編寫以下所示的代碼,可是在Java中卻作不到。

using System;

namespace CS01 {

class Program {
    public static void swap(ref int x, ref int y) {
        int temp = x;
        x = y;
        y = temp;
    }

    public static void Main (string[] args) {
        int a = 5, b = 10;
        swap (ref a, ref b);
        // a = 10, b = 5;
        Console.WriteLine ("a = {0}, b = {1}", a, b);
    }
}

}

說明:Java中沒有傳引用實在是很是的不方便,這一點在Java 8中仍然沒有獲得改進,正是如此在Java編寫的代碼中才會出現大量的Wrapper類(將須要經過方法調用修改的引用置於一個Wrapper類中,再將Wrapper對象傳入方法),這樣的作法只會讓代碼變得臃腫,尤爲是讓從C和C++轉型爲Java程序員的開發者沒法容忍。

1九、String和StringBuilder、StringBuffer的區別?
答:Java平臺提供了兩種類型的字符串:String和StringBuffer/StringBuilder,它們能夠儲存和操做字符串。其中String是隻讀字符串,也就意味着String引用的字符串內容是不能被改變的。而StringBuffer/StringBuilder類表示的字符串對象能夠直接進行修改。StringBuilder是Java 5中引入的,它和StringBuffer的方法徹底相同,區別在於它是在單線程環境下使用的,由於它的全部方面都沒有被synchronized修飾,所以它的效率也比StringBuffer要高。

面試題1 - 什麼狀況下用+運算符進行字符串鏈接比調用StringBuffer/StringBuilder對象的append方法鏈接字符串性能更好?

面試題2 - 請說出下面程序的輸出。

class StringEqualTest {

public static void main(String[] args) {
    String s1 = "Programming";
    String s2 = new String("Programming");
    String s3 = "Program" + "ming";
    System.out.println(s1 == s2);
    System.out.println(s1 == s3);
    System.out.println(s1 == s1.intern());
}

}

補充:String對象的intern方法會獲得字符串對象在常量池中對應的版本的引用(若是常量池中有一個字符串與String對象的equals結果是true),若是常量池中沒有對應的字符串,則該字符串將被添加到常量池中,而後返回常量池中字符串的引用。

20、重載(Overload)和重寫(Override)的區別。重載的方法可否根據返回類型進行區分?
答:方法的重載和重寫都是實現多態的方式,區別在於前者實現的是編譯時的多態性,然後者實現的是運行時的多態性。重載發生在一個類中,同名的方法若是有不一樣的參數列表(參數類型不一樣、參數個數不一樣或者兩者都不一樣)則視爲重載;重寫發生在子類與父類之間,重寫要求子類被重寫方法與父類被重寫方法有相同的返回類型,比父類被重寫方法更好訪問,不能比父類被重寫方法聲明更多的異常(里氏代換原則)。重載對返回類型沒有特殊的要求。

面試題:華爲的面試題中曾經問過這樣一個問題 - "爲何不能根據返回類型來區分重載",快說出你的答案吧!

2一、描述一下JVM加載class文件的原理機制?
答:JVM中類的裝載是由類加載器(ClassLoader)和它的子類來實現的,Java中的類加載器是一個重要的Java運行時系統組件,它負責在運行時查找和裝入類文件中的類。
因爲Java的跨平臺性,通過編譯的Java源程序並非一個可執行程序,而是一個或多個類文件。當Java程序須要使用某個類時,JVM會確保這個類已經被加載、鏈接(驗證、準備和解析)和初始化。類的加載是指把類的.class文件中的數據讀入到內存中,一般是建立一個字節數組讀入.class文件,而後產生與所加載類對應的Class對象。加載完成後,Class對象還不完整,因此此時的類還不可用。當類被加載後就進入鏈接階段,這一階段包括驗證、準備(爲靜態變量分配內存並設置默認的初始值)和解析(將符號引用替換爲直接引用)三個步驟。最後JVM對類進行初始化,包括:1)若是類存在直接的父類而且這個類尚未被初始化,那麼就先初始化父類;2)若是類中存在初始化語句,就依次執行這些初始化語句。
類的加載是由類加載器完成的,類加載器包括:根加載器(BootStrap)、擴展加載器(Extension)、系統加載器(System)和用戶自定義類加載器(java.lang.ClassLoader的子類)。從Java 2(JDK 1.2)開始,類加載過程採起了父親委託機制(PDM)。PDM更好的保證了Java平臺的安全性,在該機制中,JVM自帶的Bootstrap是根加載器,其餘的加載器都有且僅有一個父類加載器。類的加載首先請求父類加載器加載,父類加載器無能爲力時才由其子類加載器自行加載。JVM不會向Java程序提供對Bootstrap的引用。下面是關於幾個類加載器的說明:

Bootstrap:通常用本地代碼實現,負責加載JVM基礎核心類庫(rt.jar);
    Extension:從java.ext.dirs系統屬性所指定的目錄中加載類庫,它的父加載器是Bootstrap;
    System:又叫應用類加載器,其父類是Extension。它是應用最普遍的類加載器。它從環境變量classpath或者系統屬性java.class.path所指定的目錄中記載類,是用戶自定義加載器的默認父加載器。

2二、char 型變量中能不能存貯一箇中文漢字,爲何?
答:char類型能夠存儲一箇中文漢字,由於Java中使用的編碼是Unicode(不選擇任何特定的編碼,直接使用字符在字符集中的編號,這是統一的惟一方法),一個char類型佔2個字節(16比特),因此放一箇中文是沒問題的。

補充:使用Unicode意味着字符在JVM內部和外部有不一樣的表現形式,在JVM內部都是Unicode,當這個字符被從JVM內部轉移到外部時(例如存入文件系統中),須要進行編碼轉換。因此Java中有字節流和字符流,以及在字符流和字節流之間進行轉換的轉換流,如InputStreamReader和OutputStreamReader,這兩個類是字節流和字符流之間的適配器類,承擔了編碼轉換的任務;對於C程序員來講,要完成這樣的編碼轉換恐怕要依賴於union(聯合體/共用體)共享內存的特徵來實現了。

2三、抽象類(abstract class)和接口(interface)有什麼異同?
答:抽象類和接口都不可以實例化,但能夠定義抽象類和接口類型的引用。一個類若是繼承了某個抽象類或者實現了某個接口都須要對其中的抽象方法所有進行實現,不然該類仍然須要被聲明爲抽象類。接口比抽象類更加抽象,由於抽象類中能夠定義構造器,能夠有抽象方法和具體方法,而接口中不能定義構造器並且其中的方法所有都是抽象方法。抽象類中的成員能夠是private、默認、protected、public的,而接口中的成員全都是public的。抽象類中能夠定義成員變量,而接口中定義的成員變量實際上都是常量。有抽象方法的類必須被聲明爲抽象類,而抽象類未必要有抽象方法。

2四、靜態嵌套類(Static Nested Class)和內部類(Inner Class)的不一樣?
答:Static Nested Class是被聲明爲靜態(static)的內部類,它能夠不依賴於外部類實例被實例化。而一般的內部類須要在外部類實例化後才能實例化,其語法看起來挺詭異的,以下所示。

/**

  • 撲克類(一副撲克)
  • @author 駱昊

*/
public class Poker {
private static String[] suites = {「黑桃」, 「紅桃」, 「草花」, 「方塊」};
private static int[] faces = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};

private Card[] cards;

/**
 * 構造器
 * 
 */
public Poker() {
    cards = new Card[52];
    for(int i = 0; i < suites.length; i++) {
        for(int j = 0; j < faces.length; j++) {
            cards[i * 13 + j] = new Card(suites[i], faces[j]);
        }
    }
}

/**
 * 洗牌 (隨機亂序)
 * 
 */
public void shuffle() {
    for(int i = 0, len = cards.length; i < len; i++) {
        int index = (int) (Math.random() * len);
        Card temp = cards[index];
        cards[index] = cards[i];
        cards[i] = temp;
    }
}

/**
 * 發牌
 * @param index 發牌的位置
 * 
 */
public Card deal(int index) {
    return cards[index];
}

/**
 * 卡片類(一張撲克)
 * [內部類]
 * @author 駱昊
 *
 */
public class Card {
    private String suite;   // 花色
    private int face;       // 點數

    public Card(String suite, int face) {
        this.suite = suite;
        this.face = face;
    }

    @Override
    public String toString() {
        String faceStr = "";
        switch(face) {
        case 1: faceStr = "A"; break;
        case 11: faceStr = "J"; break;
        case 12: faceStr = "Q"; break;
        case 13: faceStr = "K"; break;
        default: faceStr = String.valueOf(face);
        }
        return suite + faceStr;
    }
}

}

測試代碼:

class PokerTest {

public static void main(String[] args) {
    Poker poker = new Poker();
    poker.shuffle();                // 洗牌
    Poker.Card c1 = poker.deal(0);  // 發第一張牌
    // 對於非靜態內部類Card
    // 只有經過其外部類Poker對象才能建立Card對象
    Poker.Card c2 = poker.new Card("紅心", 1);    // 本身建立一張牌

    System.out.println(c1);     // 洗牌後的第一張
    System.out.println(c2);     // 打印: 紅心A
}

}

面試題 - 下面的代碼哪些地方會產生編譯錯誤?

class Outer {

class Inner {}

public static void foo() { new Inner(); }

public void bar() { new Inner(); }

public static void main(String[] args) {
    new Inner();
}

}

注意:Java中非靜態內部類對象的建立要依賴其外部類對象,上面的面試題中foo和main方法都是靜態方法,靜態方法中沒有this,也就是說沒有所謂的外部類對象,所以沒法建立內部類對象,若是要在靜態方法中建立內部類對象,能夠這樣作:

        new Outer().new Inner();

2五、Java 中會存在內存泄漏嗎,請簡單描述。
答:理論上Java由於有垃圾回收機制(GC)不會存在內存泄露問題(這也是Java被普遍使用於服務器端編程的一個重要緣由);然而在實際開發中,可能會存在無用但可達的對象,這些對象不能被GC回收,所以也會致使內存泄露的發生。例如Hibernate的Session(一級緩存)中的對象屬於持久態,垃圾回收器是不會回收這些對象的,然而這些對象中可能存在無用的垃圾對象,若是不及時關閉(close)或清空(flush)一級緩存就可能致使內存泄露。下面例子中的代碼也會致使內存泄露。

import java.util.Arrays;
import java.util.EmptyStackException;

public class MyStack {
private T[] elements;
private int size = 0;

private static final int INIT_CAPACITY = 16;

public MyStack() {
    elements = (T[]) new Object[INIT_CAPACITY];
}

public void push(T elem) {
    ensureCapacity();
    elements[size++] = elem;
}

public T pop() {
    if(size == 0) 
        throw new EmptyStackException();
    return elements[--size];
}

private void ensureCapacity() {
    if(elements.length == size) {
        elements = Arrays.copyOf(elements, 2 * size + 1);
    }
}

}

上面的代碼實現了一個棧(先進後出(FILO))結構,乍看之下彷佛沒有什麼明顯的問題,它甚至能夠經過你編寫的各類單元測試。然而其中的pop方法卻存在內存泄露的問題,當咱們用pop方法彈出棧中的對象時,該對象不會被看成垃圾回收,即便使用棧的程序再也不引用這些對象,由於棧內部維護着對這些對象的過時引用(obsolete reference)。在支持垃圾回收的語言中,內存泄露是很隱蔽的,這種內存泄露其實就是無心識的對象保持。若是一個對象引用被無心識的保留起來了,那麼垃圾回收器不會處理這個對象,也不會處理該對象引用的其餘對象,即便這樣的對象只有少數幾個,也可能會致使不少的對象被排除在垃圾回收以外,從而對性能形成重大影響,極端狀況下會引起Disk Paging(物理內存與硬盤的虛擬內存交換數據),甚至形成OutOfMemoryError。

2六、抽象的(abstract)方法是否可同時是靜態的(static),是否可同時是本地方法(native),是否可同時被synchronized修飾?
答:都不能。抽象方法須要子類重寫,而靜態的方法是沒法被重寫的,所以兩者是矛盾的。本地方法是由本地代碼(如C代碼)實現的方法,而抽象方法是沒有實現的,也是矛盾的。synchronized和方法的實現細節有關,抽象方法不涉及實現細節,所以也是相互矛盾的。

2七、闡述靜態變量和實例變量的區別。
答:靜態變量是被static修飾符修飾的變量,也稱爲類變量,它屬於類,不屬於類的任何一個對象,一個類無論建立多少個對象,靜態變量在內存中有且僅有一個拷貝;實例變量必須依存於某一實例,須要先建立對象而後經過對象才能訪問到它。靜態變量能夠實現讓多個對象共享內存。

補充:在Java開發中,上下文類和工具類中一般會有大量的靜態成員。

2八、是否能夠從一個靜態(static)方法內部發出對非靜態(non-static)方法的調用?
答:不能夠,靜態方法只能訪問靜態成員,由於非靜態方法的調用要先建立對象,在調用靜態方法時可能對象並無被初始化。

2九、如何實現對象克隆?
答:有兩種方式:
??1). 實現Cloneable接口並重寫Object類中的clone()方法;
??2). 實現Serializable接口,經過對象的序列化和反序列化實現克隆,能夠實現真正的深度克隆,代碼以下。

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class MyUtil {

private MyUtil() {
    throw new AssertionError();
}

public static <T> T clone(T obj) throws Exception {
    ByteArrayOutputStream bout = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(bout);
    oos.writeObject(obj);

    ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bin);
    return (T) ois.readObject();

    // 說明:調用ByteArrayInputStream或ByteArrayOutputStream對象的close方法沒有任何意義
    // 這兩個基於內存的流只要垃圾回收器清理對象就可以釋放資源,這一點不一樣於對外部資源(如文件流)的釋放
}

}

下面是測試代碼:

import java.io.Serializable;

/**

  • 人類
  • @author 駱昊

*/
class Person implements Serializable {
private static final long serialVersionUID = -9102017020286042305L;

private String name;    // 姓名
private int age;        // 年齡
private Car car;        // 座駕

public Person(String name, int age, Car car) {
    this.name = name;
    this.age = age;
    this.car = car;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public int getAge() {
    return age;
}

public void setAge(int age) {
    this.age = age;
}

public Car getCar() {
    return car;
}

public void setCar(Car car) {
    this.car = car;
}

@Override
public String toString() {
    return "Person [name=" + name + ", age=" + age + ", car=" + car + "]";
}

}

/**

  • 小汽車類
  • @author 駱昊

*/
class Car implements Serializable {
private static final long serialVersionUID = -5713945027627603702L;

private String brand;       // 品牌
private int maxSpeed;       // 最高時速

public Car(String brand, int maxSpeed) {
    this.brand = brand;
    this.maxSpeed = maxSpeed;
}

public String getBrand() {
    return brand;
}

public void setBrand(String brand) {
    this.brand = brand;
}

public int getMaxSpeed() {
    return maxSpeed;
}

public void setMaxSpeed(int maxSpeed) {
    this.maxSpeed = maxSpeed;
}

@Override
public String toString() {
    return "Car [brand=" + brand + ", maxSpeed=" + maxSpeed + "]";
}

}

class CloneTest { public static void main(String[] args) { try { Person p1 = new Person(「Hao LUO」, 33, new Car(「Benz」, 300)); Person p2 = MyUtil.clone(p1); // 深度克隆 p2.getCar().setBrand(「BYD」); // 修改克隆的Person對象p2關聯的汽車對象的品牌屬性 // 原來的Person對象p1關聯的汽車不會受到任何影響 // 由於在克隆Person對象時其關聯的汽車對象也被克隆了 System.out.println(p1); } catch (Exception e) { e.printStackTrace(); } }}

注意:基於序列化和反序列化實現的克隆不只僅是深度克隆,更重要的是經過泛型限定,能夠檢查出要克隆的對象是否支持序列化,這項檢查是編譯器完成的,不是在運行時拋出異常,這種是方案明顯優於使用Object類的clone方法克隆對象。讓問題在編譯的時候暴露出來老是優於把問題留到運行時。

30、GC是什麼?爲何要有GC?
答:GC是垃圾收集的意思,內存處理是編程人員容易出現問題的地方,忘記或者錯誤的內存回收會致使程序或系統的不穩定甚至崩潰,Java提供的GC功能能夠自動監測對象是否超過做用域從而達到自動回收內存的目的,Java語言沒有提供釋放已分配內存的顯示操做方法。Java程序員不用擔憂內存管理,由於垃圾收集器會自動進行管理。要請求垃圾收集,能夠調用下面的方法之一:System.gc() 或Runtime.getRuntime().gc() ,但JVM能夠屏蔽掉顯示的垃圾回收調用。
垃圾回收能夠有效的防止內存泄露,有效的使用能夠使用的內存。垃圾回收器一般是做爲一個單獨的低優先級的線程運行,不可預知的狀況下對內存堆中已經死亡的或者長時間沒有使用的對象進行清除和回收,程序員不能實時的調用垃圾回收器對某個對象或全部對象進行垃圾回收。在Java誕生初期,垃圾回收是Java最大的亮點之一,由於服務器端的編程須要有效的防止內存泄露問題,然而時過境遷,現在Java的垃圾回收機制已經成爲被詬病的東西。移動智能終端用戶一般以爲iOS的系統比Android系統有更好的用戶體驗,其中一個深層次的緣由就在於Android系統中垃圾回收的不可預知性。

補充:垃圾回收機制有不少種,包括:分代複製垃圾回收、標記垃圾回收、增量垃圾回收等方式。標準的Java進程既有棧又有堆。棧保存了原始型局部變量,堆保存了要建立的對象。Java平臺對堆內存回收和再利用的基本算法被稱爲標記和清除,可是Java對其進行了改進,採用「分代式垃圾收集」。這種方法會跟Java對象的生命週期將堆內存劃分爲不一樣的區域,在垃圾收集過程當中,可能會將對象移動到不一樣區域:
- 伊甸園(Eden):這是對象最初誕生的區域,而且對大多數對象來講,這裏是它們惟一存在過的區域。
- 倖存者樂園(Survivor):從伊甸園倖存下來的對象會被挪到這裏。
- 終身頤養園(Tenured):這是足夠老的倖存對象的歸宿。年輕代收集(Minor-GC)過程是不會觸及這個地方的。當年輕代收集不能把對象放進終身頤養園時,就會觸發一次徹底收集(Major-GC),這裏可能還會牽扯到壓縮,以便爲大對象騰出足夠的空間。

與垃圾回收相關的JVM參數:

-Xms / -Xmx — 堆的初始大小 / 堆的最大大小
    -Xmn — 堆中年輕代的大小
    -XX:-DisableExplicitGC — 讓System.gc()不產生任何做用
    -XX:+PrintGCDetails — 打印GC的細節
    -XX:+PrintGCDateStamps — 打印GC操做的時間戳
    -XX:NewSize / XX:MaxNewSize — 設置新生代大小/新生代最大大小
    -XX:NewRatio — 能夠設置老生代和新生代的比例
    -XX:PrintTenuringDistribution — 設置每次新生代GC後輸出倖存者樂園中對象年齡的分佈
    -XX:InitialTenuringThreshold / -XX:MaxTenuringThreshold:設置老年代閥值的初始值和最大值
    -XX:TargetSurvivorRatio:設置倖存區的目標使用率

3一、String s = new String(「xyz」);建立了幾個字符串對象?
答:兩個對象,一個是靜態區的"xyz",一個是用new建立在堆上的對象。

3二、接口是否可繼承(extends)接口?抽象類是否可實現(implements)接口?抽象類是否可繼承具體類(concrete class)?
答:接口能夠繼承接口,並且支持多重繼承。抽象類能夠實現(implements)接口,抽象類可繼承具體類也能夠繼承抽象類。

3三、一個".java"源文件中是否能夠包含多個類(不是內部類)?有什麼限制?
答:能夠,但一個源文件中最多隻能有一個公開類(public class)並且文件名必須和公開類的類名徹底保持一致。

3四、Anonymous Inner Class(匿名內部類)是否能夠繼承其它類?是否能夠實現接口?
答:能夠繼承其餘類或實現其餘接口,在Swing編程和Android開發中經常使用此方式來實現事件監聽和回調。

3五、內部類能夠引用它的包含類(外部類)的成員嗎?有沒有什麼限制?
答:一個內部類對象能夠訪問建立它的外部類對象的成員,包括私有成員。

3六、Java 中的final關鍵字有哪些用法?
答:(1)修飾類:表示該類不能被繼承;(2)修飾方法:表示方法不能被重寫;(3)修飾變量:表示變量只能一次賦值之後值不能被修改(常量)。

3七、指出下面程序的運行結果。

class A {

static {
    System.out.print("1");
}

public A() {
    System.out.print("2");
}

}

class B extends A{

static {
    System.out.print("a");
}

public B() {
    System.out.print("b");
}

}

public class Hello {

public static void main(String[] args) {
    A ab = new B();
    ab = new B();
}

}

答:執行結果:1a2b2b。建立對象時構造器的調用順序是:先初始化靜態成員,而後調用父類構造器,再初始化非靜態成員,最後調用自身構造器。

提示:若是不能給出此題的正確答案,說明以前第21題Java類加載機制尚未徹底理解,趕忙再看看吧。

3八、數據類型之間的轉換:

  • 如何將字符串轉換爲基本數據類型?
  • 如何將基本數據類型轉換爲字符串?
    答:
  • 調用基本數據類型對應的包裝類中的方法parseXXX(String)或valueOf(String)便可返回相應基本類型;
  • 一種方法是將基本數據類型與空字符串("")鏈接(+)便可得到其所對應的字符串;另外一種方法是調用String 類中的valueOf()方法返回相應字符串

3九、如何實現字符串的反轉及替換?
答:方法不少,能夠本身寫實現也能夠使用String或StringBuffer/StringBuilder中的方法。有一道很常見的面試題是用遞歸實現字符串反轉,代碼以下所示:

public static String reverse(String originStr) {
if(originStr == null || originStr.length() <= 1)
return originStr;
return reverse(originStr.substring(1)) + originStr.charAt(0);
}

40、怎樣將GB2312編碼的字符串轉換爲ISO-8859-1編碼的字符串?
答:代碼以下所示:

String s1 = 「你好」;
String s2 = new String(s1.getBytes(「GB2312」), 「ISO-8859-1」);

4一、日期和時間:

  • 如何取得年月日、小時分鐘秒?
  • 如何取得從1970年1月1日0時0分0秒到如今的毫秒數?
  • 如何取得某月的最後一天?
  • 如何格式化日期?
    答:
    問題1:建立java.util.Calendar 實例,調用其get()方法傳入不一樣的參數便可得到參數所對應的值。Java 8中能夠使用java.time.LocalDateTimel來獲取,代碼以下所示。

public class DateTimeTest {
public static void main(String[] args) {
Calendar cal = Calendar.getInstance();
System.out.println(cal.get(Calendar.YEAR));
System.out.println(cal.get(Calendar.MONTH)); // 0 - 11
System.out.println(cal.get(Calendar.DATE));
System.out.println(cal.get(Calendar.HOUR_OF_DAY));
System.out.println(cal.get(Calendar.MINUTE));
System.out.println(cal.get(Calendar.SECOND));

// Java 8
    LocalDateTime dt = LocalDateTime.now();
    System.out.println(dt.getYear());
    System.out.println(dt.getMonthValue());     // 1 - 12
    System.out.println(dt.getDayOfMonth());
    System.out.println(dt.getHour());
    System.out.println(dt.getMinute());
    System.out.println(dt.getSecond());
}

}

問題2:如下方法都可得到該毫秒數。

Calendar.getInstance().getTimeInMillis();
System.currentTimeMillis();
Clock.systemDefaultZone().millis(); // Java 8

問題3:代碼以下所示。

Calendar time = Calendar.getInstance();
time.getActualMaximum(Calendar.DAY_OF_MONTH);

問題4:利用java.text.DataFormat 的子類(如SimpleDateFormat類)中的format(Date)方法可將日期格式化。Java 8中能夠用java.time.format.DateTimeFormatter來格式化時間日期,代碼以下所示。

import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Date;

class DateFormatTest {

public static void main(String[] args) {
    SimpleDateFormat oldFormatter = new SimpleDateFormat("yyyy/MM/dd");
    Date date1 = new Date();
    System.out.println(oldFormatter.format(date1));

    // Java 8
    DateTimeFormatter newFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
    LocalDate date2 = LocalDate.now();
    System.out.println(date2.format(newFormatter));
}

}

補充:Java的時間日期API一直以來都是被詬病的東西,爲了解決這一問題,Java 8中引入了新的時間日期API,其中包括LocalDate、LocalTime、LocalDateTime、Clock、Instant等類,這些的類的設計都使用了不變模式,所以是線程安全的設計。

4二、打印昨天的當前時刻。
答:

import java.util.Calendar;

class YesterdayCurrent {
public static void main(String[] args){
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE, -1);
System.out.println(cal.getTime());
}
}

在Java 8中,能夠用下面的代碼實現相同的功能。

import java.time.LocalDateTime;

class YesterdayCurrent {

public static void main(String[] args) {
    LocalDateTime today = LocalDateTime.now();
    LocalDateTime yesterday = today.minusDays(1);

    System.out.println(yesterday);
}

}

4三、比較一下Java和JavaSciprt。
答:JavaScript 與Java是兩個公司開發的不一樣的兩個產品。Java 是原Sun Microsystems公司推出的面向對象的程序設計語言,特別適合於互聯網應用程序開發;而JavaScript是Netscape公司的產品,爲了擴展Netscape瀏覽器的功能而開發的一種能夠嵌入Web頁面中運行的基於對象和事件驅動的解釋性語言。JavaScript的前身是LiveScript;而Java的前身是Oak語言。
下面對兩種語言間的異同做以下比較:

  • 基於對象和麪向對象:Java是一種真正的面向對象的語言,即便是開發簡單的程序,必須設計對象;JavaScript是種腳本語言,它能夠用來製做與網絡無關的,與用戶交互做用的複雜軟件。它是一種基於對象(Object-Based)和事件驅動(Event-Driven)的編程語言,於是它自己提供了很是豐富的內部對象供設計人員使用。

  • 解釋和編譯:Java的源代碼在執行以前,必須通過編譯。JavaScript是一種解釋性編程語言,其源代碼不需通過編譯,由瀏覽器解釋執行。(目前的瀏覽器幾乎都使用了JIT(即時編譯)技術來提高JavaScript的運行效率)

  • 強類型變量和類型弱變量:Java採用強類型變量檢查,即全部變量在編譯以前必須做聲明;JavaScript中變量是弱類型的,甚至在使用變量前能夠不做聲明,JavaScript的解釋器在運行時檢查推斷其數據類型。

  • 代碼格式不同。

    補充:上面列出的四點是網上流傳的所謂的標準答案。其實Java和JavaScript最重要的區別是一個是靜態語言,一個是動態語言。目前的編程語言的發展趨勢是函數式語言和動態語言。在Java中類(class)是一等公民,而JavaScript中函數(function)是一等公民,所以JavaScript支持函數式編程,能夠使用Lambda函數和閉包(closure),固然Java 8也開始支持函數式編程,提供了對Lambda表達式以及函數式接口的支持。對於這類問題,在面試的時候最好仍是用本身的語言回答會更加靠譜,不要背網上所謂的標準答案。

4四、何時用斷言(assert)?
答:斷言在軟件開發中是一種經常使用的調試方式,不少開發語言中都支持這種機制。通常來講,斷言用於保證程序最基本、關鍵的正確性。斷言檢查一般在開發和測試時開啓。爲了保證程序的執行效率,在軟件發佈後斷言檢查一般是關閉的。斷言是一個包含布爾表達式的語句,在執行這個語句時假定該表達式爲true;若是表達式的值爲false,那麼系統會報告一個AssertionError。斷言的使用以下面的代碼所示:

assert(a > 0); // throws an AssertionError if a <= 0

斷言能夠有兩種形式:
assert Expression1;
assert Expression1 : Expression2 ;
Expression1 應該老是產生一個布爾值。
Expression2 能夠是得出一個值的任意表達式;這個值用於生成顯示更多調試信息的字符串消息。

要在運行時啓用斷言,能夠在啓動JVM時使用-enableassertions或者-ea標記。要在運行時選擇禁用斷言,能夠在啓動JVM時使用-da或者-disableassertions標記。要在系統類中啓用或禁用斷言,可以使用-esa或-dsa標記。還能夠在包的基礎上啓用或者禁用斷言。

注意:斷言不該該以任何方式改變程序的狀態。簡單的說,若是但願在不知足某些條件時阻止代碼的執行,就能夠考慮用斷言來阻止它。

4五、Error和Exception有什麼區別?
答:Error表示系統級的錯誤和程序沒必要處理的異常,是恢復不是不可能但很困難的狀況下的一種嚴重問題;好比內存溢出,不可能期望程序能處理這樣的狀況;Exception表示須要捕捉或者須要程序進行處理的異常,是一種設計或實現問題;也就是說,它表示若是程序運行正常,從不會發生的狀況。

面試題:2005年摩托羅拉的面試中曾經問過這麼一個問題「If a process reports a stack overflow run-time error, what’s the most possible cause?」,給了四個選項a. lack of memory; b. write on an invalid memory space; c. recursive function calling; d. array index out of boundary. Java程序在運行時也可能會遭遇StackOverflowError,這是一個沒法恢復的錯誤,只能從新修改代碼了,這個面試題的答案是c。若是寫了不能迅速收斂的遞歸,則頗有可能引起棧溢出的錯誤,以下所示:

class StackOverflowErrorTest {

public static void main(String[] args) {
    main(null);
}

}

提示:用遞歸編寫程序時必定要牢記兩點:1. 遞歸公式;2. 收斂條件(何時就再也不繼續遞歸)。

4六、try{}裏有一個return語句,那麼緊跟在這個try後的finally{}裏的代碼會不會被執行,何時被執行,在return前仍是後?
答:會執行,在方法返回調用者前執行。

注意:在finally中改變返回值的作法是很差的,由於若是存在finally代碼塊,try中的return語句不會立馬返回調用者,而是記錄下返回值待finally代碼塊執行完畢以後再向調用者返回其值,而後若是在finally中修改了返回值,就會返回修改後的值。顯然,在finally中返回或者修改返回值會對程序形成很大的困擾,C#中直接用編譯錯誤的方式來阻止程序員幹這種齷齪的事情,Java中也能夠經過提高編譯器的語法檢查級別來產生警告或錯誤,Eclipse中能夠在如圖所示的地方進行設置,強烈建議將此項設置爲編譯錯誤。

4七、Java語言如何進行異常處理,關鍵字:throws、throw、try、catch、finally分別如何使用?
答:Java經過面向對象的方法進行異常處理,把各類不一樣的異常進行分類,並提供了良好的接口。在Java中,每一個異常都是一個對象,它是Throwable類或其子類的實例。當一個方法出現異常後便拋出一個異常對象,該對象中包含有異常信息,調用這個對象的方法能夠捕獲到這個異常並能夠對其進行處理。Java的異常處理是經過5個關鍵詞來實現的:try、catch、throw、throws和finally。通常狀況下是用try來執行一段程序,若是系統會拋出(throw)一個異常對象,能夠經過它的類型來捕獲(catch)它,或經過老是執行代碼塊(finally)來處理;try用來指定一塊預防全部異常的程序;catch子句緊跟在try塊後面,用來指定你想要捕獲的異常的類型;throw語句用來明確地拋出一個異常;throws用來聲明一個方法可能拋出的各類異常(固然聲明異常時容許無病呻吟);finally爲確保一段代碼無論發生什麼異常情況都要被執行;try語句能夠嵌套,每當遇到一個try語句,異常的結構就會被放入異常棧中,直到全部的try語句都完成。若是下一級的try語句沒有對某種異常進行處理,異常棧就會執行出棧操做,直到遇到有處理這種異常的try語句或者最終將異常拋給JVM。

4八、運行時異常與受檢異常有何異同?
答:異常表示程序運行過程當中可能出現的非正常狀態,運行時異常表示虛擬機的一般操做中可能遇到的異常,是一種常見運行錯誤,只要程序設計得沒有問題一般就不會發生。受檢異常跟程序運行的上下文環境有關,即便程序設計無誤,仍然可能因使用的問題而引起。Java編譯器要求方法必須聲明拋出可能發生的受檢異常,可是並不要求必須聲明拋出未被捕獲的運行時異常。異常和繼承同樣,是面向對象程序設計中常常被濫用的東西,在Effective Java中對異常的使用給出瞭如下指導原則:

  • 不要將異常處理用於正常的控制流(設計良好的API不該該強迫它的調用者爲了正常的控制流而使用異常)
  • 對能夠恢復的狀況使用受檢異常,對編程錯誤使用運行時異常
  • 避免沒必要要的使用受檢異常(能夠經過一些狀態檢測手段來避免異常的發生)
  • 優先使用標準的異常
  • 每一個方法拋出的異常都要有文檔
  • 保持異常的原子性
  • 不要在catch中忽略掉捕獲到的異常

4九、列出一些你常見的運行時異常?
答:

  • ArithmeticException(算術異常)
  • ClassCastException (類轉換異常)
  • IllegalArgumentException (非法參數異常)
  • IndexOutOfBoundsException (下標越界異常)
  • NullPointerException (空指針異常)
  • SecurityException (安全異常)

50、闡述final、finally、finalize的區別。
答:

  • final:修飾符(關鍵字)有三種用法:若是一個類被聲明爲final,意味着它不能再派生出新的子類,即不能被繼承,所以它和abstract是反義詞。將變量聲明爲final,能夠保證它們在使用中不被改變,被聲明爲final的變量必須在聲明時給定初值,而在之後的引用中只能讀取不可修改。被聲明爲final的方法也一樣只能使用,不能在子類中被重寫。
  • finally:一般放在try…catch…的後面構造老是執行代碼塊,這就意味着程序不管正常執行仍是發生異常,這裏的代碼只要JVM不關閉都能執行,能夠將釋放外部資源的代碼寫在finally塊中。
  • finalize:Object類中定義的方法,Java中容許使用finalize()方法在垃圾收集器將對象從內存中清除出去以前作必要的清理工做。這個方法是由垃圾收集器在銷燬對象時調用的,經過重寫finalize()方法能夠整理系統資源或者執行其餘清理工做。

5一、類ExampleA繼承Exception,類ExampleB繼承ExampleA。
有以下代碼片段:

try {
throw new ExampleB(「b」)
} catch(ExampleA e){
System.out.println(「ExampleA」);
} catch(Exception e){
System.out.println(「Exception」);
}

請問執行此段代碼的輸出是什麼?
答:輸出:ExampleA。(根據里氏代換原則[能使用父類型的地方必定能使用子類型],抓取ExampleA類型異常的catch塊可以抓住try塊中拋出的ExampleB類型的異常)

面試題 - 說出下面代碼的運行結果。(此題的出處是《Java編程思想》一書)

class Annoyance extends Exception {}
class Sneeze extends Annoyance {}

class Human {

public static void main(String[] args) 
    throws Exception {
    try {
        try {
            throw new Sneeze();
        } 
        catch ( Annoyance a ) {
            System.out.println("Caught Annoyance");
            throw a;
        }
    } 
    catch ( Sneeze s ) {
        System.out.println("Caught Sneeze");
        return ;
    }
    finally {
        System.out.println("Hello World!");
    }
}

}

5二、List、Set、Map是否繼承自Collection接口?
答:List、Set 是,Map 不是。Map是鍵值對映射容器,與List和Set有明顯的區別,而Set存儲的零散的元素且不容許有重複元素(數學中的集合也是如此),List是線性結構的容器,適用於按數值索引訪問元素的情形。

5三、闡述ArrayList、Vector、LinkedList的存儲性能和特性。
答:ArrayList 和Vector都是使用數組方式存儲數據,此數組元素數大於實際存儲的數據以便增長和插入元素,它們都容許直接按序號索引元素,可是插入元素要涉及數組元素移動等內存操做,因此索引數據快而插入數據慢,Vector中的方法因爲添加了synchronized修飾,所以Vector是線程安全的容器,但性能上較ArrayList差,所以已是Java中的遺留容器。LinkedList使用雙向鏈表實現存儲(將內存中零散的內存單元經過附加的引用關聯起來,造成一個能夠按序號索引的線性結構,這種鏈式存儲方式與數組的連續存儲方式相比,內存的利用率更高),按序號索引數據須要進行前向或後向遍歷,可是插入數據時只須要記錄本項的先後項便可,因此插入速度較快。Vector屬於遺留容器(Java早期的版本中提供的容器,除此以外,Hashtable、Dictionary、BitSet、Stack、Properties都是遺留容器),已經不推薦使用,可是因爲ArrayList和LinkedListed都是非線程安全的,若是遇到多個線程操做同一個容器的場景,則能夠經過工具類Collections中的synchronizedList方法將其轉換成線程安全的容器後再使用(這是對裝潢模式的應用,將已有對象傳入另外一個類的構造器中建立新的對象來加強實現)。

補充:遺留容器中的Properties類和Stack類在設計上有嚴重的問題,Properties是一個鍵和值都是字符串的特殊的鍵值對映射,在設計上應該是關聯一個Hashtable並將其兩個泛型參數設置爲String類型,可是Java API中的Properties直接繼承了Hashtable,這很明顯是對繼承的濫用。這裏複用代碼的方式應該是Has-A關係而不是Is-A關係,另外一方面容器都屬於工具類,繼承工具類自己就是一個錯誤的作法,使用工具類最好的方式是Has-A關係(關聯)或Use-A關係(依賴)。同理,Stack類繼承Vector也是不正確的。Sun公司的工程師們也會犯這種低級錯誤,讓人唏噓不已。

5四、Collection和Collections的區別?
答:Collection是一個接口,它是Set、List等容器的父接口;Collections是個一個工具類,提供了一系列的靜態方法來輔助容器操做,這些方法包括對容器的搜索、排序、線程安全化等等。

5五、List、Map、Set三個接口存取元素時,各有什麼特色?
答:List以特定索引來存取元素,能夠有重複元素。Set不能存放重複元素(用對象的equals()方法來區分元素是否重複)。Map保存鍵值對(key-value pair)映射,映射關係能夠是一對一或多對一。Set和Map容器都有基於哈希存儲和排序樹的兩種實現版本,基於哈希存儲的版本理論存取時間複雜度爲O(1),而基於排序樹版本的實如今插入或刪除元素時會按照元素或元素的鍵(key)構成排序樹從而達到排序和去重的效果。

5六、TreeMap和TreeSet在排序時如何比較元素?Collections工具類中的sort()方法如何比較元素?
答:TreeSet要求存放的對象所屬的類必須實現Comparable接口,該接口提供了比較元素的compareTo()方法,當插入元素時會回調該方法比較元素的大小。TreeMap要求存放的鍵值對映射的鍵必須實現Comparable接口從而根據鍵對元素進行排序。Collections工具類的sort方法有兩種重載的形式,第一種要求傳入的待排序容器中存放的對象比較實現Comparable接口以實現元素的比較;第二種不強制性的要求容器中的元素必須可比較,可是要求傳入第二個參數,參數是Comparator接口的子類型(須要重寫compare方法實現元素的比較),至關於一個臨時定義的排序規則,其實就是經過接口注入比較元素大小的算法,也是對回調模式的應用(Java中對函數式編程的支持)。
例子1:

public class Student implements Comparable {
private String name; // 姓名
private int age; // 年齡

public Student(String name, int age) {
    this.name = name;
    this.age = age;
}

@Override
public String toString() {
    return "Student [name=" + name + ", age=" + age + "]";
}

@Override
public int compareTo(Student o) {
    return this.age - o.age; // 比較年齡(年齡的升序)
}

}

import java.util.Set;
import java.util.TreeSet;

class Test01 {

public static void main(String[] args) {
    Set<Student> set = new TreeSet<>();     // Java 7的鑽石語法(構造器後面的尖括號中不須要寫類型)
    set.add(new Student("Hao LUO", 33));
    set.add(new Student("XJ WANG", 32));
    set.add(new Student("Bruce LEE", 60));
    set.add(new Student("Bob YANG", 22));

    for(Student stu : set) {
        System.out.println(stu);
    }

// 輸出結果:
// Student [name=Bob YANG, age=22]
// Student [name=XJ WANG, age=32]
// Student [name=Hao LUO, age=33]
// Student [name=Bruce LEE, age=60]
}
}

例子2:

public class Student {
private String name; // 姓名
private int age; // 年齡

public Student(String name, int age) {
    this.name = name;
    this.age = age;
}

/**
 * 獲取學生姓名
 */
public String getName() {
    return name;
}

/**
 * 獲取學生年齡
 */
public int getAge() {
    return age;
}

@Override
public String toString() {
    return "Student [name=" + name + ", age=" + age + "]";
}

}

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

class Test02 {

public static void main(String[] args) {
    List<Student> list = new ArrayList<>();     // Java 7的鑽石語法(構造器後面的尖括號中不須要寫類型)
    list.add(new Student("Hao LUO", 33));
    list.add(new Student("XJ WANG", 32));
    list.add(new Student("Bruce LEE", 60));
    list.add(new Student("Bob YANG", 22));

    // 經過sort方法的第二個參數傳入一個Comparator接口對象
    // 至關因而傳入一個比較對象大小的算法到sort方法中
    // 因爲Java中沒有函數指針、仿函數、委託這樣的概念
    // 所以要將一個算法傳入一個方法中惟一的選擇就是經過接口回調
    Collections.sort(list, new Comparator<Student> () {

        @Override
        public int compare(Student o1, Student o2) {
            return o1.getName().compareTo(o2.getName());    // 比較學生姓名
        }
    });

    for(Student stu : list) {
        System.out.println(stu);
    }

// 輸出結果:
// Student [name=Bob YANG, age=22]
// Student [name=Bruce LEE, age=60]
// Student [name=Hao LUO, age=33]
// Student [name=XJ WANG, age=32]
}
}

5七、Thread類的sleep()方法和對象的wait()方法均可以讓線程暫停執行,它們有什麼區別?
答:sleep()方法(休眠)是線程類(Thread)的靜態方法,調用此方法會讓當前線程暫停執行指定的時間,將執行機會(CPU)讓給其餘線程,可是對象的鎖依然保持,所以休眠時間結束後會自動恢復(線程回到就緒狀態,請參考第66題中的線程狀態轉換圖)。wait()是Object類的方法,調用對象的wait()方法致使當前線程放棄對象的鎖(線程暫停執行),進入對象的等待池(wait pool),只有調用對象的notify()方法(或notifyAll()方法)時才能喚醒等待池中的線程進入等鎖池(lock pool),若是線程從新得到對象的鎖就能夠進入就緒狀態。

補充:可能很多人對什麼是進程,什麼是線程還比較模糊,對於爲何須要多線程編程也不是特別理解。簡單的說:進程是具備必定獨立功能的程序關於某個數據集合上的一次運行活動,是操做系統進行資源分配和調度的一個獨立單位;線程是進程的一個實體,是CPU調度和分派的基本單位,是比進程更小的能獨立運行的基本單位。線程的劃分尺度小於進程,這使得多線程程序的併發性高;進程在執行時一般擁有獨立的內存單元,而線程之間能夠共享內存。使用多線程的編程一般可以帶來更好的性能和用戶體驗,可是多線程的程序對於其餘程序是不友好的,由於它可能佔用了更多的CPU資源。固然,也不是線程越多,程序的性能就越好,由於線程之間的調度和切換也會浪費CPU時間。時下很時髦的Node.js就採用了單線程異步I/O的工做模式。

5八、線程的sleep()方法和yield()方法有什麼區別?
答:
① sleep()方法給其餘線程運行機會時不考慮線程的優先級,所以會給低優先級的線程以運行的機會;yield()方法只會給相同優先級或更高優先級的線程以運行的機會;
② 線程執行sleep()方法後轉入阻塞(blocked)狀態,而執行yield()方法後轉入就緒(ready)狀態;
③ sleep()方法聲明拋出InterruptedException,而yield()方法沒有聲明任何異常;
④ sleep()方法比yield()方法(跟操做系統CPU調度相關)具備更好的可移植性。

5九、當一個線程進入一個對象的synchronized方法A以後,其它線程是否可進入此對象的synchronized方法B?
答:不能。其它線程只能訪問該對象的非同步方法,同步方法則不能進入。由於非靜態方法上的synchronized修飾符要求執行方法時要得到對象的鎖,若是已經進入A方法說明對象鎖已經被取走,那麼試圖進入B方法的線程就只能在等鎖池(注意不是等待池哦)中等待對象的鎖。

60、請說出與線程同步以及線程調度相關的方法。
答:

  • wait():使一個線程處於等待(阻塞)狀態,而且釋放所持有的對象的鎖;

  • sleep():使一個正在運行的線程處於睡眠狀態,是一個靜態方法,調用此方法要處理InterruptedException異常;

  • notify():喚醒一個處於等待狀態的線程,固然在調用此方法的時候,並不能確切的喚醒某一個等待狀態的線程,而是由JVM肯定喚醒哪一個線程,並且與優先級無關;

  • notityAll():喚醒全部處於等待狀態的線程,該方法並非將對象的鎖給全部線程,而是讓它們競爭,只有得到鎖的線程才能進入就緒狀態;

    補充:Java 5經過Lock接口提供了顯式的鎖機制(explicit lock),加強了靈活性以及對線程的協調。Lock接口中定義了加鎖(lock())和解鎖(unlock())的方法,同時還提供了newCondition()方法來產生用於線程之間通訊的Condition對象;此外,Java 5還提供了信號量機制(semaphore),信號量能夠用來限制對某個共享資源進行訪問的線程的數量。在對資源進行訪問以前,線程必須獲得信號量的許可(調用Semaphore對象的acquire()方法);在完成對資源的訪問後,線程必須向信號量歸還許可(調用Semaphore對象的release()方法)。

下面的例子演示了100個線程同時向一個銀行帳戶中存入1元錢,在沒有使用同步機制和使用同步機制狀況下的執行狀況。

銀行帳戶類:

/**

  • 銀行帳戶
  • @author 駱昊

*/
public class Account {
private double balance; // 帳戶餘額

/**
 * 存款
 * @param money 存入金額
 */
public void deposit(double money) {
    double newBalance = balance + money;
    try {
        Thread.sleep(10);   // 模擬此業務須要一段處理時間
    }
    catch(InterruptedException ex) {
        ex.printStackTrace();
    }
    balance = newBalance;
}

/**
 * 得到帳戶餘額
 */
public double getBalance() {
    return balance;
}

}

存錢線程類:

/**

  • 存錢線程
  • @author 駱昊

*/
public class AddMoneyThread implements Runnable {
private Account account; // 存入帳戶
private double money; // 存入金額

public AddMoneyThread(Account account, double money) {
    this.account = account;
    this.money = money;
}

@Override
public void run() {
    account.deposit(money);
}

}

測試類:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test01 {

public static void main(String[] args) {
    Account account = new Account();
    ExecutorService service = Executors.newFixedThreadPool(100);

    for(int i = 1; i <= 100; i++) {
        service.execute(new AddMoneyThread(account, 1));
    }

    service.shutdown();

    while(!service.isTerminated()) {}

    System.out.println("帳戶餘額: " + account.getBalance());
}

}

在沒有同步的狀況下,執行結果一般是顯示帳戶餘額在10元如下,出現這種情況的緣由是,當一個線程A試圖存入1元的時候,另一個線程B也可以進入存款的方法中,線程B讀取到的帳戶餘額仍然是線程A存入1元錢以前的帳戶餘額,所以也是在原來的餘額0上面作了加1元的操做,同理線程C也會作相似的事情,因此最後100個線程執行結束時,原本指望帳戶餘額爲100元,但實際獲得的一般在10元如下(極可能是1元哦)。解決這個問題的辦法就是同步,當一個線程對銀行帳戶存錢時,須要將此帳戶鎖定,待其操做完成後才容許其餘的線程進行操做,代碼有以下幾種調整方案:

在銀行帳戶的存款(deposit)方法上同步(synchronized)關鍵字

/**

  • 銀行帳戶
  • @author 駱昊

*/
public class Account {
private double balance; // 帳戶餘額

/**
 * 存款
 * @param money 存入金額
 */
public synchronized void deposit(double money) {
    double newBalance = balance + money;
    try {
        Thread.sleep(10);   // 模擬此業務須要一段處理時間
    }
    catch(InterruptedException ex) {
        ex.printStackTrace();
    }
    balance = newBalance;
}

/**
 * 得到帳戶餘額
 */
public double getBalance() {
    return balance;
}

}

在線程調用存款方法時對銀行帳戶進行同步

/**

  • 存錢線程
  • @author 駱昊

*/
public class AddMoneyThread implements Runnable {
private Account account; // 存入帳戶
private double money; // 存入金額

public AddMoneyThread(Account account, double money) {
    this.account = account;
    this.money = money;
}

@Override
public void run() {
    synchronized (account) {
        account.deposit(money); 
    }
}

}

經過Java 5顯示的鎖機制,爲每一個銀行帳戶建立一個鎖對象,在存款操做進行加鎖和解鎖的操做

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**

  • 銀行帳戶
  • @author 駱昊

*/
public class Account {
private Lock accountLock = new ReentrantLock();
private double balance; // 帳戶餘額

/**
 * 存款
 * 
 * @param money
 *            存入金額
 */
public void deposit(double money) {
    accountLock.lock();
    try {
        double newBalance = balance + money;
        try {
            Thread.sleep(10); // 模擬此業務須要一段處理時間
        }
        catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        balance = newBalance;
    }
    finally {
        accountLock.unlock();
    }
}

/**
 * 得到帳戶餘額
 */
public double getBalance() {
    return balance;
}

}

按照上述三種方式對代碼進行修改後,重寫執行測試代碼Test01,將看到最終的帳戶餘額爲100元。固然也能夠使用Semaphore或CountdownLatch來實現同步。

6一、編寫多線程程序有幾種實現方式?
答:Java 5之前實現多線程有兩種實現方法:一種是繼承Thread類;另外一種是實現Runnable接口。兩種方式都要經過重寫run()方法來定義線程的行爲,推薦使用後者,由於Java中的繼承是單繼承,一個類有一個父類,若是繼承了Thread類就沒法再繼承其餘類了,顯然使用Runnable接口更爲靈活。

補充:Java 5之後建立線程還有第三種方式:實現Callable接口,該接口中的call方法能夠在線程執行結束時產生一個返回值,代碼以下所示:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

class MyTask implements Callable {
private int upperBounds;

public MyTask(int upperBounds) {
    this.upperBounds = upperBounds;
}

@Override
public Integer call() throws Exception {
    int sum = 0; 
    for(int i = 1; i <= upperBounds; i++) {
        sum += i;
    }
    return sum;
}

}

class Test {

public static void main(String[] args) throws Exception {
    List<Future<Integer>> list = new ArrayList<>();
    ExecutorService service = Executors.newFixedThreadPool(10);
    for(int i = 0; i < 10; i++) {
        list.add(service.submit(new MyTask((int) (Math.random() * 100))));
    }

    int sum = 0;
    for(Future<Integer> future : list) {
        // while(!future.isDone()) ;
        sum += future.get();
    }

    System.out.println(sum);
}

}

6二、synchronized關鍵字的用法?
答:synchronized關鍵字能夠將對象或者方法標記爲同步,以實現對對象和方法的互斥訪問,能夠用synchronized(對象) { … }定義同步代碼塊,或者在聲明方法時將synchronized做爲方法的修飾符。在第60題的例子中已經展現了synchronized關鍵字的用法。

6三、舉例說明同步和異步。
答:若是系統中存在臨界資源(資源數量少於競爭資源的線程數量的資源),例如正在寫的數據之後可能被另外一個線程讀到,或者正在讀的數據可能已經被另外一個線程寫過了,那麼這些數據就必須進行同步存取(數據庫操做中的排他鎖就是最好的例子)。當應用程序在對象上調用了一個須要花費很長時間來執行的方法,而且不但願讓程序等待方法的返回時,就應該使用異步編程,在不少狀況下采用異步途徑每每更有效率。事實上,所謂的同步就是指阻塞式操做,而異步就是非阻塞式操做。

6四、啓動一個線程是調用run()仍是start()方法?
答:啓動一個線程是調用start()方法,使線程所表明的虛擬處理機處於可運行狀態,這意味着它能夠由JVM 調度並執行,這並不意味着線程就會當即運行。run()方法是線程啓動後要進行回調(callback)的方法。

6五、什麼是線程池(thread pool)?
答:在面向對象編程中,建立和銷燬對象是很費時間的,由於建立一個對象要獲取內存資源或者其它更多資源。在Java中更是如此,虛擬機將試圖跟蹤每個對象,以便可以在對象銷燬後進行垃圾回收。因此提升服務程序效率的一個手段就是儘量減小建立和銷燬對象的次數,特別是一些很耗資源的對象建立和銷燬,這就是」池化資源」技術產生的緣由。線程池顧名思義就是事先建立若干個可執行的線程放入一個池(容器)中,須要的時候從池中獲取線程不用自行建立,使用完畢不須要銷燬線程而是放回池中,從而減小建立和銷燬線程對象的開銷。
Java 5+中的Executor接口定義一個執行線程的工具。它的子類型即線程池接口是ExecutorService。要配置一個線程池是比較複雜的,尤爲是對於線程池的原理不是很清楚的狀況下,所以在工具類Executors面提供了一些靜態工廠方法,生成一些經常使用的線程池,以下所示:

  • newSingleThreadExecutor:建立一個單線程的線程池。這個線程池只有一個線程在工做,也就是至關於單線程串行執行全部任務。若是這個惟一的線程由於異常結束,那麼會有一個新的線程來替代它。此線程池保證全部任務的執行順序按照任務的提交順序執行。
  • newFixedThreadPool:建立固定大小的線程池。每次提交一個任務就建立一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,若是某個線程由於執行異常而結束,那麼線程池會補充一個新線程。
  • newCachedThreadPool:建立一個可緩存的線程池。若是線程池的大小超過了處理任務所須要的線程,那麼就會回收部分空閒(60秒不執行任務)的線程,當任務數增長時,此線程池又能夠智能的添加新線程來處理任務。此線程池不會對線程池大小作限制,線程池大小徹底依賴於操做系統(或者說JVM)可以建立的最大線程大小。
  • newScheduledThreadPool:建立一個大小無限的線程池。此線程池支持定時以及週期性執行任務的需求。
  • newSingleThreadExecutor:建立一個單線程的線程池。此線程池支持定時以及週期性執行任務的需求。

第60題的例子中演示了經過Executors工具類建立線程池並使用線程池執行線程的代碼。若是但願在服務器上使用線程池,強烈建議使用newFixedThreadPool方法來建立線程池,這樣能得到更好的性能。

6六、線程的基本狀態以及狀態之間的關係?
答:

說明:其中Running表示運行狀態,Runnable表示就緒狀態(萬事俱備,只欠CPU),Blocked表示阻塞狀態,阻塞狀態又有多種狀況,多是由於調用wait()方法進入等待池,也多是執行同步方法或同步代碼塊進入等鎖池,或者是調用了sleep()方法或join()方法等待休眠或其餘線程結束,或是由於發生了I/O中斷。

6七、簡述synchronized 和java.util.concurrent.locks.Lock的異同?
答:Lock是Java 5之後引入的新的API,和關鍵字synchronized相比主要相同點:Lock 能完成synchronized所實現的全部功能;主要不一樣點:Lock有比synchronized更精確的線程語義和更好的性能,並且不強制性的要求必定要得到鎖。synchronized會自動釋放鎖,而Lock必定要求程序員手工釋放,而且最好在finally 塊中釋放(這是釋放外部資源的最好的地方)。

6八、Java中如何實現序列化,有什麼意義?
答:序列化就是一種用來處理對象流的機制,所謂對象流也就是將對象的內容進行流化。能夠對流化後的對象進行讀寫操做,也可將流化後的對象傳輸於網絡之間。序列化是爲了解決對象流讀寫操做時可能引起的問題(若是不進行序列化可能會存在數據亂序的問題)。
要實現序列化,須要讓一個類實現Serializable接口,該接口是一個標識性接口,標註該類對象是可被序列化的,而後使用一個輸出流來構造一個對象輸出流並經過writeObject(Object)方法就能夠將實現對象寫出(即保存其狀態);若是須要反序列化則能夠用一個輸入流創建對象輸入流,而後經過readObject方法從流中讀取對象。序列化除了可以實現對象的持久化以外,還可以用於對象的深度克隆(能夠參考第29題)。

6九、Java中有幾種類型的流?
答:字節流和字符流。字節流繼承於InputStream、OutputStream,字符流繼承於Reader、Writer。在java.io 包中還有許多其餘的流,主要是爲了提升性能和使用方便。關於Java的I/O須要注意的有兩點:一是兩種對稱性(輸入和輸出的對稱性,字節和字符的對稱性);二是兩種設計模式(適配器模式和裝潢模式)。另外Java中的流不一樣於C#的是它只有一個維度一個方向。

面試題 - 編程實現文件拷貝。(這個題目在筆試的時候常常出現,下面的代碼給出了兩種實現方案)

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public final class MyUtil {

private MyUtil() {
    throw new AssertionError();
}

public static void fileCopy(String source, String target) throws IOException {
    try (InputStream in = new FileInputStream(source)) {
        try (OutputStream out = new FileOutputStream(target)) {
            byte[] buffer = new byte[4096];
            int bytesToRead;
            while((bytesToRead = in.read(buffer)) != -1) {
                out.write(buffer, 0, bytesToRead);
            }
        }
    }
}

public static void fileCopyNIO(String source, String target) throws IOException {
    try (FileInputStream in = new FileInputStream(source)) {
        try (FileOutputStream out = new FileOutputStream(target)) {
            FileChannel inChannel = in.getChannel();
            FileChannel outChannel = out.getChannel();
            ByteBuffer buffer = ByteBuffer.allocate(4096);
            while(inChannel.read(buffer) != -1) {
                buffer.flip();
                outChannel.write(buffer);
                buffer.clear();
            }
        }
    }
}

}

注意:上面用到Java 7的TWR,使用TWR後能夠不用在finally中釋放外部資源 ,從而讓代碼更加優雅。

70、寫一個方法,輸入一個文件名和一個字符串,統計這個字符串在這個文件中出現的次數。
答:代碼以下:

import java.io.BufferedReader;
import java.io.FileReader;

public final class MyUtil {

// 工具類中的方法都是靜態方式訪問的所以將構造器私有不容許建立對象(絕對好習慣)
private MyUtil() {
    throw new AssertionError();
}

/**
 * 統計給定文件中給定字符串的出現次數
 * 
 * @param filename  文件名
 * @param word 字符串
 * @return 字符串在文件中出現的次數
 */
public static int countWordInFile(String filename, String word) {
    int counter = 0;
    try (FileReader fr = new FileReader(filename)) {
        try (BufferedReader br = new BufferedReader(fr)) {
            String line = null;
            while ((line = br.readLine()) != null) {
                int index = -1;
                while (line.length() >= word.length() && (index = line.indexOf(word)) >= 0) {
                    counter++;
                    line = line.substring(index + word.length());
                }
            }
        }
    } catch (Exception ex) {
        ex.printStackTrace();
    }
    return counter;
}

}

7一、如何用Java代碼列出一個目錄下全部的文件?
答:
若是隻要求列出當前文件夾下的文件,代碼以下所示:

import java.io.File;

class Test12 {

public static void main(String[] args) {
    File f = new File("/Users/Hao/Downloads");
    for(File temp : f.listFiles()) {
        if(temp.isFile()) {
            System.out.println(temp.getName());
        }
    }
}

}

若是須要對文件夾繼續展開,代碼以下所示:

import java.io.File;

class Test12 {

public static void main(String[] args) {
    showDirectory(new File("/Users/Hao/Downloads"));
}

public static void showDirectory(File f) {
    _walkDirectory(f, 0);
}

private static void _walkDirectory(File f, int level) {
    if(f.isDirectory()) {
        for(File temp : f.listFiles()) {
            _walkDirectory(temp, level + 1);
        }
    }
    else {
        for(int i = 0; i < level - 1; i++) {
            System.out.print("\t");
        }
        System.out.println(f.getName());
    }
}

}

在Java 7中能夠使用NIO.2的API來作一樣的事情,代碼以下所示:

class ShowFileTest {

public static void main(String[] args) throws IOException {
    Path initPath = Paths.get("/Users/Hao/Downloads");
    Files.walkFileTree(initPath, new SimpleFileVisitor<Path>() {

        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
                throws IOException {
            System.out.println(file.getFileName().toString());
            return FileVisitResult.CONTINUE;
        }

    });
}

}

7二、用Java的套接字編程實現一個多線程的回顯(echo)服務器。
答:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class EchoServer {

private static final int ECHO_SERVER_PORT = 6789;

public static void main(String[] args) {        
    try(ServerSocket server = new ServerSocket(ECHO_SERVER_PORT)) {
        System.out.println("服務器已經啓動...");
        while(true) {
            Socket client = server.accept();
            new Thread(new ClientHandler(client)).start();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

private static class ClientHandler implements Runnable {
    private Socket client;

    public ClientHandler(Socket client) {
        this.client = client;
    }

    @Override
    public void run() {
        try(BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
                PrintWriter pw = new PrintWriter(client.getOutputStream())) {
            String msg = br.readLine();
            System.out.println("收到" + client.getInetAddress() + "發送的: " + msg);
            pw.println(msg);
            pw.flush();
        } catch(Exception ex) {
            ex.printStackTrace();
        } finally {
            try {
                client.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

}

注意:上面的代碼使用了Java 7的TWR語法,因爲不少外部資源類都間接的實現了AutoCloseable接口(單方法回調接口),所以能夠利用TWR語法在try結束的時候經過回調的方式自動調用外部資源類的close()方法,避免書寫冗長的finally代碼塊。此外,上面的代碼用一個靜態內部類實現線程的功能,使用多線程能夠避免一個用戶I/O操做所產生的中斷影響其餘用戶對服務器的訪問,簡單的說就是一個用戶的輸入操做不會形成其餘用戶的阻塞。固然,上面的代碼使用線程池能夠得到更好的性能,由於頻繁的建立和銷燬線程所形成的開銷也是不可忽視的。

下面是一段回顯客戶端測試代碼:

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class EchoClient {

public static void main(String[] args) throws Exception {
    Socket client = new Socket("localhost", 6789);
    Scanner sc = new Scanner(System.in);
    System.out.print("請輸入內容: ");
    String msg = sc.nextLine();
    sc.close();
    PrintWriter pw = new PrintWriter(client.getOutputStream());
    pw.println(msg);
    pw.flush();
    BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
    System.out.println(br.readLine());
    client.close();
}

}

若是但願用NIO的多路複用套接字實現服務器,代碼以下所示。NIO的操做雖然帶來了更好的性能,可是有些操做是比較底層的,對於初學者來講仍是有些難於理解。

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

public class EchoServerNIO {

private static final int ECHO_SERVER_PORT = 6789;
private static final int ECHO_SERVER_TIMEOUT = 5000;
private static final int BUFFER_SIZE = 1024;

private static ServerSocketChannel serverChannel = null;
private static Selector selector = null;    // 多路複用選擇器
private static ByteBuffer buffer = null;    // 緩衝區

public static void main(String[] args) {
    init();
    listen();
}

private static void init() {
    try {
        serverChannel = ServerSocketChannel.open();
        buffer = ByteBuffer.allocate(BUFFER_SIZE);
        serverChannel.socket().bind(new InetSocketAddress(ECHO_SERVER_PORT));
        serverChannel.configureBlocking(false);
        selector = Selector.open();
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

private static void listen() {
    while (true) {
        try {
            if (selector.select(ECHO_SERVER_TIMEOUT) != 0) {
                Iterator<SelectionKey> it = selector.selectedKeys().iterator();
                while (it.hasNext()) {
                    SelectionKey key = it.next();
                    it.remove();
                    handleKey(key);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

private static void handleKey(SelectionKey key) throws IOException {
    SocketChannel channel = null;

    try {
        if (key.isAcceptable()) {
            ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
            channel = serverChannel.accept();
            channel.configureBlocking(false);
            channel.register(selector, SelectionKey.OP_READ);
        } else if (key.isReadable()) {
            channel = (SocketChannel) key.channel();
            buffer.clear();
            if (channel.read(buffer) > 0) {
                buffer.flip();
                CharBuffer charBuffer = CharsetHelper.decode(buffer);
                String msg = charBuffer.toString();
                System.out.println("收到" + channel.getRemoteAddress() + "的消息:" + msg);
                channel.write(CharsetHelper.encode(CharBuffer.wrap(msg)));
            } else {
                channel.close();
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
        if (channel != null) {
            channel.close();
        }
    }
}

}

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;

public final class CharsetHelper {
private static final String UTF_8 = 「UTF-8」;
private static CharsetEncoder encoder = Charset.forName(UTF_8).newEncoder();
private static CharsetDecoder decoder = Charset.forName(UTF_8).newDecoder();

private CharsetHelper() {
}

public static ByteBuffer encode(CharBuffer in) throws CharacterCodingException{
    return encoder.encode(in);
}

public static CharBuffer decode(ByteBuffer in) throws CharacterCodingException{
    return decoder.decode(in);
}

}

7三、XML文檔定義有幾種形式?它們之間有何本質區別?解析XML文檔有哪幾種方式?
答:XML文檔定義分爲DTD和Schema兩種形式,兩者都是對XML語法的約束,其本質區別在於Schema自己也是一個XML文件,能夠被XML解析器解析,並且能夠爲XML承載的數據定義類型,約束能力較之DTD更強大。對XML的解析主要有DOM(文檔對象模型,DocumentObjectModel)、SAX(Simple API for XML)和StAX(Java 6中引入的新的解析XML的方式,StreamingAPI forXML),其中DOM處理大型文件時其性能降低的很是厲害,這個問題是由DOM樹結構佔用的內存較多形成的,並且DOM解析方式必須在解析文件以前把整個文檔裝入內存,適合對XML的隨機訪問(典型的用空間換取時間的策略);SAX是事件驅動型的XML解析方式,它順序讀取XML文件,不須要一次所有裝載整個文件。當遇到像文件開頭,文檔結束,或者標籤開頭與標籤結束時,它會觸發一個事件,用戶經過事件回調代碼來處理XML文件,適合對XML的順序訪問;顧名思義,StAX把重點放在流上,實際上StAX與其餘解析方式的本質區別就在於應用程序可以把XML做爲一個事件流來處理。將XML做爲一組事件來處理的想法並不新穎(SAX就是這樣作的),但不一樣之處在於StAX容許應用程序代碼把這些事件逐個拉出來,而不用提供在解析器方便時從解析器中接收事件的處理程序。

7四、你在項目中哪些地方用到了XML?
答:XML的主要做用有兩個方面:數據交換和信息配置。在作數據交換時,XML將數據用標籤組裝成起來,而後壓縮打包加密後經過網絡傳送給接收者,接收解密與解壓縮後再從XML文件中還原相關信息進行處理,XML曾經是異構系統間交換數據的事實標準,但此項功能幾乎已經被JSON(JavaScriptObjectNotation)取而代之。固然,目前不少軟件仍然使用XML來存儲配置信息,咱們在不少項目中一般也會將做爲配置信息的硬代碼寫在XML文件中,Java的不少框架也是這麼作的,並且這些框架都選擇了dom4j做爲處理XML的工具,由於Sun公司的官方API實在不怎麼好用。

補充:如今有不少時髦的軟件(如Sublime)已經開始將配置文件書寫成JSON格式,咱們已經強烈的感覺到XML的另外一項功能也將逐漸被業界拋棄。

7五、闡述JDBC操做數據庫的步驟。
答:下面的代碼以鏈接本機的Oracle數據庫爲例,演示JDBC操做數據庫的步驟。

加載驅動。

Class.forName(「oracle.jdbc.driver.OracleDriver」);

建立鏈接。

Connection con = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl", "scott", "tiger");

建立語句。

PreparedStatement ps = con.prepareStatement(「select * from emp where sal between ? and ?」);
ps.setInt(1, 1000);
ps.setInt(2, 3000);

執行語句。

ResultSet rs = ps.executeQuery();

處理結果。

while(rs.next()) {
System.out.println(rs.getInt(「empno」) + " - " + rs.getString(「ename」));
}

關閉資源。

finally {
if(con != null) {
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}

提示:關閉外部資源的順序應該和打開的順序相反,也就是說先關閉ResultSet、再關閉Statement、在關閉Connection。上面的代碼只關閉了Connection(鏈接),雖然一般狀況下在關閉鏈接時,鏈接上建立的語句和打開的遊標也會關閉,但不能保證老是如此,所以應該按照剛纔說的順序分別關閉。此外,第一步加載驅動在JDBC 4.0中是能夠省略的(自動從類路徑中加載驅動),可是咱們建議保留。

7六、Statement和PreparedStatement有什麼區別?哪一個性能更好?
答:與Statement相比,①PreparedStatement接口表明預編譯的語句,它主要的優點在於能夠減小SQL的編譯錯誤並增長SQL的安全性(減小SQL注射攻擊的可能性);②PreparedStatement中的SQL語句是能夠帶參數的,避免了用字符串鏈接拼接SQL語句的麻煩和不安全;③當批量處理SQL或頻繁執行相同的查詢時,PreparedStatement有明顯的性能上的優點,因爲數據庫能夠將編譯優化後的SQL語句緩存起來,下次執行相同結構的語句時就會很快(不用再次編譯和生成執行計劃)。

補充:爲了提供對存儲過程的調用,JDBC API中還提供了CallableStatement接口。存儲過程(Stored Procedure)是數據庫中一組爲了完成特定功能的SQL語句的集合,經編譯後存儲在數據庫中,用戶經過指定存儲過程的名字並給出參數(若是該存儲過程帶有參數)來執行它。雖然調用存儲過程會在網絡開銷、安全性、性能上得到不少好處,可是存在若是底層數據庫發生遷移時就會有不少麻煩,由於每種數據庫的存儲過程在書寫上存在很多的差異。

7七、使用JDBC操做數據庫時,如何提高讀取數據的性能?如何提高更新數據的性能?
答:要提高讀取數據的性能,能夠指定經過結果集(ResultSet)對象的setFetchSize()方法指定每次抓取的記錄數(典型的空間換時間策略);要提高更新數據的性能能夠使用PreparedStatement語句構建批處理,將若干SQL語句置於一個批處理中執行。

7八、在進行數據庫編程時,鏈接池有什麼做用?
答:因爲建立鏈接和釋放鏈接都有很大的開銷(尤爲是數據庫服務器不在本地時,每次創建鏈接都須要進行TCP的三次握手,釋放鏈接須要進行TCP四次握手,形成的開銷是不可忽視的),爲了提高系統訪問數據庫的性能,能夠事先建立若干鏈接置於鏈接池中,須要時直接從鏈接池獲取,使用結束時歸還鏈接池而沒必要關閉鏈接,從而避免頻繁建立和釋放鏈接所形成的開銷,這是典型的用空間換取時間的策略(浪費了空間存儲鏈接,但節省了建立和釋放鏈接的時間)。池化技術在Java開發中是很常見的,在使用線程時建立線程池的道理與此相同。基於Java的開源數據庫鏈接池主要有:C3P0、Proxool、DBCP、BoneCP、Druid等。

補充:在計算機系統中時間和空間是不可調和的矛盾,理解這一點對設計知足性能要求的算法是相當重要的。大型網站性能優化的一個關鍵就是使用緩存,而緩存跟上面講的鏈接池道理很是相似,也是使用空間換時間的策略。能夠將熱點數據置於緩存中,當用戶查詢這些數據時能夠直接從緩存中獲得,這不管如何也快過去數據庫中查詢。固然,緩存的置換策略等也會對系統性能產生重要影響,對於這個問題的討論已經超出了這裏要闡述的範圍。

7九、什麼是DAO模式?
答:DAO(Data Access Object)顧名思義是一個爲數據庫或其餘持久化機制提供了抽象接口的對象,在不暴露底層持久化方案實現細節的前提下提供了各類數據訪問操做。在實際的開發中,應該將全部對數據源的訪問操做進行抽象化後封裝在一個公共API中。用程序設計語言來講,就是創建一個接口,接口中定義了此應用程序中將會用到的全部事務方法。在這個應用程序中,當須要和數據源進行交互的時候則使用這個接口,而且編寫一個單獨的類來實現這個接口,在邏輯上該類對應一個特定的數據存儲。DAO模式實際上包含了兩個模式,一是Data Accessor(數據訪問器),二是Data Object(數據對象),前者要解決如何訪問數據的問題,然後者要解決的是如何用對象封裝數據。

80、事務的ACID是指什麼? 答: - 原子性(Atomic):事務中各項操做,要麼全作要麼全不作,任何一項操做的失敗都會致使整個事務的失敗; - 一致性(Consistent):事務結束後系統狀態是一致的; - 隔離性(Isolated):併發執行的事務彼此沒法看到對方的中間狀態; - 持久性(Durable):事務完成後所作的改動都會被持久化,即便發生災難性的失敗。經過日誌和同步備份能夠在故障發生後重建數據。

補充:關於事務,在面試中被問到的機率是很高的,能夠問的問題也是不少的。首先須要知道的是,只有存在併發數據訪問時才須要事務。當多個事務訪問同一數據時,可能會存在5類問題,包括3類數據讀取問題(髒讀、不可重複讀和幻讀)和2類數據更新問題(第1類丟失更新和第2類丟失更新)。

髒讀(Dirty Read):A事務讀取B事務還沒有提交的數據並在此基礎上操做,而B事務執行回滾,那麼A讀取到的數據就是髒數據。

時間 轉帳事務A 取款事務B
T1
開始事務
T2 開始事務
T3
查詢帳戶餘額爲1000元
T4
取出500元餘額修改成500元
T5 查詢帳戶餘額爲500元(髒讀)
T6
撤銷事務餘額恢復爲1000元
T7 匯入100元把餘額修改成600元
T8 提交事務
不可重複讀(Unrepeatable Read):事務A從新讀取前面讀取過的數據,發現該數據已經被另外一個已提交的事務B修改過了。

時間 轉帳事務A 取款事務B
T1
開始事務
T2 開始事務
T3
查詢帳戶餘額爲1000元
T4 查詢帳戶餘額爲1000元
T5
取出100元修改餘額爲900元
T6
提交事務
T7 查詢帳戶餘額爲900元(不可重複讀)
幻讀(Phantom Read):事務A從新執行一個查詢,返回一系列符合查詢條件的行,發現其中插入了被事務B提交的行。

時間 統計金額事務A 轉帳事務B
T1
開始事務
T2 開始事務
T3 統計總存款爲10000元
T4
新增一個存款帳戶存入100元
T5
提交事務
T6 再次統計總存款爲10100元(幻讀)
第1類丟失更新:事務A撤銷時,把已經提交的事務B的更新數據覆蓋了。

時間 取款事務A 轉帳事務B
T1 開始事務
T2
開始事務
T3 查詢帳戶餘額爲1000元
T4
查詢帳戶餘額爲1000元
T5
匯入100元修改餘額爲1100元
T6
提交事務
T7 取出100元將餘額修改成900元
T8 撤銷事務
T9 餘額恢復爲1000元(丟失更新)
第2類丟失更新:事務A覆蓋事務B已經提交的數據,形成事務B所作的操做丟失。

時間 轉帳事務A 取款事務B
T1
開始事務
T2 開始事務
T3
查詢帳戶餘額爲1000元
T4 查詢帳戶餘額爲1000元
T5
取出100元將餘額修改成900元
T6
提交事務
T7 匯入100元將餘額修改成1100元
T8 提交事務
T9 查詢帳戶餘額爲1100元(丟失更新)
數據併發訪問所產生的問題,在有些場景下多是容許的,可是有些場景下可能就是致命的,數據庫一般會經過鎖機制來解決數據併發訪問問題,按鎖定對象不一樣能夠分爲表級鎖和行級鎖;按併發事務鎖定關係能夠分爲共享鎖和獨佔鎖,具體的內容你們能夠自行查閱資料進行了解。直接使用鎖是很是麻煩的,爲此數據庫爲用戶提供了自動鎖機制,只要用戶指定會話的事務隔離級別,數據庫就會經過分析SQL語句而後爲事務訪問的資源加上合適的鎖,此外,數據庫還會維護這些鎖經過各類手段提升系統的性能,這些對用戶來講都是透明的(就是說你不用理解,事實上我確實也不知道)。ANSI/ISO SQL 92標準定義了4個等級的事務隔離級別,以下表所示:

隔離級別 髒讀 不可重複讀 幻讀 第一類丟失更新 第二類丟失更新
READ UNCOMMITED 容許 容許 容許 不容許 容許
READ COMMITTED 不容許 容許 容許 不容許 容許
REPEATABLE READ 不容許 不容許 容許 不容許 不容許
SERIALIZABLE 不容許 不容許 不容許 不容許 不容許
須要說明的是,事務隔離級別和數據訪問的併發性是對立的,事務隔離級別越高併發性就越差。因此要根據具體的應用來肯定合適的事務隔離級別,這個地方沒有萬能的原則。

8一、JDBC中如何進行事務處理?答:Connection提供了事務處理的方法,經過調用setAutoCommit(false)能夠設置手動提交事務;當事務完成後用commit()顯式提交事務;若是在事務處理過程當中發生異常則經過rollback()進行事務回滾。除此以外,從JDBC 3.0中還引入了Savepoint(保存點)的概念,容許經過代碼設置保存點並讓事務回滾到指定的保存點。這裏寫圖片描述

8一、JDBC中如何進行事務處理?
答:Connection提供了事務處理的方法,經過調用setAutoCommit(false)能夠設置手動提交事務;當事務完成後用commit()顯式提交事務;若是在事務處理過程當中發生異常則經過rollback()進行事務回滾。除此以外,從JDBC 3.0中還引入了Savepoint(保存點)的概念,容許經過代碼設置保存點並讓事務回滾到指定的保存點。
這裏寫圖片描述

8二、JDBC可否處理Blob和Clob?
答: Blob是指二進制大對象(Binary Large Object),而Clob是指大字符對象(Character Large Objec),所以其中Blob是爲存儲大的二進制數據而設計的,而Clob是爲存儲大的文本數據而設計的。JDBC的PreparedStatement和ResultSet都提供了相應的方法來支持Blob和Clob操做。下面的代碼展現瞭如何使用JDBC操做LOB:
下面以MySQL數據庫爲例,建立一個張有三個字段的用戶表,包括編號(id)、姓名(name)和照片(photo),建表語句以下:

create table tb_user
(
id int primary key auto_increment,
name varchar(20) unique not null,
photo longblob
);

下面的Java代碼向數據庫中插入一條記錄:

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

class JdbcLobTest {

public static void main(String[] args) {
    Connection con = null;
    try {
        // 1. 加載驅動(Java6以上版本能夠省略)
        Class.forName("com.mysql.jdbc.Driver");
        // 2. 創建鏈接
        con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");
        // 3. 建立語句對象
        PreparedStatement ps = con.prepareStatement("insert into tb_user values (default, ?, ?)");
        ps.setString(1, "駱昊");              // 將SQL語句中第一個佔位符換成字符串
        try (InputStream in = new FileInputStream("test.jpg")) {    // Java 7的TWR
            ps.setBinaryStream(2, in);      // 將SQL語句中第二個佔位符換成二進制流
            // 4. 發出SQL語句得到受影響行數
            System.out.println(ps.executeUpdate() == 1 ? "插入成功" : "插入失敗");
        } catch(IOException e) {
            System.out.println("讀取照片失敗!");
        }
    } catch (ClassNotFoundException | SQLException e) {     // Java 7的多異常捕獲
        e.printStackTrace();
    } finally { // 釋放外部資源的代碼都應當放在finally中保證其可以獲得執行
        try {
            if(con != null && !con.isClosed()) {
                con.close();    // 5. 釋放數據庫鏈接
                con = null;     // 指示垃圾回收器能夠回收該對象
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

}

8三、簡述正則表達式及其用途。
答:在編寫處理字符串的程序時,常常會有查找符合某些複雜規則的字符串的須要。正則表達式就是用於描述這些規則的工具。換句話說,正則表達式就是記錄文本規則的代碼。

說明:計算機誕生初期處理的信息幾乎都是數值,可是時過境遷,今天咱們使用計算機處理的信息更多的時候不是數值而是字符串,正則表達式就是在進行字符串匹配和處理的時候最爲強大的工具,絕大多數語言都提供了對正則表達式的支持。

8四、Java中是如何支持正則表達式操做的?
答:Java中的String類提供了支持正則表達式操做的方法,包括:matches()、replaceAll()、replaceFirst()、split()。此外,Java中能夠用Pattern類表示正則表達式對象,它提供了豐富的API進行各類正則表達式操做,請參考下面面試題的代碼。

面試題: - 若是要從字符串中截取第一個英文左括號以前的字符串,例如:北京市(朝陽區)(西城區)(海淀區),截取結果爲:北京市,那麼正則表達式怎麼寫?

import java.util.regex.Matcher;
import java.util.regex.Pattern;

class RegExpTest {

public static void main(String[] args) {
    String str = "北京市(朝陽區)(西城區)(海淀區)";
    Pattern p = Pattern.compile(".*?(?=\\()");
    Matcher m = p.matcher(str);
    if(m.find()) {
        System.out.println(m.group());
    }
}

}

說明:上面的正則表達式中使用了懶惰匹配和前瞻,若是不清楚這些內容,推薦讀一下網上頗有名的《正則表達式30分鐘入門教程》。

8五、得到一個類的類對象有哪些方式?
答:

  • 方法1:類型.class,例如:String.class
  • 方法2:對象.getClass(),例如:「hello」.getClass()
  • 方法3:Class.forName(),例如:Class.forName(「java.lang.String」)

8六、如何經過反射建立對象?
答:

  • 方法1:經過類對象調用newInstance()方法,例如:String.class.newInstance()
  • 方法2:經過類對象的getConstructor()或getDeclaredConstructor()方法得到構造器(Constructor)對象並調用其newInstance()方法建立對象,例如:String.class.getConstructor(String.class).newInstance(「Hello」);

8七、如何經過反射獲取和設置對象私有字段的值?
答:能夠經過類對象的getDeclaredField()方法字段(Field)對象,而後再經過字段對象的setAccessible(true)將其設置爲能夠訪問,接下來就能夠經過get/set方法來獲取/設置字段的值了。下面的代碼實現了一個反射的工具類,其中的兩個靜態方法分別用於獲取和設置私有字段的值,字段能夠是基本類型也能夠是對象類型且支持多級對象操做,例如ReflectionUtil.get(dog, 「owner.car.engine.id」);能夠得到dog對象的主人的汽車的引擎的ID號。

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

/**

  • 反射工具類
  • @author 駱昊

*/
public class ReflectionUtil {

private ReflectionUtil() {
    throw new AssertionError();
}

/**
 * 經過反射取對象指定字段(屬性)的值
 * @param target 目標對象
 * @param fieldName 字段的名字
 * @throws 若是取不到對象指定字段的值則拋出異常
 * @return 字段的值
 */
public static Object getValue(Object target, String fieldName) {
    Class<?> clazz = target.getClass();
    String[] fs = fieldName.split("\\.");

    try {
        for(int i = 0; i < fs.length - 1; i++) {
            Field f = clazz.getDeclaredField(fs[i]);
            f.setAccessible(true);
            target = f.get(target);
            clazz = target.getClass();
        }

        Field f = clazz.getDeclaredField(fs[fs.length - 1]);
        f.setAccessible(true);
        return f.get(target);
    }
    catch (Exception e) {
        throw new RuntimeException(e);
    }
}

/**
 * 經過反射給對象的指定字段賦值
 * @param target 目標對象
 * @param fieldName 字段的名稱
 * @param value 值
 */
public static void setValue(Object target, String fieldName, Object value) {
    Class<?> clazz = target.getClass();
    String[] fs = fieldName.split("\\.");
    try {
        for(int i = 0; i < fs.length - 1; i++) {
            Field f = clazz.getDeclaredField(fs[i]);
            f.setAccessible(true);
            Object val = f.get(target);
            if(val == null) {
                Constructor<?> c = f.getType().getDeclaredConstructor();
                c.setAccessible(true);
                val = c.newInstance();
                f.set(target, val);
            }
            target = val;
            clazz = target.getClass();
        }

        Field f = clazz.getDeclaredField(fs[fs.length - 1]);
        f.setAccessible(true);
        f.set(target, value);
    }
    catch (Exception e) {
        throw new RuntimeException(e);
    }
}

}

8八、如何經過反射調用對象的方法?
答:請看下面的代碼:

import java.lang.reflect.Method;

class MethodInvokeTest {

public static void main(String[] args) throws Exception {
    String str = "hello";
    Method m = str.getClass().getMethod("toUpperCase");
    System.out.println(m.invoke(str));  // HELLO
}

}

8九、簡述一下面向對象的"六原則一法則"。答: - 單一職責原則:一個類只作它該作的事情。(單一職責原則想表達的就是"高內聚",寫代碼最終極的原則只有六個字"高內聚、低耦合",就如同葵花寶典或辟邪劍譜的中心思想就八個字"欲練此功必先自宮",所謂的高內聚就是一個代碼模塊只完成一項功能,在面向對象中,若是隻讓一個類完成它該作的事,而不涉及與它無關的領域就是踐行了高內聚的原則,這個類就只有單一職責。咱們都知道一句話叫"由於專一,因此專業",一個對象若是承擔太多的職責,那麼註定它什麼都作很差。這個世界上任何好的東西都有兩個特徵,一個是功能單一,好的相機絕對不是電視購物裏面賣的那種一個機器有一百多種功能的,它基本上只能照相;另外一個是模塊化,好的自行車是組裝車,從減震叉、剎車到變速器,全部的部件都是能夠拆卸和從新組裝的,好的乒乓球拍也不是成品拍,必定是底板和膠皮能夠拆分和自行組裝的,一個好的軟件系統,它裏面的每一個功能模塊也應該是能夠輕易的拿到其餘系統中使用的,這樣才能實現軟件複用的目標。)- 開閉原則:軟件實體應當對擴展開放,對修改關閉。(在理想的狀態下,當咱們須要爲一個軟件系統增長新功能時,只須要從原來的系統派生出一些新類就能夠,不須要修改原來的任何一行代碼。要作到開閉有兩個要點:①抽象是關鍵,一個系統中若是沒有抽象類或接口系統就沒有擴展點;②封裝可變性,將系統中的各類可變因素封裝到一個繼承結構中,若是多個可變因素混雜在一塊兒,系統將變得複雜而換亂,若是不清楚如何封裝可變性,能夠參考《設計模式精解》一書中對橋樑模式的講解的章節。)- 依賴倒轉原則:面向接口編程。(該原則說得直白和具體一些就是聲明方法的參數類型、方法的返回類型、變量的引用類型時,儘量使用抽象類型而不用具體類型,由於抽象類型能夠被它的任何一個子類型所替代,請參考下面的里氏替換原則。)里氏替換原則:任什麼時候候均可以用子類型替換掉父類型。(關於里氏替換原則的描述,Barbara Liskov女士的描述比這個要複雜得多,但簡單的說就是能用父類型的地方就必定能使用子類型。里氏替換原則能夠檢查繼承關係是否合理,若是一個繼承關係違背了里氏替換原則,那麼這個繼承關係必定是錯誤的,須要對代碼進行重構。例如讓貓繼承狗,或者狗繼承貓,又或者讓正方形繼承長方形都是錯誤的繼承關係,由於你很容易找到違反里氏替換原則的場景。須要注意的是:子類必定是增長父類的能力而不是減小父類的能力,由於子類比父類的能力更多,把能力多的對象當成能力少的對象來用固然沒有任何問題。)- 接口隔離原則:接口要小而專,毫不能大而全。(臃腫的接口是對接口的污染,既然接口表示能力,那麼一個接口只應該描述一種能力,接口也應該是高度內聚的。例如,琴棋書畫就應該分別設計爲四個接口,而不該設計成一個接口中的四個方法,由於若是設計成一個接口中的四個方法,那麼這個接口很難用,畢竟琴棋書畫四樣都精通的人仍是少數,而若是設計成四個接口,會幾項就實現幾個接口,這樣的話每一個接口被複用的可能性是很高的。Java中的接口表明能力、表明約定、表明角色,可否正確的使用接口必定是編程水平高低的重要標識。)- 合成聚合複用原則:優先使用聚合或合成關係複用代碼。(經過繼承來複用代碼是面向對象程序設計中被濫用得最多的東西,由於全部的教科書都無一例外的對繼承進行了鼓吹從而誤導了初學者,類與類之間簡單的說有三種關係,Is-A關係、Has-A關係、Use-A關係,分別表明繼承、關聯和依賴。其中,關聯關係根據其關聯的強度又能夠進一步劃分爲關聯、聚合和合成,但說白了都是Has-A關係,合成聚合複用原則想表達的是優先考慮Has-A關係而不是Is-A關係複用代碼,緣由嘛能夠本身從百度上找到一萬個理由,須要說明的是,即便在Java的API中也有很多濫用繼承的例子,例如Properties類繼承了Hashtable類,Stack類繼承了Vector類,這些繼承明顯就是錯誤的,更好的作法是在Properties類中放置一個Hashtable類型的成員而且將其鍵和值都設置爲字符串來存儲數據,而Stack類的設計也應該是在Stack類中放一個Vector對象來存儲數據。記住:任什麼時候候都不要繼承工具類,工具是能夠擁有並能夠使用的,而不是拿來繼承的。)- 迪米特法則:迪米特法則又叫最少知識原則,一個對象應當對其餘對象有儘量少的瞭解。(迪米特法則簡單的說就是如何作到"低耦合",門面模式和調停者模式就是對迪米特法則的踐行。對於門面模式能夠舉一個簡單的例子,你去一家公司洽談業務,你不須要了解這個公司內部是如何運做的,你甚至能夠對這個公司一無所知,去的時候只須要找到公司入口處的前臺美女,告訴她們你要作什麼,她們會找到合適的人跟你接洽,前臺的美女就是公司這個系統的門面。再複雜的系統均可覺得用戶提供一個簡單的門面,Java Web開發中做爲前端控制器的Servlet或Filter不就是一個門面嗎,瀏覽器對服務器的運做方式一無所知,可是經過前端控制器就可以根據你的請求獲得相應的服務。調停者模式也能夠舉一個簡單的例子來講明,例如一臺計算機,CPU、內存、硬盤、顯卡、聲卡各類設備須要相互配合才能很好的工做,可是若是這些東西都直接鏈接到一塊兒,計算機的佈線將異常複雜,在這種狀況下,主板做爲一個調停者的身份出現,它將各個設備鏈接在一塊兒而不須要每一個設備之間直接交換數據,這樣就減少了系統的耦合度和複雜度,以下圖所示。迪米特法則用通俗的話來將就是不要和陌生人打交道,若是真的須要,找一個本身的朋友,讓他替你和陌生人打交道。)

這裏寫圖片描述這裏寫圖片描述

90、簡述一下你瞭解的設計模式。
答:所謂設計模式,就是一套被反覆使用的代碼設計經驗的總結(情境中一個問題通過證明的一個解決方案)。使用設計模式是爲了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。設計模式令人們能夠更加簡單方便的複用成功的設計和體系結構。將已證明的技術表述成設計模式也會使新系統開發者更加容易理解其設計思路。
在GoF的《Design Patterns: Elements of Reusable Object-Oriented Software》中給出了三類(建立型[對類的實例化過程的抽象化]、結構型[描述如何將類或對象結合在一塊兒造成更大的結構]、行爲型[對在不一樣的對象之間劃分責任和算法的抽象化])共23種設計模式,包括:Abstract Factory(抽象工廠模式),Builder(建造者模式),Factory Method(工廠方法模式),Prototype(原始模型模式),Singleton(單例模式);Facade(門面模式),Adapter(適配器模式),Bridge(橋樑模式),Composite(合成模式),Decorator(裝飾模式),Flyweight(享元模式),Proxy(代理模式);Command(命令模式),Interpreter(解釋器模式),Visitor(訪問者模式),Iterator(迭代子模式),Mediator(調停者模式),Memento(備忘錄模式),Observer(觀察者模式),State(狀態模式),Strategy(策略模式),Template Method(模板方法模式), Chain Of Responsibility(責任鏈模式)。
面試被問到關於設計模式的知識時,能夠揀最經常使用的做答,例如:

  • 工廠模式:工廠類能夠根據條件生成不一樣的子類實例,這些子類有一個公共的抽象父類而且實現了相同的方法,可是這些方法針對不一樣的數據進行了不一樣的操做(多態方法)。當獲得子類的實例後,開發人員能夠調用基類中的方法而沒必要考慮到底返回的是哪個子類的實例。
  • 代理模式:給一個對象提供一個代理對象,並由代理對象控制原對象的引用。實際開發中,按照使用目的的不一樣,代理能夠分爲:遠程代理、虛擬代理、保護代理、Cache代理、防火牆代理、同步化代理、智能引用代理。
  • 適配器模式:把一個類的接口變換成客戶端所期待的另外一種接口,從而使本來因接口不匹配而沒法在一塊兒使用的類可以一塊兒工做。
  • 模板方法模式:提供一個抽象類,將部分邏輯以具體方法或構造器的形式實現,而後聲明一些抽象方法來迫使子類實現剩餘的邏輯。不一樣的子類能夠以不一樣的方式實現這些抽象方法(多態實現),從而實現不一樣的業務邏輯。
    除此以外,還能夠講講上面提到的門面模式、橋樑模式、單例模式、裝潢模式(Collections工具類和I/O系統中都使用裝潢模式)等,反正基本原則就是揀本身最熟悉的、用得最多的做答,以避免言多必失。

9一、用Java寫一個單例類。
答:

  • 餓漢式單例

public class Singleton {
private Singleton(){}
private static Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
}

懶漢式單例

public class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static synchronized Singleton getInstance(){
if (instance == null) instance = new Singleton();
return instance;
}
}

注意:實現一個單例有兩點注意事項,①將構造器私有,不容許外界經過構造器建立對象;②經過公開的靜態方法向外界返回類的惟一實例。這裏有一個問題能夠思考:Spring的IoC容器能夠爲普通的類建立單例,它是怎麼作到的呢?

9二、什麼是UML?
答:UML是統一建模語言(Unified Modeling Language)的縮寫,它發表於1997年,綜合了當時已經存在的面向對象的建模語言、方法和過程,是一個支持模型化和軟件系統開發的圖形化語言,爲軟件開發的全部階段提供模型化和可視化支持。使用UML能夠幫助溝通與交流,輔助應用設計和文檔的生成,還可以闡釋系統的結構和行爲。

9三、UML中有哪些經常使用的圖? 答:UML定義了多種圖形化的符號來描述軟件系統部分或所有的靜態結構和動態結構,包括:用例圖(use case diagram)、類圖(class diagram)、時序圖(sequence diagram)、協做圖(collaboration diagram)、狀態圖(statechart diagram)、活動圖(activity diagram)、構件圖(component diagram)、部署圖(deployment diagram)等。在這些圖形化符號中,有三種圖最爲重要,分別是:用例圖(用來捕獲需求,描述系統的功能,經過該圖能夠迅速的瞭解系統的功能模塊及其關係)、類圖(描述類以及類與類之間的關係,經過該圖能夠快速瞭解系統)、時序圖(描述執行特定任務時對象之間的交互關係以及執行順序,經過該圖能夠了解對象能接收的消息也就是說對象可以向外界提供的服務)。用例圖: 這裏寫圖片描述 類圖: 這裏寫圖片描述 時序圖: 這裏寫圖片描述

9四、用Java寫一個冒泡排序。
答:冒泡排序幾乎是個程序員都寫得出來,可是面試的時候如何寫一個逼格高的冒泡排序卻不是每一個人都能作到,下面提供一個參考代碼:

import java.util.Comparator;

/**

  • 排序器接口(策略模式: 將算法封裝到具備共同接口的獨立的類中使得它們能夠相互替換)
  • @author駱昊

*/
public interface Sorter {

/**
* 排序
* @param list 待排序的數組
*/
public <T extends Comparable> void sort(T[] list);

/**
* 排序
* @param list 待排序的數組
* @param comp 比較兩個對象的比較器
*/
public void sort(T[] list, Comparator comp);
}

import java.util.Comparator;

/**

  • 冒泡排序
  • @author駱昊

*/
public class BubbleSorter implements Sorter {

@Override
public <T extends Comparable<T>> void sort(T[] list) {
    boolean swapped = true;
    for (int i = 1, len = list.length; i < len && swapped; ++i) {
        swapped = false;
        for (int j = 0; j < len - i; ++j) {
            if (list[j].compareTo(list[j + 1]) > 0) {
                T temp = list[j];
                list[j] = list[j + 1];
                list[j + 1] = temp;
                swapped = true;
            }
        }
    }
}

@Override
public <T> void sort(T[] list, Comparator<T> comp) {
    boolean swapped = true;
    for (int i = 1, len = list.length; i < len && swapped; ++i) {
        swapped = false;
        for (int j = 0; j < len - i; ++j) {
            if (comp.compare(list[j], list[j + 1]) > 0) {
                T temp = list[j];
                list[j] = list[j + 1];
                list[j + 1] = temp;
                swapped = true;
            }
        }
    }
}

}

9五、用Java寫一個折半查找。
答:折半查找,也稱二分查找、二分搜索,是一種在有序數組中查找某一特定元素的搜索算法。搜素過程從數組的中間元素開始,若是中間元素正好是要查找的元素,則搜素過程結束;若是某一特定元素大於或者小於中間元素,則在數組大於或小於中間元素的那一半中查找,並且跟開始同樣從中間元素開始比較。若是在某一步驟數組已經爲空,則表示找不到指定的元素。這種搜索算法每一次比較都使搜索範圍縮小一半,其時間複雜度是O(logN)。

import java.util.Comparator;

public class MyUtil {

public static <T extends Comparable> int binarySearch(T[] x, T key) {
return binarySearch(x, 0, x.length- 1, key);
}

// 使用循環實現的二分查找
public static int binarySearch(T[] x, T key, Comparator comp) {
int low = 0;
int high = x.length - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
int cmp = comp.compare(x[mid], key);
if (cmp < 0) {
low= mid + 1;
}
else if (cmp > 0) {
high= mid - 1;
}
else {
return mid;
}
}
return -1;
}

// 使用遞歸實現的二分查找
private static<T extends Comparable> int binarySearch(T[] x, int low, int high, T key) {
if(low <= high) {
int mid = low + ((high -low) >> 1);
if(key.compareTo(x[mid])== 0) {
return mid;
}
else if(key.compareTo(x[mid])< 0) {
return binarySearch(x,low, mid - 1, key);
}
else {
return binarySearch(x,mid + 1, high, key);
}
}
return -1;
}
}

說明:上面的代碼中給出了折半查找的兩個版本,一個用遞歸實現,一個用循環實現。須要注意的是計算中間位置時不該該使用(high+ low) / 2的方式,由於加法運算可能致使整數越界,這裏應該使用如下三種方式之一:low + (high - low) / 2或low + (high – low) >> 1或(low + high) >>> 1(>>>是邏輯右移,是不帶符號位的右移)

這部分主要是與Java Web和Web Service相關的面試題。

9六、闡述Servlet和CGI的區別?
答:Servlet與CGI的區別在於Servlet處於服務器進程中,它經過多線程方式運行其service()方法,一個實例能夠服務於多個請求,而且其實例通常不會銷燬,而CGI對每一個請求都產生新的進程,服務完成後就銷燬,因此效率上低於Servlet。

補充:Sun Microsystems公司在1996年發佈Servlet技術就是爲了和CGI進行競爭,Servlet是一個特殊的Java程序,一個基於Java的Web應用一般包含一個或多個Servlet類。Servlet不可以自行建立並執行,它是在Servlet容器中運行的,容器將用戶的請求傳遞給Servlet程序,並將Servlet的響應回傳給用戶。一般一個Servlet會關聯一個或多個JSP頁面。之前CGI常常由於性能開銷上的問題被詬病,然而Fast CGI早就已經解決了CGI效率上的問題,因此面試的時候大可沒必要信口開河的詬病CGI,事實上有不少你熟悉的網站都使用了CGI技術。

9七、Servlet接口中有哪些方法?
答:Servlet接口定義了5個方法,其中前三個方法與Servlet生命週期相關:

  • void init(ServletConfig config) throws ServletException
  • void service(ServletRequest req, ServletResponse resp) throws ServletException, java.io.IOException
  • void destory()
  • java.lang.String getServletInfo()
  • ServletConfig getServletConfig()

Web容器加載Servlet並將其實例化後,Servlet生命週期開始,容器運行其init()方法進行Servlet的初始化;請求到達時調用Servlet的service()方法,service()方法會根據須要調用與請求對應的doGet或doPost等方法;當服務器關閉或項目被卸載時服務器會將Servlet實例銷燬,此時會調用Servlet的destroy()方法。

9八、轉發(forward)和重定向(redirect)的區別?
答:forward是容器中控制權的轉向,是服務器請求資源,服務器直接訪問目標地址的URL,把那個URL 的響應內容讀取過來,而後把這些內容再發給瀏覽器,瀏覽器根本不知道服務器發送的內容是從哪兒來的,因此它的地址欄中仍是原來的地址。redirect就是服務器端根據邏輯,發送一個狀態碼,告訴瀏覽器從新去請求那個地址,所以從瀏覽器的地址欄中能夠看到跳轉後的連接地址,很明顯redirect沒法訪問到服務器保護起來資源,可是能夠從一個網站redirect到其餘網站。forward更加高效,因此在知足須要時儘可能使用forward(經過調用RequestDispatcher對象的forward()方法,該對象能夠經過ServletRequest對象的getRequestDispatcher()方法得到),而且這樣也有助於隱藏實際的連接;在有些狀況下,好比須要訪問一個其它服務器上的資源,則必須使用重定向(經過HttpServletResponse對象調用其sendRedirect()方法實現)。

9九、JSP有哪些內置對象?做用分別是什麼?
答:JSP有9個內置對象:

  • request:封裝客戶端的請求,其中包含來自GET或POST請求的參數;

  • response:封裝服務器對客戶端的響應;

  • pageContext:經過該對象能夠獲取其餘對象;

  • session:封裝用戶會話的對象;

  • application:封裝服務器運行環境的對象;

  • out:輸出服務器響應的輸出流對象;

  • config:Web應用的配置對象;

  • page:JSP頁面自己(至關於Java程序中的this);

  • exception:封裝頁面拋出異常的對象。

    補充:若是用Servlet來生成網頁中的動態內容無疑是很是繁瑣的工做,另外一方面,全部的文本和HTML標籤都是硬編碼,即便作出微小的修改,都須要進行從新編譯。JSP解決了Servlet的這些問題,它是Servlet很好的補充,能夠專門用做爲用戶呈現視圖(View),而Servlet做爲控制器(Controller)專門負責處理用戶請求並轉發或重定向到某個頁面。基於Java的Web開發不少都同時使用了Servlet和JSP。JSP頁面實際上是一個Servlet,可以運行Servlet的服務器(Servlet容器)一般也是JSP容器,能夠提供JSP頁面的運行環境,Tomcat就是一個Servlet/JSP容器。第一次請求一個JSP頁面時,Servlet/JSP容器首先將JSP頁面轉換成一個JSP頁面的實現類,這是一個實現了JspPage接口或其子接口HttpJspPage的Java類。JspPage接口是Servlet的子接口,所以每一個JSP頁面都是一個Servlet。轉換成功後,容器會編譯Servlet類,以後容器加載和實例化Java字節碼,並執行它一般對Servlet所作的生命週期操做。對同一個JSP頁面的後續請求,容器會查看這個JSP頁面是否被修改過,若是修改過就會從新轉換並從新編譯並執行。若是沒有則執行內存中已經存在的Servlet實例。咱們能夠看一段JSP代碼對應的Java程序就知道一切了,並且9個內置對象的神祕面紗也會被揭開。

JSP頁面:

<%@ page pageEncoding=「UTF-8」%>
<%
String path = request.getContextPath();
String basePath = request.getScheme() + 「😕/」 + request.getServerName() + 「:」 + request.getServerPort() + path + 「/」;
%>

首頁

Hello, World!


Current time is: <%= new java.util.Date().toString() %>

對應的Java代碼:

package org.apache.jsp;

import javax.servlet.;
import javax.servlet.http.
;
import javax.servlet.jsp.*;

public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase
implements org.apache.jasper.runtime.JspSourceDependent {

private static final javax.servlet.jsp.JspFactory _jspxFactory = javax.servlet.jsp.JspFactory
        .getDefaultFactory();

private static java.util.Map<java.lang.String, java.lang.Long> _jspx_dependants;

private javax.el.ExpressionFactory _el_expressionfactory;
private org.apache.tomcat.InstanceManager _jsp_instancemanager;

public java.util.Map<java.lang.String, java.lang.Long> getDependants() {
    return _jspx_dependants;
}

public void _jspInit() {
    _el_expressionfactory = _jspxFactory.getJspApplicationContext(
            getServletConfig().getServletContext()).getExpressionFactory();
    _jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory
            .getInstanceManager(getServletConfig());
}

public void _jspDestroy() {
}

public void _jspService(
        final javax.servlet.http.HttpServletRequest request,
        final javax.servlet.http.HttpServletResponse response)
        throws java.io.IOException, javax.servlet.ServletException {
    // 內置對象就是在這裏定義的
    final javax.servlet.jsp.PageContext pageContext;
    javax.servlet.http.HttpSession session = null;
    final javax.servlet.ServletContext application;
    final javax.servlet.ServletConfig config;
    javax.servlet.jsp.JspWriter out = null;
    final java.lang.Object page = this;
    javax.servlet.jsp.JspWriter _jspx_out = null;
    javax.servlet.jsp.PageContext _jspx_page_context = null;

    try {
        response.setContentType("text/html;charset=UTF-8");
        pageContext = _jspxFactory.getPageContext(this, request, response,
                null, true, 8192, true);
        _jspx_page_context = pageContext;
        application = pageContext.getServletContext();
        config = pageContext.getServletConfig();
        session = pageContext.getSession();
        out = pageContext.getOut();
        _jspx_out = out;

        out.write('\r');
        out.write('\n');

        String path = request.getContextPath();
        String basePath = request.getScheme() + "://"
                + request.getServerName() + ":" + request.getServerPort()
                + path + "/";

// 如下代碼經過輸出流將HTML標籤輸出到瀏覽器中
out.write("\r\n");
out.write("\r\n");
out.write("\r\n");
out.write("\r\n");
out.write(" \r\n");
out.write(" <base href="");
out.print(basePath);
out.write("">\r\n");
out.write(" 首頁\r\n");
out.write(" <style type=「text/css」>\r\n");
out.write(" \t* { font-family: 「Arial」; }\r\n");
out.write(" \r\n");
out.write(" \r\n");
out.write(" \r\n");
out.write(" \r\n");
out.write("

Hello, World!

\r\n");
out.write("
\r\n");
out.write("

Current time is: 「);
out.print(new java.util.Date().toString());
out.write(」

\r\n");
out.write(" \r\n");
out.write("\r\n");
} catch (java.lang.Throwable t) {
if (!(t instanceof javax.servlet.jsp.SkipPageException)) {
out = _jspx_out;
if (out != null && out.getBufferSize() != 0)
try {
out.clearBuffer();
} catch (java.io.IOException e) {
}
if (_jspx_page_context != null)
_jspx_page_context.handlePageException(t);
else
throw new ServletException(t);
}
} finally {
_jspxFactory.releasePageContext(_jspx_page_context);
}
}
}

100、get和post請求的區別?
答:
①get請求用來從服務器上得到資源,而post是用來向服務器提交數據;
②get將表單中數據按照name=value的形式,添加到action 所指向的URL 後面,而且二者使用"?「鏈接,而各個變量之間使用」&「鏈接;post是將表單中的數據放在HTTP協議的請求頭或消息體中,傳遞到action所指向URL;
③get傳輸的數據要受到URL長度限制(1024字節);而post能夠傳輸大量的數據,上傳文件一般要使用post方式;
④使用get時參數會顯示在地址欄上,若是這些數據不是敏感數據,那麼能夠使用get;對於敏感數據仍是應用使用post;
⑤get使用MIME類型application/x-www-form-urlencoded的URL編碼(也叫百分號編碼)文本的格式傳遞參數,保證被傳送的參數由遵循規範的文本組成,例如一個空格的編碼是」%20"。

10一、經常使用的Web服務器有哪些?
答:Unix和Linux平臺下使用最普遍的免費HTTP服務器是Apache服務器,而Windows平臺的服務器一般使用IIS做爲Web服務器。選擇Web服務器應考慮的因素有:性能、安全性、日誌和統計、虛擬主機、代理服務器、緩衝服務和集成應用程序等。下面是對常見服務器的簡介:

  • IIS:Microsoft的Web服務器產品,全稱是Internet Information Services。IIS是容許在公共Intranet或Internet上發佈信息的Web服務器。IIS是目前最流行的Web服務器產品之一,不少著名的網站都是創建在IIS的平臺上。IIS提供了一個圖形界面的管理工具,稱爲Internet服務管理器,可用於監視配置和控制Internet服務。IIS是一種Web服務組件,其中包括Web服務器、FTP服務器、NNTP服務器和SMTP服務器,分別用於網頁瀏覽、文件傳輸、新聞服務和郵件發送等方面,它使得在網絡(包括互聯網和局域網)上發佈信息成了一件很容易的事。它提供ISAPI(Intranet Server API)做爲擴展Web服務器功能的編程接口;同時,它還提供一個Internet數據庫鏈接器,能夠實現對數據庫的查詢和更新。
  • Kangle:Kangle Web服務器是一款跨平臺、功能強大、安全穩定、易操做的高性能Web服務器和反向代理服務器軟件。此外,Kangle也是一款專爲作虛擬主機研發的Web服務器。實現虛擬主機獨立進程、獨立身份運行。用戶之間安全隔離,一個用戶出問題不影響其餘用戶。支持PHP、ASP、ASP.NET、Java、Ruby等多種動態開發語言。
  • WebSphere:WebSphere Application Server是功能完善、開放的Web應用程序服務器,是IBM電子商務計劃的核心部分,它是基於Java的應用環境,用於創建、部署和管理Internet和Intranet Web應用程序,適應各類Web應用程序服務器的須要。
  • WebLogic:WebLogic Server是一款多功能、基於標準的Web應用服務器,爲企業構建企業應用提供了堅實的基礎。針對各類應用開發、關鍵性任務的部署,各類系統和數據庫的集成、跨Internet協做等Weblogic都提供了相應的支持。因爲它具備全面的功能、對開放標準的聽從性、多層架構、支持基於組件的開發等優點,不少公司的企業級應用都選擇它來做爲開發和部署的環境。WebLogic Server在使應用服務器成爲企業應用架構的基礎方面一直處於領先地位,爲構建集成化的企業級應用提供了穩固的基礎。
  • Apache:目前Apache仍然是世界上用得最多的Web服務器,其市場佔有率很長時間都保持在60%以上(目前的市場份額約40%左右)。世界上不少著名的網站都是Apache的產物,它的成功之處主要在於它的源代碼開放、有一支強大的開發團隊、支持跨平臺的應用(能夠運行在幾乎全部的Unix、Windows、Linux系統平臺上)以及它的可移植性等方面。
  • Tomcat:Tomcat是一個開放源代碼、運行Servlet和JSP的容器。Tomcat實現了Servlet和JSP規範。此外,Tomcat還實現了Apache-Jakarta規範並且比絕大多數商業應用軟件服務器要好,所以目前也有很多的Web服務器都選擇了Tomcat。
  • Nginx:讀做"engine x",是一個高性能的HTTP和反向代理服務器,也是一個IMAP/POP3/SMTP代理服務器。 Nginx是由Igor Sysoev爲俄羅斯訪問量第二的Rambler站點開發的,第一個公開版本0.1.0發佈於2004年10月4日。其將源代碼以類BSD許可證的形式發佈,因它的穩定性、豐富的功能集、示例配置文件和低系統資源的消耗而聞名。在2014年下半年,Nginx的市場份額達到了14%。

10二、JSP和Servlet是什麼關係?
答:其實這個問題在上面已經闡述過了,Servlet是一個特殊的Java程序,它運行於服務器的JVM中,可以依靠服務器的支持向瀏覽器提供顯示內容。JSP本質上是Servlet的一種簡易形式,JSP會被服務器處理成一個相似於Servlet的Java程序,能夠簡化頁面內容的生成。Servlet和JSP最主要的不一樣點在於,Servlet的應用邏輯是在Java文件中,而且徹底從表示層中的HTML分離開來。而JSP的狀況是Java和HTML能夠組合成一個擴展名爲.jsp的文件。有人說,Servlet就是在Java中寫HTML,而JSP就是在HTML中寫Java代碼,固然這個說法是很片面且不夠準確的。JSP側重於視圖,Servlet更側重於控制邏輯,在MVC架構模式中,JSP適合充當視圖(view)而Servlet適合充當控制器(controller)。

10三、講解JSP中的四種做用域。
答:JSP中的四種做用域包括page、request、session和application,具體來講:

  • page表明與一個頁面相關的對象和屬性。
  • request表明與Web客戶機發出的一個請求相關的對象和屬性。一個請求可能跨越多個頁面,涉及多個Web組件;須要在頁面顯示的臨時數據能夠置於此做用域。
  • session表明與某個用戶與服務器創建的一次會話相關的對象和屬性。跟某個用戶相關的數據應該放在用戶本身的session中。
  • application表明與整個Web應用程序相關的對象和屬性,它實質上是跨越整個Web應用程序,包括多個頁面、請求和會話的一個全局做用域。

10四、如何實現JSP或Servlet的單線程模式?
答:
對於JSP頁面,能夠經過page指令進行設置。

<%@page isThreadSafe=」false」%>

對於Servlet,可讓自定義的Servlet實現SingleThreadModel標識接口。

說明:若是將JSP或Servlet設置成單線程工做模式,會致使每一個請求建立一個Servlet實例,這種實踐將致使嚴重的性能問題(服務器的內存壓力很大,還會致使頻繁的垃圾回收),因此一般狀況下並不會這麼作。

10五、實現會話跟蹤的技術有哪些?
答:因爲HTTP協議自己是無狀態的,服務器爲了區分不一樣的用戶,就須要對用戶會話進行跟蹤,簡單的說就是爲用戶進行登記,爲用戶分配惟一的ID,下一次用戶在請求中包含此ID,服務器據此判斷究竟是哪個用戶。
①URL 重寫:在URL中添加用戶會話的信息做爲請求的參數,或者將惟一的會話ID添加到URL結尾以標識一個會話。
②設置表單隱藏域:將和會話跟蹤相關的字段添加到隱式表單域中,這些信息不會在瀏覽器中顯示可是提交表單時會提交給服務器。
這兩種方式很難處理跨越多個頁面的信息傳遞,由於若是每次都要修改URL或在頁面中添加隱式表單域來存儲用戶會話相關信息,事情將變得很是麻煩。
③cookie:cookie有兩種,一種是基於窗口的,瀏覽器窗口關閉後,cookie就沒有了;另外一種是將信息存儲在一個臨時文件中,並設置存在的時間。當用戶經過瀏覽器和服務器創建一次會話後,會話ID就會隨響應信息返回存儲在基於窗口的cookie中,那就意味着只要瀏覽器沒有關閉,會話沒有超時,下一次請求時這個會話ID又會提交給服務器讓服務器識別用戶身份。會話中能夠爲用戶保存信息。會話對象是在服務器內存中的,而基於窗口的cookie是在客戶端內存中的。若是瀏覽器禁用了cookie,那麼就須要經過下面兩種方式進行會話跟蹤。固然,在使用cookie時要注意幾點:首先不要在cookie中存放敏感信息;其次cookie存儲的數據量有限(4k),不能將過多的內容存儲cookie中;再者瀏覽器一般只容許一個站點最多存放20個cookie。固然,和用戶會話相關的其餘信息(除了會話ID)也能夠存在cookie方便進行會話跟蹤。
④HttpSession:在全部會話跟蹤技術中,HttpSession對象是最強大也是功能最多的。當一個用戶第一次訪問某個網站時會自動建立HttpSession,每一個用戶能夠訪問他本身的HttpSession。能夠經過HttpServletRequest對象的getSession方法得到HttpSession,經過HttpSession的setAttribute方法能夠將一個值放在HttpSession中,經過調用HttpSession對象的getAttribute方法,同時傳入屬性名就能夠獲取保存在HttpSession中的對象。與上面三種方式不一樣的是,HttpSession放在服務器的內存中,所以不要將過大的對象放在裏面,即便目前的Servlet容器能夠在內存將滿時將HttpSession中的對象移到其餘存儲設備中,可是這樣勢必影響性能。添加到HttpSession中的值能夠是任意Java對象,這個對象最好實現了Serializable接口,這樣Servlet容器在必要的時候能夠將其序列化到文件中,不然在序列化時就會出現異常。

**補充:**HTML5中能夠使用Web Storage技術經過JavaScript來保存數據,例如能夠使用localStorage和sessionStorage來保存用戶會話的信息,也可以實現會話跟蹤。

10六、過濾器有哪些做用和用法?
答: Java Web開發中的過濾器(filter)是從Servlet 2.3規範開始增長的功能,並在Servlet 2.4規範中獲得加強。對Web應用來講,過濾器是一個駐留在服務器端的Web組件,它能夠截取客戶端和服務器之間的請求與響應信息,並對這些信息進行過濾。當Web容器接受到一個對資源的請求時,它將判斷是否有過濾器與這個資源相關聯。若是有,那麼容器將把請求交給過濾器進行處理。在過濾器中,你能夠改變請求的內容,或者從新設置請求的報頭信息,而後再將請求發送給目標資源。當目標資源對請求做出響應時候,容器一樣會將響應先轉發給過濾器,在過濾器中你能夠對響應的內容進行轉換,而後再將響應發送到客戶端。

常見的過濾器用途主要包括:對用戶請求進行統一認證、對用戶的訪問請求進行記錄和審覈、對用戶發送的數據進行過濾或替換、轉換圖象格式、對響應內容進行壓縮以減小傳輸量、對請求或響應進行加解密處理、觸發資源訪問事件、對XML的輸出應用XSLT等。

和過濾器相關的接口主要有:Filter、FilterConfig和FilterChain。

編碼過濾器的例子:

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebInitParam;

@WebFilter(urlPatterns = { 「*」 },
initParams = {@WebInitParam(name=「encoding」, value=「utf-8」)})
public class CodingFilter implements Filter {
private String defaultEncoding = 「utf-8」;

@Override
public void destroy() {
}

@Override
public void doFilter(ServletRequest req, ServletResponse resp,
        FilterChain chain) throws IOException, ServletException {
    req.setCharacterEncoding(defaultEncoding);
    resp.setCharacterEncoding(defaultEncoding);
    chain.doFilter(req, resp);
}

@Override
public void init(FilterConfig config) throws ServletException {
    String encoding = config.getInitParameter("encoding");
    if (encoding != null) {
        defaultEncoding = encoding;
    }
}

}

下載計數過濾器的例子:

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;

@WebFilter(urlPatterns = {"/*"})
public class DownloadCounterFilter implements Filter {

private ExecutorService executorService = Executors.newSingleThreadExecutor();
private Properties downloadLog;
private File logFile;

@Override
public void destroy() {
    executorService.shutdown();
}

@Override
public void doFilter(ServletRequest req, ServletResponse resp,
        FilterChain chain) throws IOException, ServletException {
    HttpServletRequest request = (HttpServletRequest) req;
    final String uri = request.getRequestURI();
    executorService.execute(new Runnable() {

        @Override
        public void run() {
            String value = downloadLog.getProperty(uri);
            if(value == null) {
                downloadLog.setProperty(uri, "1");
            }
            else {
                int count = Integer.parseInt(value);
                downloadLog.setProperty(uri, String.valueOf(++count));
            }
            try {
                downloadLog.store(new FileWriter(logFile), "");
            } 
            catch (IOException e) {
                e.printStackTrace();
            }
        }
    });
    chain.doFilter(req, resp);
}

@Override
public void init(FilterConfig config) throws ServletException {
    String appPath = config.getServletContext().getRealPath("/");
    logFile = new File(appPath, "downloadLog.txt");
    if(!logFile.exists()) {
        try {
            logFile.createNewFile();
        } 
        catch(IOException e) {
            e.printStackTrace();
        }
    }
    downloadLog = new Properties();
    try {
        downloadLog.load(new FileReader(logFile));
    } catch (IOException e) {
        e.printStackTrace();
    }
}

}

說明:這裏使用了Servlet 3規範中的註解來部署過濾器,固然也能夠在web.xml中使用<filter>和<filter-mapping>標籤部署過濾器,如108題中所示。

10七、監聽器有哪些做用和用法?
答:Java Web開發中的監聽器(listener)就是application、session、request三個對象建立、銷燬或者往其中添加修改刪除屬性時自動執行代碼的功能組件,以下所示:
①ServletContextListener:對Servlet上下文的建立和銷燬進行監聽。
②ServletContextAttributeListener:監聽Servlet上下文屬性的添加、刪除和替換。
③HttpSessionListener:對Session的建立和銷燬進行監聽。

補充:session的銷燬有兩種狀況:1). session超時(能夠在web.xml中經過<session-config>/<session-timeout>標籤配置超時時間);2). 經過調用session對象的invalidate()方法使session失效。

④HttpSessionAttributeListener:對Session對象中屬性的添加、刪除和替換進行監聽。
⑤ServletRequestListener:對請求對象的初始化和銷燬進行監聽。
⑥ServletRequestAttributeListener:對請求對象屬性的添加、刪除和替換進行監聽。

下面是一個統計網站最多在線人數監聽器的例子。

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

/**
上下文監聽器,在服務器啓動時初始化onLineCount和maxOnLineCount兩個變量
並將其置於服務器上下文(ServletContext)中,其初始值都是0
*/
@WebListener
public class InitListener implements ServletContextListener {

@Override
public void contextDestroyed(ServletContextEvent evt) {
}

@Override
public void contextInitialized(ServletContextEvent evt) {
    evt.getServletContext().setAttribute("onLineCount", 0);
    evt.getServletContext().setAttribute("maxOnLineCount", 0);
}

}

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.servlet.ServletContext;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

/**
會話監聽器,在用戶會話建立和銷燬的時候根據狀況
修改onLineCount和maxOnLineCount的值
*/
@WebListener
public class MaxCountListener implements HttpSessionListener {

@Override
public void sessionCreated(HttpSessionEvent event) {
    ServletContext ctx = event.getSession().getServletContext();
    int count = Integer.parseInt(ctx.getAttribute("onLineCount").toString());
    count++;
    ctx.setAttribute("onLineCount", count);
    int maxOnLineCount = Integer.parseInt(ctx.getAttribute("maxOnLineCount").toString());
    if (count > maxOnLineCount) {
        ctx.setAttribute("maxOnLineCount", count);
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        ctx.setAttribute("date", df.format(new Date()));
    }
}

@Override
public void sessionDestroyed(HttpSessionEvent event) {
    ServletContext app = event.getSession().getServletContext();
    int count = Integer.parseInt(app.getAttribute("onLineCount").toString());
    count--;
    app.setAttribute("onLineCount", count);
}

}

說明:這裏使用了Servlet 3規範中的@WebListener註解配置監聽器,固然你能夠在web.xml文件中用<listener>標籤配置監聽器,如108題中所示。

10八、web.xml文件中能夠配置哪些內容?
答:web.xml用於配置Web應用的相關信息,如:監聽器(listener)、過濾器(filter)、 Servlet、相關參數、會話超時時間、安全驗證方式、錯誤頁面等,下面是一些開發中常見的配置:

①配置Spring上下文加載監聽器加載Spring配置文件並建立IoC容器:

contextConfigLocation classpath:applicationContext.xml org.springframework.web.context.ContextLoaderListener

②配置Spring的OpenSessionInView過濾器來解決延遲加載和Hibernate會話關閉的矛盾:

openSessionInView org.springframework.orm.hibernate3.support.OpenSessionInViewFilter openSessionInView /*

③配置會話超時時間爲10分鐘:

10

④配置404和Exception的錯誤頁面:

404 /error.jsp java.lang.Exception /error.jsp

⑤配置安全認證方式:

ProtectedArea /admin/* GET POST admin BASIC admin
說明:對Servlet(小服務)、Listener(監聽器)和Filter(過濾器)等Web組件的配置,Servlet 3規範提供了基於註解的配置方式,能夠分別使用@WebServlet、@WebListener、@WebFilter註解進行配置。

補充:若是Web提供了有價值的商業信息或者是敏感數據,那麼站點的安全性就是必須考慮的問題。安全認證是實現安全性的重要手段,認證就是要解決「Are you who you say you are?」的問題。認證的方式很是多,簡單說來能夠分爲三類:
A. What you know? — 口令
B. What you have? — 數字證書(U盾、密保卡)
C. Who you are? — 指紋識別、虹膜識別
在Tomcat中能夠經過創建安全套接字層(Secure Socket Layer, SSL)以及經過基本驗證或表單驗證來實現對安全性的支持。

10九、你的項目中使用過哪些JSTL標籤?
答:項目中主要使用了JSTL的核心標籤庫,包括<c:if>、<c:choose>、<c: when>、<c: otherwise>、<c:forEach>等,主要用於構造循環和分支結構以控制顯示邏輯。

說明:雖然JSTL標籤庫提供了core、sql、fmt、xml等標籤庫,可是實際開發中建議只使用核心標籤庫(core),並且最好只使用分支和循環標籤並輔以表達式語言(EL),這樣才能真正作到數據顯示和業務邏輯的分離,這纔是最佳實踐。

1十、使用標籤庫有什麼好處?如何自定義JSP標籤?
答:使用標籤庫的好處包括如下幾個方面:

  • 分離JSP頁面的內容和邏輯,簡化了Web開發;
  • 開發者能夠建立自定義標籤來封裝業務邏輯和顯示邏輯;
  • 標籤具備很好的可移植性、可維護性和可重用性;
  • 避免了對Scriptlet(小腳本)的使用(不少公司的項目開發都不容許在JSP中書寫小腳本)

自定義JSP標籤包括如下幾個步驟:

  • 編寫一個Java類實現實現Tag/BodyTag/IterationTag接口(開發中一般不直接實現這些接口而是繼承TagSupport/BodyTagSupport/SimpleTagSupport類,這是對缺省適配模式的應用),重寫doStartTag()、doEndTag()等方法,定義標籤要完成的功能
  • 編寫擴展名爲tld的標籤描述文件對自定義標籤進行部署,tld文件一般放在WEB-INF文件夾下或其子目錄中
  • 在JSP頁面中使用taglib指令引用該標籤庫

下面是一個自定義標籤庫的例子。

步驟1 - 標籤類源代碼TimeTag.java:

package com.jackfrued.tags;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.TagSupport;

public class TimeTag extends TagSupport {
private static final long serialVersionUID = 1L;

private String format = "yyyy-MM-dd hh:mm:ss";
private String foreColor = "black";
private String backColor = "white";

public int doStartTag() throws JspException {
     SimpleDateFormat sdf = new SimpleDateFormat(format);
     JspWriter writer = pageContext.getOut();
     StringBuilder sb = new StringBuilder();
     sb.append(String.format("<span style='color:%s;background-color:%s'>%s</span>",
         foreColor, backColor, sdf.format(new Date())));
     try {
       writer.print(sb.toString());
     } catch(IOException e) {
       e.printStackTrace();
     }
     return SKIP_BODY;
  }

public void setFormat(String format) {
    this.format = format;
}

public void setForeColor(String foreColor) {
    this.foreColor = foreColor;
}

public void setBackColor(String backColor) {
    this.backColor = backColor;
}

}

步驟2 - 編寫標籤庫描述文件my.tld:

<?xml version="1.0" encoding="UTF-8" ?>

<description>定義標籤庫</description>
<tlib-version>1.0</tlib-version>
<short-name>MyTag</short-name>
<tag>
    <name>time</name>
    <tag-class>com.jackfrued.tags.TimeTag</tag-class>
    <body-content>empty</body-content>
    <attribute>
        <name>format</name>
        <required>false</required>
    </attribute>
    <attribute>
        <name>foreColor</name>
    </attribute>
    <attribute>
        <name>backColor</name>
    </attribute>
</tag>

步驟3 - 在JSP頁面中使用自定義標籤:

<%@ page pageEncoding=「UTF-8」%>
<%@ taglib prefix=「my」 uri="/WEB-INF/tld/my.tld" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme() + 「😕/」 + request.getServerName() + 「:」 + request.getServerPort() + path + 「/」;
%>

首頁
提示:若是要將自定義的標籤庫發佈成JAR文件,須要將標籤庫描述文件(tld文件)放在JAR文件的META-INF目錄下,能夠JDK中的jar工具完成JAR文件的生成。

1十一、說一下表達式語言(EL)的隱式對象及其做用。
答:EL的隱式對象包括:pageContext、initParam(訪問上下文參數)、param(訪問請求參數)、paramValues、header(訪問請求頭)、headerValues、cookie(訪問cookie)、applicationScope(訪問application做用域)、sessionScope(訪問session做用域)、requestScope(訪問request做用域)、pageScope(訪問page做用域)。

用法以下所示:

${pageContext.request.method}
${pageContext[「request」][「method」]}
${pageContext.request[「method」]}
${pageContext[「request」].method}
${initParam.defaultEncoding}
${header[「accept-language」]}
${headerValues[「accept-language」][0]}
${cookie.jsessionid.value}
${sessionScope.loginUser.username}

補充:表達式語言的.和[]運算做用是一致的,惟一的差異在於若是訪問的屬性名不符合Java標識符命名規則,例如上面的accept-language就不是一個有效的Java標識符,那麼這時候就只能用[]運算符而不能使用.運算符獲取它的值

1十二、表達式語言(EL)支持哪些運算符?
答:除了.和[]運算符,EL還提供了:

  • 算術運算符:+、-、*、/或div、%或mod
  • 關係運算符:==或eq、!=或ne、>或gt、>=或ge、<或lt、<=或le
  • 邏輯運算符:&&或and、||或or、!或not
  • 條件運算符:${statement? A : B}(跟Java的條件運算符相似)
  • empty運算符:檢查一個值是否爲null或者空(數組長度爲0或集合中沒有元素也返回true)

11三、Java Web開發的Model 1和Model 2分別指的是什麼?
答:Model 1是以頁面爲中心的Java Web開發,使用JSP+JavaBean技術將頁面顯示邏輯和業務邏輯處理分開,JSP實現頁面顯示,JavaBean對象用來保存數據和實現業務邏輯。Model 2是基於MVC(模型-視圖-控制器,Model-View-Controller)架構模式的開發模型,實現了模型和視圖的完全分離,利於團隊開發和代碼複用,以下圖所示。

這裏寫圖片描述

11四、Servlet 3中的異步處理指的是什麼?
答:在Servlet 3中引入了一項新的技術可讓Servlet異步處理請求。有人可能會質疑,既然都有多線程了,還須要異步處理請求嗎?答案是確定的,由於若是一個任務處理時間至關長,那麼Servlet或Filter會一直佔用着請求處理線程直到任務結束,隨着併發用戶的增長,容器將會遭遇線程超出的風險,這這種狀況下不少的請求將會被堆積起來然後續的請求可能會遭遇拒絕服務,直到有資源能夠處理請求爲止。異步特性能夠幫助應用節省容器中的線程,特別適合執行時間長並且用戶須要獲得結果的任務,若是用戶不須要獲得結果則直接將一個Runnable對象交給Executor並當即返回便可。

補充:多線程在Java誕生初期無疑是一個亮點,而Servlet單實例多線程的工做方式也曾爲其贏得美名,然而技術的發展每每會顛覆咱們不少的認知,就如同當年愛因斯坦的相對論顛覆了牛頓的經典力學通常。事實上,異步處理毫不是Serlvet 3獨創,若是你瞭解Node.js的話,對Servlet 3的這個重要改進就不覺得奇了。

下面是一個支持異步處理請求的Servlet的例子。

import java.io.IOException;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(urlPatterns = {"/async"}, asyncSupported = true)
public class AsyncServlet extends HttpServlet {
private static final long serialVersionUID = 1L;

@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
    // 開啓Tomcat異步Servlet支持
    req.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);

    final AsyncContext ctx = req.startAsync();  // 啓動異步處理的上下文
    // ctx.setTimeout(30000);
    ctx.start(new Runnable() {

        @Override
        public void run() {
            // 在此處添加異步處理的代碼

            ctx.complete();
        }
    });
}

}

11五、如何在基於Java的Web項目中實現文件上傳和下載?
答:在Sevlet 3 之前,Servlet API中沒有支持上傳功能的API,所以要實現上傳功能須要引入第三方工具從POST請求中得到上傳的附件或者經過自行處理輸入流來得到上傳的文件,咱們推薦使用Apache的commons-fileupload。
從Servlet 3開始,文件上傳變得無比簡單,相信看看下面的例子一切都清楚了。

上傳頁面index.jsp:

<%@ page pageEncoding=「utf-8」%>

Photo Upload

Select your photo and upload


${hint}
Photo file:

支持上傳的Servlet:

package com.jackfrued.servlet;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;

@WebServlet("/UploadServlet")
@MultipartConfig
public class UploadServlet extends HttpServlet {
private static final long serialVersionUID = 1L;

protected void doPost(HttpServletRequest request,
        HttpServletResponse response) throws ServletException, IOException {
    // 能夠用request.getPart()方法得到名爲photo的上傳附件
    // 也能夠用request.getParts()得到全部上傳附件(多文件上傳)
    // 而後經過循環分別處理每個上傳的文件
    Part part = request.getPart("photo");
    if (part != null && part.getSubmittedFileName().length() > 0) {
        // 用ServletContext對象的getRealPath()方法得到上傳文件夾的絕對路徑
        String savePath = request.getServletContext().getRealPath("/upload");
        // Servlet 3.1規範中能夠用Part對象的getSubmittedFileName()方法得到上傳的文件名
        // 更好的作法是爲上傳的文件進行重命名(避免同名文件的相互覆蓋)
        part.write(savePath + "/" + part.getSubmittedFileName());
        request.setAttribute("hint", "Upload Successfully!");
    } else {
        request.setAttribute("hint", "Upload failed!");
    }
    // 跳轉回到上傳頁面
    request.getRequestDispatcher("index.jsp").forward(request, response);
}

}

11六、服務器收到用戶提交的表單數據,究竟是調用Servlet的doGet()仍是doPost()方法?
答:HTML的元素有一個method屬性,用來指定提交表單的方式,其值能夠是get或post。咱們自定義的Servlet通常狀況下會重寫doGet()或doPost()兩個方法之一或所有,若是是GET請求就調用doGet()方法,若是是POST請求就調用doPost()方法,那爲何爲何這樣呢?咱們自定義的Servlet一般繼承自HttpServlet,HttpServlet繼承自GenericServlet並重寫了其中的service()方法,這個方法是Servlet接口中定義的。HttpServlet重寫的service()方法會先獲取用戶請求的方法,而後根據請求方法調用doGet()、doPost()、doPut()、doDelete()等方法,若是在自定義Servlet中重寫了這些方法,那麼顯然會調用重寫過的(自定義的)方法,這顯然是對模板方法模式的應用(若是不理解,請參考閻宏博士的《Java與模式》一書的第37章)。固然,自定義Servlet中也能夠直接重寫service()方法,那麼無論是哪一種方式的請求,均可以經過本身的代碼進行處理,這對於不區分請求方法的場景比較合適。

11七、JSP中的靜態包含和動態包含有什麼區別?
答:靜態包含是經過JSP的include指令包含頁面,動態包含是經過JSP標準動做jsp:forward包含頁面。靜態包含是編譯時包含,若是包含的頁面不存在則會產生編譯錯誤,並且兩個頁面的"contentType"屬性應保持一致,由於兩個頁面會合二爲一,只產生一個class文件,所以被包含頁面發生的變更再包含它的頁面更新前不會獲得更新。動態包含是運行時包含,能夠向被包含的頁面傳遞參數,包含頁面和被包含頁面是獨立的,會編譯出兩個class文件,若是被包含的頁面不存在,不會產生編譯錯誤,也不影響頁面其餘部分的執行。代碼以下所示:

<%-- 靜態包含 --%>
<%@ include file="…" %>

<%-- 動態包含 --%>
<jsp:include page="…">
<jsp:param name="…" value="…" />
</jsp:include>

11八、Servlet中如何獲取用戶提交的查詢參數或表單數據?
答:能夠經過請求對象(HttpServletRequest)的getParameter()方法經過參數名得到參數值。若是有包含多個值的參數(例如複選框),能夠經過請求對象的getParameterValues()方法得到。固然也能夠經過請求對象的getParameterMap()得到一個參數名和參數值的映射(Map)。

11九、Servlet中如何獲取用戶配置的初始化參數以及服務器上下文參數?
答:能夠經過重寫Servlet接口的init(ServletConfig)方法並經過ServletConfig對象的getInitParameter()方法來獲取Servlet的初始化參數。能夠經過ServletConfig對象的getServletContext()方法獲取ServletContext對象,並經過該對象的getInitParameter()方法來獲取服務器上下文參數。固然,ServletContext對象也在處理用戶請求的方法(如doGet()方法)中經過請求對象的getServletContext()方法來得到。

120、如何設置請求的編碼以及響應內容的類型?
答:經過請求對象(ServletRequest)的setCharacterEncoding(String)方法能夠設置請求的編碼,其實要完全解決亂碼問題就應該讓頁面、服務器、請求和響應、Java程序都使用統一的編碼,最好的選擇固然是UTF-8;經過響應對象(ServletResponse)的setContentType(String)方法能夠設置響應內容的類型,固然也能夠經過HttpServletResponsed對象的setHeader(String, String)方法來設置。

說明:如今若是還有公司在面試的時候問JSP的聲明標記、表達式標記、小腳本標記這些內容的話,這樣的公司也不用去了,其實JSP內置對象、JSP指令這些東西基本上均可以忘卻了,關於Java Web開發的相關知識,能夠看一下個人《Servlet&JSP思惟導圖》,上面有完整的知識點的羅列。想了解如何實現自定義MVC框架的,能夠看一下個人《Java Web自定義MVC框架詳解》。

12一、解釋一下網絡應用的模式及其特色。
答:典型的網絡應用模式大體有三類:B/S、C/S、P2P。其中B表明瀏覽器(Browser)、C表明客戶端(Client)、S表明服務器(Server),P2P是對等模式,不區分客戶端和服務器。B/S應用模式中能夠視爲特殊的C/S應用模式,只是將C/S應用模式中的特殊的客戶端換成了瀏覽器,由於幾乎全部的系統上都有瀏覽器,那麼只要打開瀏覽器就能夠使用應用,沒有安裝、配置、升級客戶端所帶來的各類開銷。P2P應用模式中,成千上萬臺彼此鏈接的計算機都處於對等的地位,整個網絡通常來講不依賴專用的集中服務器。網絡中的每一臺計算機既能充當網絡服務的請求者,又對其它計算機的請求做出響應,提供資源和服務。一般這些資源和服務包括:信息的共享和交換、計算資源(如CPU的共享)、存儲共享(如緩存和磁盤空間的使用)等,這種應用模式最大的阻力安全性、版本等問題,目前有不少應用都混合使用了多種應用模型,最多見的網絡視頻應用,它幾乎把三種模式都用上了。

補充:此題要跟"電子商務模式"區分開,由於有不少人被問到這個問題的時候立刻想到的是B2B(如阿里巴巴)、B2C(如噹噹、亞馬遜、京東)、C2C(如淘寶、拍拍)、C2B(如威客)、O2O(如美團、餓了麼)。對於這類問題,能夠去百度上面科普一下。

12二、什麼是Web Service(Web服務)?
答:從表面上看,Web Service就是一個應用程序,它向外界暴露出一個可以經過Web進行調用的API。這就是說,你可以用編程的方法透明的調用這個應用程序,不須要了解它的任何細節,跟你使用的編程語言也沒有關係。例如能夠建立一個提供天氣預報的Web Service,那麼不管你用哪一種編程語言開發的應用均可以經過調用它的API並傳入城市信息來得到該城市的天氣預報。之因此稱之爲Web Service,是由於它基於HTTP協議傳輸數據,這使得運行在不一樣機器上的不一樣應用無須藉助附加的、專門的第三方軟件或硬件,就可相互交換數據或集成。

補充:這裏必需要說起的一個概念是SOA(Service-Oriented Architecture,面向服務的架構),SOA是一種思想,它將應用程序的不一樣功能單元經過中立的契約聯繫起來,獨立於硬件平臺、操做系統和編程語言,使得各類形式的功能單元可以更好的集成。顯然,Web Service是SOA的一種較好的解決方案,它更多的是一種標準,而不是一種具體的技術。

12三、概念解釋:SOAP、WSDL、UDDI。
答:

  • SOAP:簡單對象訪問協議(Simple Object Access Protocol),是Web Service中交換數據的一種協議規範。

  • WSDL:Web服務描述語言(Web Service Description Language),它描述了Web服務的公共接口。這是一個基於XML的關於如何與Web服務通信和使用的服務描述;也就是描述與目錄中列出的Web服務進行交互時須要綁定的協議和信息格式。一般採用抽象語言描述該服務支持的操做和信息,使用的時候再將實際的網絡協議和信息格式綁定給該服務。

  • UDDI:統一描述、發現和集成(Universal Description, Discovery and Integration),它是一個基於XML的跨平臺的描述規範,能夠使世界範圍內的企業在互聯網上發佈本身所提供的服務。簡單的說,UDDI是訪問各類WSDL的一個門面(能夠參考設計模式中的門面模式)。

    提示:關於Web Service的相關概念和知識能夠在W3CSchool上找到相關的資料。

12四、Java規範中和Web Service相關的規範有哪些?
答:Java規範中和Web Service相關的有三個:

  • JAX-WS(JSR 224):這個規範是早期的基於SOAP的Web Service規範JAX-RPC的替代版本,它並不提供向下兼容性,由於RPC樣式的WSDL以及相關的API已經在Java EE5中被移除了。WS-MetaData是JAX-WS的依賴規範,提供了基於註解配置Web Service和SOAP消息的相關API。
  • JAXM(JSR 67):定義了發送和接收消息所需的API,至關於Web Service的服務器端。
  • JAX-RS(JSR 311 & JSR 339 & JSR 370):是Java針對REST(Representation State Transfer)架構風格制定的一套Web Service規範。REST是一種軟件架構模式,是一種風格,它不像SOAP那樣自己承載着一種消息協議, (兩種風格的Web Service均採用了HTTP作傳輸協議,由於HTTP協議能穿越防火牆,Java的遠程方法調用(RMI)等是重量級協議,一般不能穿越防火牆),所以能夠將REST視爲基於HTTP協議的軟件架構。REST中最重要的兩個概念是資源定位和資源操做,而HTTP協議剛好完整的提供了這兩個點。HTTP協議中的URI能夠完成資源定位,而GET、POST、OPTION、DELETE方法能夠完成資源操做。所以REST徹底依賴HTTP協議就能夠完成Web Service,而不像SOAP協議那樣只利用了HTTP的傳輸特性,定位和操做都是由SOAP協議自身完成的,也正是因爲SOAP消息的存在使得基於SOAP的Web Service顯得笨重而逐漸被淘汰。

12五、介紹一下你瞭解的Java領域的Web Service框架。
答:Java領域的Web Service框架不少,包括Axis2(Axis的升級版本)、Jersey(RESTful的Web Service框架)、CXF(XFire的延續版本)、Hessian、Turmeric、JBoss SOA等,其中絕大多數都是開源框架。

提示:面試被問到這類問題的時候必定選擇本身用過的最熟悉的做答,若是以前沒有了解過就應該在面試前花一些時間瞭解其中的兩個,並比較其優缺點,這樣才能在面試時給出一個漂亮的答案。

這部分主要是開源Java EE框架方面的內容,包括Hibernate、MyBatis、Spring、Spring MVC等,因爲Struts 2已是明日黃花,在這裏就不討論Struts 2的面試題,若是須要了解相關內容,能夠參考Java程序員面試題集(86-115)。

12六、什麼是ORM?
答:對象關係映射(Object-Relational Mapping,簡稱ORM)是一種爲了解決程序的面向對象模型與數據庫的關係模型互不匹配問題的技術;簡單的說,ORM是經過使用描述對象和數據庫之間映射的元數據(在Java中能夠用XML或者是註解),將程序中的對象自動持久化到關係數據庫中或者將關係數據庫表中的行轉換成Java對象,其本質上就是將數據從一種形式轉換到另一種形式。

12七、持久層設計要考慮的問題有哪些?你用過的持久層框架有哪些?
答:所謂"持久"就是將數據保存到可掉電式存儲設備中以便從此使用,簡單的說,就是將內存中的數據保存到關係型數據庫、文件系統、消息隊列等提供持久化支持的設備中。持久層就是系統中專一於實現數據持久化的相對獨立的層面。

持久層設計的目標包括:

  • 數據存儲邏輯的分離,提供抽象化的數據訪問接口。
  • 數據訪問底層實現的分離,能夠在不修改代碼的狀況下切換底層實現。
  • 資源管理和調度的分離,在數據訪問層實現統一的資源調度(如緩存機制)。
  • 數據抽象,提供更面向對象的數據操做。

持久層框架有:

  • Hibernate
  • MyBatis
  • TopLink
  • Guzz
  • jOOQ
  • Spring Data
  • ActiveJDBC

12八、Hibernate中SessionFactory是線程安全的嗎?Session是線程安全的嗎(兩個線程可以共享同一個Session嗎)?
答:SessionFactory對應Hibernate的一個數據存儲的概念,它是線程安全的,能夠被多個線程併發訪問。SessionFactory通常只會在啓動的時候構建。對於應用程序,最好將SessionFactory經過單例模式進行封裝以便於訪問。Session是一個輕量級非線程安全的對象(線程間不能共享session),它表示與數據庫進行交互的一個工做單元。Session是由SessionFactory建立的,在任務完成以後它會被關閉。Session是持久層服務對外提供的主要接口。Session會延遲獲取數據庫鏈接(也就是在須要的時候纔會獲取)。爲了不建立太多的session,能夠使用ThreadLocal將session和當前線程綁定在一塊兒,這樣可讓同一個線程得到的老是同一個session。Hibernate 3中SessionFactory的getCurrentSession()方法就能夠作到。

12九、Hibernate中Session的load和get方法的區別是什麼?
答:主要有如下三項區別:
① 若是沒有找到符合條件的記錄,get方法返回null,load方法拋出異常。
② get方法直接返回實體類對象,load方法返回實體類對象的代理。
③ 在Hibernate 3以前,get方法只在一級緩存中進行數據查找,若是沒有找到對應的數據則越過二級緩存,直接發出SQL語句完成數據讀取;load方法則能夠從二級緩存中獲取數據;從Hibernate 3開始,get方法再也不是對二級緩存只寫不讀,它也是能夠訪問二級緩存的。

說明:對於load()方法Hibernate認爲該數據在數據庫中必定存在能夠放心的使用代理來實現延遲加載,若是沒有數據就拋出異常,而經過get()方法獲取的數據能夠不存在。

130、Session的save()、update()、merge()、lock()、saveOrUpdate()和persist()方法分別是作什麼的?有什麼區別?
答:Hibernate的對象有三種狀態:瞬時態(transient)、持久態(persistent)和遊離態(detached),如第135題中的圖所示。瞬時態的實例能夠經過調用save()、persist()或者saveOrUpdate()方法變成持久態;遊離態的實例能夠經過調用 update()、saveOrUpdate()、lock()或者replicate()變成持久態。save()和persist()將會引起SQL的INSERT語句,而update()或merge()會引起UPDATE語句。save()和update()的區別在於一個是將瞬時態對象變成持久態,一個是將遊離態對象變爲持久態。merge()方法能夠完成save()和update()方法的功能,它的意圖是將新的狀態合併到已有的持久化對象上或建立新的持久化對象。對於persist()方法,按照官方文檔的說明:① persist()方法把一個瞬時態的實例持久化,可是並不保證標識符被馬上填入到持久化實例中,標識符的填入可能被推遲到flush的時間;② persist()方法保證當它在一個事務外部被調用的時候並不觸發一個INSERT語句,當須要封裝一個長會話流程的時候,persist()方法是頗有必要的;③ save()方法不保證第②條,它要返回標識符,因此它會當即執行INSERT語句,無論是在事務內部仍是外部。至於lock()方法和update()方法的區別,update()方法是把一個已經更改過的脫管狀態的對象變成持久狀態;lock()方法是把一個沒有更改過的脫管狀態的對象變成持久狀態。

13一、闡述Session加載實體對象的過程。
答:Session加載實體對象的步驟是:
① Session在調用數據庫查詢功能以前,首先會在一級緩存中經過實體類型和主鍵進行查找,若是一級緩存查找命中且數據狀態合法,則直接返回;
② 若是一級緩存沒有命中,接下來Session會在當前NonExists記錄(至關於一個查詢黑名單,若是出現重複的無效查詢能夠迅速作出判斷,從而提高性能)中進行查找,若是NonExists中存在一樣的查詢條件,則返回null;
③ 若是一級緩存查詢失敗則查詢二級緩存,若是二級緩存命中則直接返回;
④ 若是以前的查詢都未命中,則發出SQL語句,若是查詢未發現對應記錄則將這次查詢添加到Session的NonExists中加以記錄,並返回null;
⑤ 根據映射配置和SQL語句獲得ResultSet,並建立對應的實體對象;
⑥ 將對象歸入Session(一級緩存)的管理;
⑦ 若是有對應的攔截器,則執行攔截器的onLoad方法;
⑧ 若是開啓並設置了要使用二級緩存,則將數據對象歸入二級緩存;
⑨ 返回數據對象。

13二、Query接口的list方法和iterate方法有什麼區別?
答:
① list()方法沒法利用一級緩存和二級緩存(對緩存只寫不讀),它只能在開啓查詢緩存的前提下使用查詢緩存;iterate()方法能夠充分利用緩存,若是目標數據只讀或者讀取頻繁,使用iterate()方法能夠減小性能開銷。
② list()方法不會引發N+1查詢問題,而iterate()方法可能引發N+1查詢問題

說明:關於N+1查詢問題,能夠參考CSDN上的一篇文章《什麼是N+1查詢》

13三、Hibernate如何實現分頁查詢?
答:經過Hibernate實現分頁查詢,開發人員只須要提供HQL語句(調用Session的createQuery()方法)或查詢條件(調用Session的createCriteria()方法)、設置查詢起始行數(調用Query或Criteria接口的setFirstResult()方法)和最大查詢行數(調用Query或Criteria接口的setMaxResults()方法),並調用Query或Criteria接口的list()方法,Hibernate會自動生成分頁查詢的SQL語句。

13四、鎖機制有什麼用?簡述Hibernate的悲觀鎖和樂觀鎖機制。
答:有些業務邏輯在執行過程當中要求對數據進行排他性的訪問,因而須要經過一些機制保證在此過程當中數據被鎖住不會被外界修改,這就是所謂的鎖機制。
Hibernate支持悲觀鎖和樂觀鎖兩種鎖機制。悲觀鎖,顧名思義悲觀的認爲在數據處理過程當中極有可能存在修改數據的併發事務(包括本系統的其餘事務或來自外部系統的事務),因而將處理的數據設置爲鎖定狀態。悲觀鎖必須依賴數據庫自己的鎖機制才能真正保證數據訪問的排他性,關於數據庫的鎖機制和事務隔離級別在《Java面試題大全(上)》中已經討論過了。樂觀鎖,顧名思義,對併發事務持樂觀態度(認爲對數據的併發操做不會常常性的發生),經過更加寬鬆的鎖機制來解決因爲悲觀鎖排他性的數據訪問對系統性能形成的嚴重影響。最多見的樂觀鎖是經過數據版本標識來實現的,讀取數據時得到數據的版本號,更新數據時將此版本號加1,而後和數據庫表對應記錄的當前版本號進行比較,若是提交的數據版本號大於數據庫中此記錄的當前版本號則更新數據,不然認爲是過時數據沒法更新。Hibernate中經過Session的get()和load()方法從數據庫中加載對象時能夠經過參數指定使用悲觀鎖;而樂觀鎖能夠經過給實體類加整型的版本字段再經過XML或@Version註解進行配置。

提示:使用樂觀鎖會增長了一個版本字段,很明顯這須要額外的空間來存儲這個版本字段,浪費了空間,可是樂觀鎖會讓系統具備更好的併發性,這是對時間的節省。所以樂觀鎖也是典型的空間換時間的策略。

13五、闡述實體對象的三種狀態以及轉換關係。
答:最新的Hibernate文檔中爲Hibernate對象定義了四種狀態(原來是三種狀態,面試的時候基本上問的也是三種狀態),分別是:瞬時態(new, or transient)、持久態(managed, or persistent)、遊狀態(detached)和移除態(removed,之前Hibernate文檔中定義的三種狀態中沒有移除態),以下圖所示,就之前的Hibernate文檔中移除態被視爲是瞬時態。

這裏寫圖片描述

瞬時態:當new一個實體對象後,這個對象處於瞬時態,即這個對象只是一個保存臨時數據的內存區域,若是沒有變量引用這個對象,則會被JVM的垃圾回收機制回收。這個對象所保存的數據與數據庫沒有任何關係,除非經過Session的save()、saveOrUpdate()、persist()、merge()方法把瞬時態對象與數據庫關聯,並把數據插入或者更新到數據庫,這個對象才轉換爲持久態對象。
持久態:持久態對象的實例在數據庫中有對應的記錄,並擁有一個持久化標識(ID)。對持久態對象進行delete操做後,數據庫中對應的記錄將被刪除,那麼持久態對象與數據庫記錄再也不存在對應關係,持久態對象變成移除態(能夠視爲瞬時態)。持久態對象被修改變動後,不會立刻同步到數據庫,直到數據庫事務提交。
遊離態:當Session進行了close()、clear()、evict()或flush()後,實體對象從持久態變成遊離態,對象雖然擁有持久和與數據庫對應記錄一致的標識值,可是由於對象已經從會話中清除掉,對象不在持久化管理以內,因此處於遊離態(也叫脫管態)。遊離態的對象與臨時狀態對象是十分類似的,只是它還含有持久化標識。

提示:關於這個問題,在Hibernate的官方文檔中有更爲詳細的解讀。

13六、如何理解Hibernate的延遲加載機制?在實際應用中,延遲加載與Session關閉的矛盾是如何處理的?
答:延遲加載就是並非在讀取的時候就把數據加載進來,而是等到使用時再加載。Hibernate使用了虛擬代理機制實現延遲加載,咱們使用Session的load()方法加載數據或者一對多關聯映射在使用延遲加載的狀況下從一的一方加載多的一方,獲得的都是虛擬代理,簡單的說返回給用戶的並非實體自己,而是實體對象的代理。代理對象在用戶調用getter方法時纔會去數據庫加載數據。但加載數據就須要數據庫鏈接。而當咱們把會話關閉時,數據庫鏈接就同時關閉了。

延遲加載與session關閉的矛盾通常能夠這樣處理:
① 關閉延遲加載特性。這種方式操做起來比較簡單,由於Hibernate的延遲加載特性是能夠經過映射文件或者註解進行配置的,但這種解決方案存在明顯的缺陷。首先,出現"no session or session was closed"一般說明系統中已經存在主外鍵關聯,若是去掉延遲加載的話,每次查詢的開銷都會變得很大。
② 在session關閉以前先獲取須要查詢的數據,能夠使用工具方法Hibernate.isInitialized()判斷對象是否被加載,若是沒有被加載則能夠使用Hibernate.initialize()方法加載對象。
③ 使用攔截器或過濾器延長Session的生命週期直到視圖得到數據。Spring整合Hibernate提供的OpenSessionInViewFilter和OpenSessionInViewInterceptor就是這種作法。

13七、舉一個多對多關聯的例子,並說明如何實現多對多關聯映射。
答:例如:商品和訂單、學生和課程都是典型的多對多關係。能夠在實體類上經過@ManyToMany註解配置多對多關聯或者經過映射文件中的和標籤配置多對多關聯,可是實際項目開發中,不少時候都是將多對多關聯映射轉換成兩個多對一關聯映射來實現的。

13八、談一下你對繼承映射的理解。
答:繼承關係的映射策略有三種:
① 每一個繼承結構一張表(table per class hierarchy),無論多少個子類都用一張表。
② 每一個子類一張表(table per subclass),公共信息放一張表,特有信息放單獨的表。
③ 每一個具體類一張表(table per concrete class),有多少個子類就有多少張表。
第一種方式屬於單表策略,其優勢在於查詢子類對象的時候無需錶鏈接,查詢速度快,適合多態查詢;缺點是可能致使表很大。後兩種方式屬於多表策略,其優勢在於數據存儲緊湊,其缺點是須要進行鏈接查詢,不適合多態查詢。

13九、簡述Hibernate常見優化策略。
答:這個問題應當挑本身使用過的優化策略回答,經常使用的有:
① 制定合理的緩存策略(二級緩存、查詢緩存)。
② 採用合理的Session管理機制。
③ 儘可能使用延遲加載特性。
④ 設定合理的批處理參數。
⑤ 若是能夠,選用UUID做爲主鍵生成器。
⑥ 若是能夠,選用基於版本號的樂觀鎖替代悲觀鎖。
⑦ 在開發過程當中, 開啓hibernate.show_sql選項查看生成的SQL,從而瞭解底層的情況;開發完成後關閉此選項。
⑧ 考慮數據庫自己的優化,合理的索引、恰當的數據分區策略等都會對持久層的性能帶來可觀的提高,但這些須要專業的DBA(數據庫管理員)提供支持。

140、談一談Hibernate的一級緩存、二級緩存和查詢緩存。
答:Hibernate的Session提供了一級緩存的功能,默認老是有效的,當應用程序保存持久化實體、修改持久化實體時,Session並不會當即把這種改變提交到數據庫,而是緩存在當前的Session中,除非顯示調用了Session的flush()方法或經過close()方法關閉Session。經過一級緩存,能夠減小程序與數據庫的交互,從而提升數據庫訪問性能。
SessionFactory級別的二級緩存是全局性的,全部的Session能夠共享這個二級緩存。不過二級緩存默認是關閉的,須要顯示開啓並指定須要使用哪一種二級緩存實現類(能夠使用第三方提供的實現)。一旦開啓了二級緩存並設置了須要使用二級緩存的實體類,SessionFactory就會緩存訪問過的該實體類的每一個對象,除非緩存的數據超出了指定的緩存空間。
一級緩存和二級緩存都是對整個實體進行緩存,不會緩存普通屬性,若是但願對普通屬性進行緩存,能夠使用查詢緩存。查詢緩存是將HQL或SQL語句以及它們的查詢結果做爲鍵值對進行緩存,對於一樣的查詢能夠直接從緩存中獲取數據。查詢緩存默認也是關閉的,須要顯示開啓。

14一、Hibernate中DetachedCriteria類是作什麼的?
答:DetachedCriteria和Criteria的用法基本上是一致的,但Criteria是由Session的createCriteria()方法建立的,也就意味着離開建立它的Session,Criteria就沒法使用了。DetachedCriteria不須要Session就能夠建立(使用DetachedCriteria.forClass()方法建立),因此一般也稱其爲離線的Criteria,在須要進行查詢操做的時候再和Session綁定(調用其getExecutableCriteria(Session)方法),這也就意味着一個DetachedCriteria能夠在須要的時候和不一樣的Session進行綁定。

14二、@OneToMany註解的mappedBy屬性有什麼做用?
答:@OneToMany用來配置一對多關聯映射,但一般狀況下,一對多關聯映射都由多的一方來維護關聯關係,例如學生和班級,應該在學生類中添加班級屬性來維持學生和班級的關聯關係(在數據庫中是由學生表中的外鍵班級編號來維護學生表和班級表的多對一關係),若是要使用雙向關聯,在班級類中添加一個容器屬性來存放學生,並使用@OneToMany註解進行映射,此時mappedBy屬性就很是重要。若是使用XML進行配置,能夠用標籤的inverse="true"設置來達到一樣的效果。

14三、MyBatis中使用#和KaTeX parse error: Expected 'EOF', got '#' at position 15: 書寫佔位符有什麼區別? 答:#̲將傳入的數據都當成一個字符串,…將傳入的數據直接顯示生成在SQL中。注意:使用KaTeX parse error: Expected 'EOF', got '#' at position 19: …可能會致使SQL注射攻擊,能用#̲的地方就不要使用,寫order by子句的時候應該用$而不是#。

14四、解釋一下MyBatis中命名空間(namespace)的做用。
答:在大型項目中,可能存在大量的SQL語句,這時候爲每一個SQL語句起一個惟一的標識(ID)就變得並不容易了。爲了解決這個問題,在MyBatis中,能夠爲每一個映射文件起一個惟一的命名空間,這樣定義在這個映射文件中的每一個SQL語句就成了定義在這個命名空間中的一個ID。只要咱們可以保證每一個命名空間中這個ID是惟一的,即便在不一樣映射文件中的語句ID相同,也不會再產生衝突了。

14五、MyBatis中的動態SQL是什麼意思?
答:對於一些複雜的查詢,咱們可能會指定多個查詢條件,可是這些條件可能存在也可能不存在,例如在58同城上面找房子,咱們可能會指定面積、樓層和所在位置來查找房源,也可能會指定面積、價格、戶型和所在位置來查找房源,此時就須要根據用戶指定的條件動態生成SQL語句。若是不使用持久層框架咱們可能須要本身拼裝SQL語句,還好MyBatis提供了動態SQL的功能來解決這個問題。MyBatis中用於實現動態SQL的元素主要有:

  • if
  • choose / when / otherwise
  • trim
  • where
  • set
  • foreach

下面是映射文件的片斷。

select * from t_blog where 1 = 1 and title = #{title} and content = #{content} and owner = #{owner}

固然也能夠像下面這些書寫。

select * from t_blog where 1 = 1 and title = #{title} and content = #{content} and owner = "owner1"

再看看下面這個例子。

select * from t_blog where id in #{item}

14六、什麼是IoC和DI?DI是如何實現的?
答:IoC叫控制反轉,是Inversion of Control的縮寫,DI(Dependency Injection)叫依賴注入,是對IoC更簡單的詮釋。控制反轉是把傳統上由程序代碼直接操控的對象的調用權交給容器,經過容器來實現對象組件的裝配和管理。所謂的"控制反轉"就是對組件對象控制權的轉移,從程序代碼自己轉移到了外部容器,由容器來建立對象並管理對象之間的依賴關係。IoC體現了好萊塢原則 - 「Don’t call me, we will call you」。依賴注入的基本原則是應用組件不該該負責查找資源或者其餘依賴的協做對象。配置對象的工做應該由容器負責,查找資源的邏輯應該從應用組件的代碼中抽取出來,交給容器來完成。DI是對IoC更準確的描述,即組件之間的依賴關係由容器在運行期決定,形象的來講,即由容器動態的將某種依賴關係注入到組件之中。

舉個例子:一個類A須要用到接口B中的方法,那麼就須要爲類A和接口B創建關聯或依賴關係,最原始的方法是在類A中建立一個接口B的實現類C的實例,但這種方法須要開發人員自行維護兩者的依賴關係,也就是說當依賴關係發生變更的時候須要修改代碼並從新構建整個系統。若是經過一個容器來管理這些對象以及對象的依賴關係,則只須要在類A中定義好用於關聯接口B的方法(構造器或setter方法),將類A和接口B的實現類C放入容器中,經過對容器的配置來實現兩者的關聯。

依賴注入能夠經過setter方法注入(設值注入)、構造器注入和接口注入三種方式來實現,Spring支持setter注入和構造器注入,一般使用構造器注入來注入必須的依賴關係,對於可選的依賴關係,則setter注入是更好的選擇,setter注入須要類提供無參構造器或者無參的靜態工廠方法來建立對象。

14七、Spring中Bean的做用域有哪些?
答:在Spring的早期版本中,僅有兩個做用域:singleton和prototype,前者表示Bean以單例的方式存在;後者表示每次從容器中調用Bean時,都會返回一個新的實例,prototype一般翻譯爲原型。

補充:設計模式中的建立型模式中也有一個原型模式,原型模式也是一個經常使用的模式,例如作一個室內設計軟件,全部的素材都在工具箱中,而每次從工具箱中取出的都是素材對象的一個原型,能夠經過對象克隆來實現原型模式。

Spring 2.x中針對WebApplicationContext新增了3個做用域,分別是:request(每次HTTP請求都會建立一個新的Bean)、session(同一個HttpSession共享同一個Bean,不一樣的HttpSession使用不一樣的Bean)和globalSession(同一個全局Session共享一個Bean)。

說明:單例模式和原型模式都是重要的設計模式。通常狀況下,無狀態或狀態不可變的類適合使用單例模式。在傳統開發中,因爲DAO持有Connection這個非線程安全對象於是沒有使用單例模式;但在Spring環境下,全部DAO類對能夠採用單例模式,由於Spring利用AOP和Java API中的ThreadLocal對非線程安全的對象進行了特殊處理。

ThreadLocal爲解決多線程程序的併發問題提供了一種新的思路。ThreadLocal,顧名思義是線程的一個本地化對象,當工做於多線程中的對象使用ThreadLocal維護變量時,ThreadLocal爲每一個使用該變量的線程分配一個獨立的變量副本,因此每個線程均可以獨立的改變本身的副本,而不影響其餘線程所對應的副本。從線程的角度看,這個變量就像是線程的本地變量。

ThreadLocal類很是簡單好用,只有四個方法,能用上的也就是下面三個方法:

  • void set(T value):設置當前線程的線程局部變量的值。
  • T get():得到當前線程所對應的線程局部變量的值。
  • void remove():刪除當前線程中線程局部變量的值。

ThreadLocal是如何作到爲每個線程維護一份獨立的變量副本的呢?在ThreadLocal類中有一個Map,鍵爲線程對象,值是其線程對應的變量的副本,本身要模擬實現一個ThreadLocal類其實並不困難,代碼以下所示:

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class MyThreadLocal {
private Map<Thread, T> map = Collections.synchronizedMap(new HashMap<Thread, T>());

public void set(T newValue) {
    map.put(Thread.currentThread(), newValue);
}

public T get() {
    return map.get(Thread.currentThread());
}

public void remove() {
    map.remove(Thread.currentThread());
}

}

14八、解釋一下什麼叫AOP(面向切面編程)?
答:AOP(Aspect-Oriented Programming)指一種程序設計範型,該範型以一種稱爲切面(aspect)的語言構造爲基礎,切面是一種新的模塊化機制,用來描述分散在對象、類或方法中的橫切關注點(crosscutting concern)。

14九、你是如何理解"橫切關注"這個概念的?
答:"橫切關注"是會影響到整個應用程序的關注功能,它跟正常的業務邏輯是正交的,沒有必然的聯繫,可是幾乎全部的業務邏輯都會涉及到這些關注功能。一般,事務、日誌、安全性等關注就是應用中的橫切關注功能。

150、你如何理解AOP中的鏈接點(Joinpoint)、切點(Pointcut)、加強(Advice)、引介(Introduction)、織入(Weaving)、切面(Aspect)這些概念?
答:
a. 鏈接點(Joinpoint):程序執行的某個特定位置(如:某個方法調用前、調用後,方法拋出異常後)。一個類或一段程序代碼擁有一些具備邊界性質的特定點,這些代碼中的特定點就是鏈接點。Spring僅支持方法的鏈接點。
b. 切點(Pointcut):若是鏈接點至關於數據中的記錄,那麼切點至關於查詢條件,一個切點能夠匹配多個鏈接點。Spring AOP的規則解析引擎負責解析切點所設定的查詢條件,找到對應的鏈接點。
c. 加強(Advice):加強是織入到目標類鏈接點上的一段程序代碼。Spring提供的加強接口都是帶方位名的,如:BeforeAdvice、AfterReturningAdvice、ThrowsAdvice等。不少資料上將加強譯爲「通知」,這明顯是個詞不達意的翻譯,讓不少程序員困惑了許久。

說明: Advice在國內的不少書面資料中都被翻譯成"通知",可是很顯然這個翻譯沒法表達其本質,有少許的讀物上將這個詞翻譯爲"加強",這個翻譯是對Advice較爲準確的詮釋,咱們經過AOP將橫切關注功能加到原有的業務邏輯上,這就是對原有業務邏輯的一種加強,這種加強能夠是前置加強、後置加強、返回後加強、拋異常時加強和包圍型加強。

d. 引介(Introduction):引介是一種特殊的加強,它爲類添加一些屬性和方法。這樣,即便一個業務類本來沒有實現某個接口,經過引介功能,能夠動態的未該業務類添加接口的實現邏輯,讓業務類成爲這個接口的實現類。
e. 織入(Weaving):織入是將加強添加到目標類具體鏈接點上的過程,AOP有三種織入方式:①編譯期織入:須要特殊的Java編譯期(例如AspectJ的ajc);②裝載期織入:要求使用特殊的類加載器,在裝載類的時候對類進行加強;③運行時織入:在運行時爲目標類生成代理實現加強。Spring採用了動態代理的方式實現了運行時織入,而AspectJ採用了編譯期織入和裝載期織入的方式。
f. 切面(Aspect):切面是由切點和加強(引介)組成的,它包括了對橫切關注功能的定義,也包括了對鏈接點的定義。

補充:代理模式是GoF提出的23種設計模式中最爲經典的模式之一,代理模式是對象的結構模式,它給某一個對象提供一個代理對象,並由代理對象控制對原對象的引用。簡單的說,代理對象能夠完成比原對象更多的職責,當須要爲原對象添加橫切關注功能時,就能夠使用原對象的代理對象。咱們在打開Office系列的Word文檔時,若是文檔中有插圖,當文檔剛加載時,文檔中的插圖都只是一個虛框佔位符,等用戶真正翻到某頁要查看該圖片時,纔會真正加載這張圖,這其實就是對代理模式的使用,代替真正圖片的虛框就是一個虛擬代理;Hibernate的load方法也是返回一個虛擬代理對象,等用戶真正須要訪問對象的屬性時,才向數據庫發出SQL語句得到真實對象。

下面用一個找槍手代考的例子演示代理模式的使用:

/**

  • 參考人員接口
  • @author 駱昊

*/
public interface Candidate {

/**
 * 答題
 */
public void answerTheQuestions();

}

/**

  • 懶學生
  • @author 駱昊

*/
public class LazyStudent implements Candidate {
private String name; // 姓名

public LazyStudent(String name) {
    this.name = name;
}

@Override
public void answerTheQuestions() {
    // 懶學生只能寫出本身的名字不會答題
    System.out.println("姓名: " + name);
}

}

/**

  • 槍手
  • @author 駱昊

*/
public class Gunman implements Candidate {
private Candidate target; // 被代理對象

public Gunman(Candidate target) {
    this.target = target;
}

@Override
public void answerTheQuestions() {
    // 槍手要寫上代考的學生的姓名
    target.answerTheQuestions();
    // 槍手要幫助懶學生答題並交卷
    System.out.println("奮筆疾書正確答案");
    System.out.println("交卷");
}

}

public class ProxyTest1 {

public static void main(String[] args) {
    Candidate c = new Gunman(new LazyStudent("王小二"));
    c.answerTheQuestions();
}

}

說明:從JDK 1.3開始,Java提供了動態代理技術,容許開發者在運行時建立接口的代理實例,主要包括Proxy類和InvocationHandler接口。下面的例子使用動態代理爲ArrayList編寫一個代理,在添加和刪除元素時,在控制檯打印添加或刪除的元素以及ArrayList的大小:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.List;

public class ListProxy implements InvocationHandler {
private List target;

public ListProxy(List<T> target) {
    this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable {
    Object retVal = null;
    System.out.println("[" + method.getName() + ": " + args[0] + "]");
    retVal = method.invoke(target, args);
    System.out.println("[size=" + target.size() + "]");
    return retVal;
}

}

import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;

public class ProxyTest2 {

@SuppressWarnings("unchecked")
public static void main(String[] args) {
    List<String> list = new ArrayList<String>();
    Class<?> clazz = list.getClass();
    ListProxy<String> myProxy = new ListProxy<String>(list);
    List<String> newList = (List<String>)
            Proxy.newProxyInstance(clazz.getClassLoader(),
            clazz.getInterfaces(), myProxy);
    newList.add("apple");
    newList.add("banana");
    newList.add("orange");
    newList.remove("banana");
}

}

說明:使用Java的動態代理有一個侷限性就是代理的類必需要實現接口,雖然面向接口編程是每一個優秀的Java程序都知道的規則,但現實每每不盡如人意,對於沒有實現接口的類如何爲其生成代理呢?繼承!繼承是最經典的擴展已有代碼能力的手段,雖然繼承經常被初學者濫用,但繼承也經常被進階的程序員忽視。CGLib採用很是底層的字節碼生成技術,經過爲一個類建立子類來生成代理,它彌補了Java動態代理的不足,所以Spring中動態代理和CGLib都是建立代理的重要手段,對於實現了接口的類就用動態代理爲其生成代理類,而沒有實現接口的類就用CGLib經過繼承的方式爲其建立代理。

15一、Spring中自動裝配的方式有哪些?
答:

  • no:不進行自動裝配,手動設置Bean的依賴關係。

  • byName:根據Bean的名字進行自動裝配。

  • byType:根據Bean的類型進行自動裝配。

  • constructor:相似於byType,不過是應用於構造器的參數,若是正好有一個Bean與構造器的參數類型相同則能夠自動裝配,不然會致使錯誤。

  • autodetect:若是有默認的構造器,則經過constructor的方式進行自動裝配,不然使用byType的方式進行自動裝配。

    說明:自動裝配沒有自定義裝配方式那麼精確,並且不能自動裝配簡單屬性(基本類型、字符串等),在使用時應注意。

15二、Spring中如何使用註解來配置Bean?有哪些相關的註解?
答:首先須要在Spring配置文件中增長以下配置:

<context:component-scan base-package=「org.example」/>

而後能夠用@Component、@Controller、@Service、@Repository註解來標註須要由Spring IoC容器進行對象託管的類。這幾個註解沒有本質區別,只不過@Controller一般用於控制器,@Service一般用於業務邏輯類,@Repository一般用於倉儲類(例如咱們的DAO實現類),普通的類用@Component來標註。

15三、Spring支持的事務管理類型有哪些?你在項目中使用哪一種方式?
答:Spring支持編程式事務管理和聲明式事務管理。許多Spring框架的用戶選擇聲明式事務管理,由於這種方式和應用程序的關聯較少,所以更加符合輕量級容器的概念。聲明式事務管理要優於編程式事務管理,儘管在靈活性方面它弱於編程式事務管理,由於編程式事務容許你經過代碼控制業務。

事務分爲全局事務和局部事務。全局事務由應用服務器管理,須要底層服務器JTA支持(如WebLogic、WildFly等)。局部事務和底層採用的持久化方案有關,例如使用JDBC進行持久化時,須要使用Connetion對象來操做事務;而採用Hibernate進行持久化時,須要使用Session對象來操做事務。

Spring提供了以下所示的事務管理器。

事務管理器實現類 目標對象
DataSourceTransactionManager 注入DataSource
HibernateTransactionManager 注入SessionFactory
JdoTransactionManager 管理JDO事務
JtaTransactionManager 使用JTA管理事務
PersistenceBrokerTransactionManager 管理Apache的OJB事務

這些事務的父接口都是PlatformTransactionManager。Spring的事務管理機制是一種典型的策略模式,PlatformTransactionManager表明事務管理接口,該接口定義了三個方法,該接口並不知道底層如何管理事務,可是它的實現類必須提供getTransaction()方法(開啓事務)、commit()方法(提交事務)、rollback()方法(回滾事務)的多態實現,這樣就能夠用不一樣的實現類表明不一樣的事務管理策略。使用JTA全局事務策略時,須要底層應用服務器支持,而不一樣的應用服務器所提供的JTA全局事務可能存在細節上的差別,所以實際配置全局事務管理器是可能須要使用JtaTransactionManager的子類,如:WebLogicJtaTransactionManager(Oracle的WebLogic服務器提供)、UowJtaTransactionManager(IBM的WebSphere服務器提供)等。

編程式事務管理以下所示。

<?xml version="1.0" encoding="UTF-8"?>

<context:component-scan base-package="com.jackfrued"/>

 <bean id="propertyConfig"
     class="org.springframework.beans.factory.config.

PropertyPlaceholderConfigurer">

jdbc.properties

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
     <property name="driverClassName">
         <value>${db.driver}</value>
     </property>
     <property name="url">
         <value>${db.url}</value>
     </property>
     <property name="username">
         <value>${db.username}</value>
     </property>
     <property name="password">
         <value>${db.password}</value>
     </property>
 </bean>

 <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
     <property name="dataSource">
         <ref bean="dataSource" />
     </property>
 </bean>

 <!-- JDBC事務管理器 -->
 <bean id="transactionManager"
     class="org.springframework.jdbc.datasource.
   DataSourceTransactionManager" scope="singleton">
     <property name="dataSource">
         <ref bean="dataSource" />
     </property>
 </bean>

 <!-- 聲明事務模板 -->
 <bean id="transactionTemplate"
     class="org.springframework.transaction.support.

TransactionTemplate">



package com.jackfrued.dao.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;

import com.jackfrued.dao.EmpDao;
import com.jackfrued.entity.Emp;

@Repository
public class EmpDaoImpl implements EmpDao {
@Autowired
private JdbcTemplate jdbcTemplate;

@Override
public boolean save(Emp emp) {
    String sql = "insert into emp values (?,?,?)";
    return jdbcTemplate.update(sql, emp.getId(), emp.getName(), emp.getBirthday()) == 1;
}

}

package com.jackfrued.biz.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

import com.jackfrued.biz.EmpService;
import com.jackfrued.dao.EmpDao;
import com.jackfrued.entity.Emp;

@Service
public class EmpServiceImpl implements EmpService {
@Autowired
private TransactionTemplate txTemplate;
@Autowired
private EmpDao empDao;

@Override
public void addEmp(final Emp emp) {
    txTemplate.execute(new TransactionCallbackWithoutResult() {

        @Override
        protected void doInTransactionWithoutResult(TransactionStatus txStatus) {
            empDao.save(emp);
        }
    });
}

}

聲明式事務以下圖所示,以Spring整合Hibernate 3爲例,包括完整的DAO和業務邏輯代碼。

<?xml version="1.0" encoding="UTF-8"?>

<!-- 配置由Spring IoC容器託管的對象對應的被註解的類所在的包 -->
<context:component-scan base-package="com.jackfrued" />

<!-- 配置經過自動生成代理實現AOP功能 -->
<aop:aspectj-autoproxy />

<!-- 配置數據庫鏈接池 (DBCP) -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
    destroy-method="close">
    <!-- 配置驅動程序類 -->
    <property name="driverClassName" value="com.mysql.jdbc.Driver" />
    <!-- 配置鏈接數據庫的URL -->
    <property name="url" value="jdbc:mysql://localhost:3306/myweb" />
    <!-- 配置訪問數據庫的用戶名 -->
    <property name="username" value="root" />
    <!-- 配置訪問數據庫的口令 -->
    <property name="password" value="123456" />
    <!-- 配置最大鏈接數 -->
    <property name="maxActive" value="150" />
    <!-- 配置最小空閒鏈接數 -->
    <property name="minIdle" value="5" />
    <!-- 配置最大空閒鏈接數 -->
    <property name="maxIdle" value="20" />
    <!-- 配置初始鏈接數 -->
    <property name="initialSize" value="10" />
    <!-- 配置鏈接被泄露時是否生成日誌 -->
    <property name="logAbandoned" value="true" />
    <!-- 配置是否刪除超時鏈接 -->
    <property name="removeAbandoned" value="true" />
    <!-- 配置刪除超時鏈接的超時門限值(以秒爲單位) -->
    <property name="removeAbandonedTimeout" value="120" />
    <!-- 配置超時等待時間(以毫秒爲單位) -->
    <property name="maxWait" value="5000" />
    <!-- 配置空閒鏈接回收器線程運行的時間間隔(以毫秒爲單位) -->
    <property name="timeBetweenEvictionRunsMillis" value="300000" />
    <!-- 配置鏈接空閒多長時間後(以毫秒爲單位)被斷開鏈接 -->
    <property name="minEvictableIdleTimeMillis" value="60000" />
</bean>

<!-- 配置Spring提供的支持註解ORM映射的Hibernate會話工廠 -->
<bean id="sessionFactory"
    class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
    <!-- 經過setter注入數據源屬性 -->
    <property name="dataSource" ref="dataSource" />
    <!-- 配置實體類所在的包 -->
    <property name="packagesToScan" value="com.jackfrued.entity" />
    <!-- 配置Hibernate的相關屬性 -->
    <property name="hibernateProperties">
        <!-- 在項目調試完成後要刪除show_sql和format_sql屬性不然對性能有顯著影響 -->
        <value>
            hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
        </value>
    </property>
</bean>

<!-- 配置Spring提供的Hibernate事務管理器 -->
<bean id="transactionManager"
    class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <!-- 經過setter注入Hibernate會話工廠 -->
    <property name="sessionFactory" ref="sessionFactory" />
</bean>

<!-- 配置基於註解配置聲明式事務 -->
<tx:annotation-driven />

package com.jackfrued.dao;

import java.io.Serializable;
import java.util.List;

import com.jackfrued.comm.QueryBean;
import com.jackfrued.comm.QueryResult;

/**

  • 數據訪問對象接口(以對象爲單位封裝CRUD操做)

  • @author 駱昊

  • @param 實體類型

  • @param 實體標識字段的類型
    */
    public interface BaseDao <E, K extends Serializable> {

    /**

    • 新增
    • @param entity 業務實體對象
    • @return 增長成功返回實體對象的標識
      */
      public K save(E entity);

    /**

    • 刪除
    • @param entity 業務實體對象
      */
      public void delete(E entity);

    /**

    • 根據ID刪除
    • @param id 業務實體對象的標識
    • @return 刪除成功返回true不然返回false
      */
      public boolean deleteById(K id);

    /**

    • 修改
    • @param entity 業務實體對象
    • @return 修改爲功返回true不然返回false
      */
      public void update(E entity);

    /**

    • 根據ID查找業務實體對象
    • @param id 業務實體對象的標識
    • @return 業務實體對象對象或null
      */
      public E findById(K id);

    /**

    • 根據ID查找業務實體對象
    • @param id 業務實體對象的標識
    • @param lazy 是否使用延遲加載
    • @return 業務實體對象對象
      */
      public E findById(K id, boolean lazy);

    /**

    • 查找全部業務實體對象
    • @return 裝全部業務實體對象的列表容器
      */
      public List findAll();

    /**

    • 分頁查找業務實體對象
    • @param page 頁碼
    • @param size 頁面大小
    • @return 查詢結果對象
      */
      public QueryResult findByPage(int page, int size);

    /**

    • 分頁查找業務實體對象
    • @param queryBean 查詢條件對象
    • @param page 頁碼
    • @param size 頁面大小
    • @return 查詢結果對象
      */
      public QueryResult findByPage(QueryBean queryBean, int page, int size);

}

package com.jackfrued.dao;

import java.io.Serializable;
import java.util.List;

import com.jackfrued.comm.QueryBean;
import com.jackfrued.comm.QueryResult;

/**

  • BaseDao的缺省適配器

  • @author 駱昊

  • @param 實體類型

  • @param 實體標識字段的類型
    */
    public abstract class BaseDaoAdapter<E, K extends Serializable> implements
    BaseDao<E, K> {

    @Override
    public K save(E entity) {
    return null;
    }

    @Override
    public void delete(E entity) {
    }

    @Override
    public boolean deleteById(K id) {
    E entity = findById(id);
    if(entity != null) {
    delete(entity);
    return true;
    }
    return false;
    }

    @Override
    public void update(E entity) {
    }

    @Override
    public E findById(K id) {
    return null;
    }

    @Override
    public E findById(K id, boolean lazy) {
    return null;
    }

    @Override
    public List findAll() {
    return null;
    }

    @Override
    public QueryResult findByPage(int page, int size) {
    return null;
    }

    @Override
    public QueryResult findByPage(QueryBean queryBean, int page, int size) {
    return null;
    }

}

package com.jackfrued.dao;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;

import com.jackfrued.comm.HQLQueryBean;
import com.jackfrued.comm.QueryBean;
import com.jackfrued.comm.QueryResult;

/**

  • 基於Hibernate的BaseDao實現類

  • @author 駱昊

  • @param 實體類型

  • @param 主鍵類型
    */
    @SuppressWarnings(value = {「unchecked」})
    public abstract class BaseDaoHibernateImpl<E, K extends Serializable> extends BaseDaoAdapter<E, K> {
    @Autowired
    protected SessionFactory sessionFactory;

    private Class<?> entityClass; // 業務實體的類對象
    private String entityName; // 業務實體的名字

    public BaseDaoHibernateImpl() {
    ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
    entityClass = (Class<?>) pt.getActualTypeArguments()[0];
    entityName = entityClass.getSimpleName();
    }

    @Override
    public K save(E entity) {
    return (K) sessionFactory.getCurrentSession().save(entity);
    }

    @Override
    public void delete(E entity) {
    sessionFactory.getCurrentSession().delete(entity);
    }

    @Override
    public void update(E entity) {
    sessionFactory.getCurrentSession().update(entity);
    }

    @Override
    public E findById(K id) {
    return findById(id, false);
    }

    @Override
    public E findById(K id, boolean lazy) {
    Session session = sessionFactory.getCurrentSession();
    return (E) (lazy? session.load(entityClass, id) : session.get(entityClass, id));
    }

    @Override
    public List findAll() {
    return sessionFactory.getCurrentSession().createCriteria(entityClass).list();
    }

    @Override
    public QueryResult findByPage(int page, int size) {
    return new QueryResult(
    findByHQLAndPage("from " + entityName , page, size),
    getCountByHQL("select count(*) from " + entityName)
    );
    }

    @Override
    public QueryResult findByPage(QueryBean queryBean, int page, int size) {
    if(queryBean instanceof HQLQueryBean) {
    HQLQueryBean hqlQueryBean = (HQLQueryBean) queryBean;
    return new QueryResult(
    findByHQLAndPage(hqlQueryBean.getQueryString(), page, size, hqlQueryBean.getParameters()),
    getCountByHQL(hqlQueryBean.getCountString(), hqlQueryBean.getParameters())
    );
    }
    return null;
    }

    /**

    • 根據HQL和可變參數列表進行查詢
    • @param hql 基於HQL的查詢語句
    • @param params 可變參數列表
    • @return 持有查詢結果的列表容器或空列表容器
      */
      protected List findByHQL(String hql, Object… params) {
      return this.findByHQL(hql, getParamList(params));
      }

    /**

    • 根據HQL和參數列表進行查詢
    • @param hql 基於HQL的查詢語句
    • @param params 查詢參數列表
    • @return 持有查詢結果的列表容器或空列表容器
      */
      protected List findByHQL(String hql, List params) {
      List list = createQuery(hql, params).list();
      return list != null && list.size() > 0 ? list : Collections.EMPTY_LIST;
      }

    /**

    • 根據HQL和參數列表進行分頁查詢
    • @param hql 基於HQL的查詢語句
    • @page 頁碼
    • @size 頁面大小
    • @param params 可變參數列表
    • @return 持有查詢結果的列表容器或空列表容器
      */
      protected List findByHQLAndPage(String hql, int page, int size, Object… params) {
      return this.findByHQLAndPage(hql, page, size, getParamList(params));
      }

    /**

    • 根據HQL和參數列表進行分頁查詢
    • @param hql 基於HQL的查詢語句
    • @page 頁碼
    • @size 頁面大小
    • @param params 查詢參數列表
    • @return 持有查詢結果的列表容器或空列表容器
      */
      protected List findByHQLAndPage(String hql, int page, int size, List params) {
      List list = createQuery(hql, params)
      .setFirstResult((page - 1) * size)
      .setMaxResults(size)
      .list();
      return list != null && list.size() > 0 ? list : Collections.EMPTY_LIST;
      }

    /**

    • 查詢知足條件的記錄數
    • @param hql 基於HQL的查詢語句
    • @param params 可變參數列表
    • @return 知足查詢條件的總記錄數
      */
      protected long getCountByHQL(String hql, Object… params) {
      return this.getCountByHQL(hql, getParamList(params));
      }

    /**

    • 查詢知足條件的記錄數
    • @param hql 基於HQL的查詢語句
    • @param params 參數列表容器
    • @return 知足查詢條件的總記錄數
      */
      protected long getCountByHQL(String hql, List params) {
      return (Long) createQuery(hql, params).uniqueResult();
      }

    // 建立Hibernate查詢對象(Query)
    private Query createQuery(String hql, List params) {
    Query query = sessionFactory.getCurrentSession().createQuery(hql);
    for(int i = 0; i < params.size(); i++) {
    query.setParameter(i, params.get(i));
    }
    return query;
    }

    // 將可變參數列表組裝成列表容器
    private List getParamList(Object… params) {
    List paramList = new ArrayList<>();
    if(params != null) {
    for(int i = 0; i < params.length; i++) {
    paramList.add(params[i]);
    }
    }
    return paramList.size() == 0? Collections.EMPTY_LIST : paramList;
    }

}

package com.jackfrued.comm;

import java.util.List;

/**

  • 查詢條件的接口
  • @author 駱昊

*/
public interface QueryBean {

/**
 * 添加排序字段
 * @param fieldName 用於排序的字段
 * @param asc 升序仍是降序
 * @return 查詢條件對象自身(方便級聯編程)
 */
public QueryBean addOrder(String fieldName, boolean asc);

/**
 * 添加排序字段
 * @param available 是否添加此排序字段
 * @param fieldName 用於排序的字段
 * @param asc 升序仍是降序
 * @return 查詢條件對象自身(方便級聯編程)
 */
public QueryBean addOrder(boolean available, String fieldName, boolean asc);

/**
 * 添加查詢條件
 * @param condition 條件
 * @param params 替換掉條件中參數佔位符的參數
 * @return 查詢條件對象自身(方便級聯編程)
 */
public QueryBean addCondition(String condition, Object... params);

/**
 * 添加查詢條件
 * @param available 是否須要添加此條件
 * @param condition 條件
 * @param params 替換掉條件中參數佔位符的參數
 * @return 查詢條件對象自身(方便級聯編程)
 */
public QueryBean addCondition(boolean available, String condition, Object... params);

/**
 * 得到查詢語句
 * @return 查詢語句
 */
public String getQueryString();

/**
 * 獲取查詢記錄數的查詢語句
 * @return 查詢記錄數的查詢語句
 */
public String getCountString();

/**
 * 得到查詢參數
 * @return 查詢參數的列表容器
 */
public List<Object> getParameters();

}

package com.jackfrued.comm;

import java.util.List;

/**

  • 查詢結果

  • @author 駱昊

  • @param 泛型參數
    */
    public class QueryResult {
    private List result; // 持有查詢結果的列表容器
    private long totalRecords; // 查詢到的總記錄數

    /**

    • 構造器
      */
      public QueryResult() {
      }

    /**

    • 構造器
    • @param result 持有查詢結果的列表容器
    • @param totalRecords 查詢到的總記錄數
      */
      public QueryResult(List result, long totalRecords) {
      this.result = result;
      this.totalRecords = totalRecords;
      }

    public List getResult() {
    return result;
    }

    public void setResult(List result) {
    this.result = result;
    }

    public long getTotalRecords() {
    return totalRecords;
    }

    public void setTotalRecords(long totalRecords) {
    this.totalRecords = totalRecords;
    }
    }

package com.jackfrued.dao;

import com.jackfrued.comm.QueryResult;
import com.jackfrued.entity.Dept;

/**

  • 部門數據訪問對象接口
  • @author 駱昊

*/
public interface DeptDao extends BaseDao<Dept, Integer> {

/**
 * 分頁查詢頂級部門
 * @param page 頁碼
 * @param size 頁碼大小
 * @return 查詢結果對象
 */
public QueryResult<Dept> findTopDeptByPage(int page, int size);

}

package com.jackfrued.dao.impl;

import java.util.List;

import org.springframework.stereotype.Repository;

import com.jackfrued.comm.QueryResult;
import com.jackfrued.dao.BaseDaoHibernateImpl;
import com.jackfrued.dao.DeptDao;
import com.jackfrued.entity.Dept;

@Repository
public class DeptDaoImpl extends BaseDaoHibernateImpl<Dept, Integer> implements DeptDao {
private static final String HQL_FIND_TOP_DEPT = " from Dept as d where d.superiorDept is null ";

@Override
public QueryResult<Dept> findTopDeptByPage(int page, int size) {
    List<Dept> list = findByHQLAndPage(HQL_FIND_TOP_DEPT, page, size);
    long totalRecords = getCountByHQL(" select count(*) " + HQL_FIND_TOP_DEPT);
    return new QueryResult<>(list, totalRecords);
}

}

package com.jackfrued.comm;

import java.util.List;

/**

  • 分頁器

  • @author 駱昊

  • @param 分頁數據對象的類型
    */
    public class PageBean {
    private static final int DEFAUL_INIT_PAGE = 1;
    private static final int DEFAULT_PAGE_SIZE = 10;
    private static final int DEFAULT_PAGE_COUNT = 5;

    private List data; // 分頁數據
    private PageRange pageRange; // 頁碼範圍
    private int totalPage; // 總頁數
    private int size; // 頁面大小
    private int currentPage; // 當前頁碼
    private int pageCount; // 頁碼數量

    /**

    • 構造器
    • @param currentPage 當前頁碼
    • @param size 頁碼大小
    • @param pageCount 頁碼數量
      */
      public PageBean(int currentPage, int size, int pageCount) {
      this.currentPage = currentPage > 0 ? currentPage : 1;
      this.size = size > 0 ? size : DEFAULT_PAGE_SIZE;
      this.pageCount = pageCount > 0 ? size : DEFAULT_PAGE_COUNT;
      }

    /**

    • 構造器
    • @param currentPage 當前頁碼
    • @param size 頁碼大小
      */
      public PageBean(int currentPage, int size) {
      this(currentPage, size, DEFAULT_PAGE_COUNT);
      }

    /**

    • 構造器
    • @param currentPage 當前頁碼
      */
      public PageBean(int currentPage) {
      this(currentPage, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_COUNT);
      }

    /**

    • 構造器
      */
      public PageBean() {
      this(DEFAUL_INIT_PAGE, DEFAULT_PAGE_SIZE, DEFAULT_PAGE_COUNT);
      }

    public List getData() {
    return data;
    }

    public int getStartPage() {
    return pageRange != null ? pageRange.getStartPage() : 1;
    }

    public int getEndPage() {
    return pageRange != null ? pageRange.getEndPage() : 1;
    }

    public long getTotalPage() {
    return totalPage;
    }

    public int getSize() {
    return size;
    }

    public int getCurrentPage() {
    return currentPage;
    }

    /**

    • 將查詢結果轉換爲分頁數據

    • @param queryResult 查詢結果對象
      */
      public void transferQueryResult(QueryResult queryResult) {
      long totalRecords = queryResult.getTotalRecords();

      data = queryResult.getResult();
      totalPage = (int) ((totalRecords + size - 1) / size);
      totalPage = totalPage >= 0 ? totalPage : Integer.MAX_VALUE;
      this.pageRange = new PageRange(pageCount, currentPage, totalPage);
      }

}

package com.jackfrued.comm;

/**

  • 頁碼範圍
  • @author 駱昊

*/
public class PageRange {
private int startPage; // 起始頁碼
private int endPage; // 終止頁碼

/**
 * 構造器
 * @param pageCount 總共顯示幾個頁碼
 * @param currentPage 當前頁碼
 * @param totalPage 總頁數
 */
public PageRange(int pageCount, int currentPage, int totalPage) {
    startPage = currentPage - (pageCount - 1) / 2;
    endPage = currentPage + pageCount / 2;
    if(startPage < 1) {
        startPage = 1;
        endPage = totalPage > pageCount ? pageCount : totalPage;
    }
    if (endPage > totalPage) {
        endPage = totalPage;
        startPage = (endPage - pageCount > 0) ? endPage - pageCount + 1 : 1;
    }
}

/**
 * 得到起始頁頁碼
 * @return 起始頁頁碼
 */
public int getStartPage() {
    return startPage;
}

/**
 * 得到終止頁頁碼
 * @return 終止頁頁碼
 */
public int getEndPage() {
    return endPage;
}

}

package com.jackfrued.biz;

import com.jackfrued.comm.PageBean;
import com.jackfrued.entity.Dept;

/**

  • 部門業務邏輯接口
  • @author 駱昊

*/
public interface DeptService {

/**
 * 建立新的部門
 * @param department 部門對象
 * @return 建立成功返回true不然返回false
 */
public boolean createNewDepartment(Dept department);

/**
 * 刪除指定部門
 * @param id 要刪除的部門的編號
 * @return 刪除成功返回true不然返回false
 */
public boolean deleteDepartment(Integer id);

/**
 * 分頁獲取頂級部門
 * @param page 頁碼
 * @param size 頁碼大小
 * @return 部門對象的分頁器對象
 */
public PageBean<Dept> getTopDeptByPage(int page, int size);

}

package com.jackfrued.biz.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.jackfrued.biz.DeptService;
import com.jackfrued.comm.PageBean;
import com.jackfrued.comm.QueryResult;
import com.jackfrued.dao.DeptDao;
import com.jackfrued.entity.Dept;

@Service
@Transactional // 聲明式事務的註解
public class DeptServiceImpl implements DeptService {
@Autowired
private DeptDao deptDao;

@Override
public boolean createNewDepartment(Dept department) {
    return deptDao.save(department) != null;
}

@Override
public boolean deleteDepartment(Integer id) {
    return deptDao.deleteById(id);
}

@Override
public PageBean<Dept> getTopDeptByPage(int page, int size) {
    QueryResult<Dept> queryResult = deptDao.findTopDeptByPage(page, size);
    PageBean<Dept> pageBean = new PageBean<>(page, size);
    pageBean.transferQueryResult(queryResult);
    return pageBean;
}

}

15四、如何在Web項目中配置Spring的IoC容器?
答:若是須要在Web項目中使用Spring的IoC容器,能夠在Web項目配置文件web.xml中作出以下配置:

contextConfigLocation classpath:applicationContext.xml org.springframework.web.context.ContextLoaderListener
15五、如何在Web項目中配置Spring MVC?
答:要使用Spring MVC須要在Web項目配置文件中配置其前端控制器DispatcherServlet,以下所示:

說明:上面的配置中使用了*.html的後綴映射,這樣作一方面不可以經過URL推斷採用了何種服務器端的技術,另外一方面能夠欺騙搜索引擎,由於搜索引擎不會搜索動態頁面,這種作法稱爲僞靜態化。

<web-app>

    <servlet>
        <servlet-name>example</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>example</servlet-name>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>

</web-app>

15六、Spring MVC的工做原理是怎樣的?
答:Spring MVC的工做原理以下圖所示:
這裏寫圖片描述
① 客戶端的全部請求都交給前端控制器DispatcherServlet來處理,它會負責調用系統的其餘模塊來真正處理用戶的請求。
② DispatcherServlet收到請求後,將根據請求的信息(包括URL、HTTP協議方法、請求頭、請求參數、Cookie等)以及HandlerMapping的配置找處處理該請求的Handler(任何一個對象均可以做爲請求的Handler)。
③在這個地方Spring會經過HandlerAdapter對該處理器進行封裝。
④ HandlerAdapter是一個適配器,它用統一的接口對各類Handler中的方法進行調用。
⑤ Handler完成對用戶請求的處理後,會返回一個ModelAndView對象給DispatcherServlet,ModelAndView顧名思義,包含了數據模型以及相應的視圖的信息。
⑥ ModelAndView的視圖是邏輯視圖,DispatcherServlet還要藉助ViewResolver完成從邏輯視圖到真實視圖對象的解析工做。
⑦ 當獲得真正的視圖對象後,DispatcherServlet會利用視圖對象對模型數據進行渲染。
⑧ 客戶端獲得響應,多是一個普通的HTML頁面,也能夠是XML或JSON字符串,還能夠是一張圖片或者一個PDF文件。

15七、如何在Spring IoC容器中配置數據源?
答:
DBCP配置:






<context:property-placeholder location=「jdbc.properties」/>

C3P0配置:






<context:property-placeholder location=「jdbc.properties」/>

提示: DBCP的詳細配置在第153題中已經完整的展現過了。

15八、如何配置配置事務加強?
答:

<?xml version="1.0" encoding="UTF-8"?>

<tx:advice id=「txAdvice」 transaction-manager=「txManager」>

tx:attributes

<tx:method name=「get*」 read-only=「true」/>

<tx:method name="*"/>
</tx:attributes>
</tx:advice>

aop:config
<aop:pointcut id=「fooServiceOperation」
expression=「execution(* x.y.service.FooService.*(…))」/>
<aop:advisor advice-ref=「txAdvice」 pointcut-ref=「fooServiceOperation」/>
</aop:config>






15九、選擇使用Spring框架的緣由(Spring框架爲企業級開發帶來的好處有哪些)?
答:能夠從如下幾個方面做答:

  • 非侵入式:支持基於POJO的編程模式,不強制性的要求實現Spring框架中的接口或繼承Spring框架中的類。
  • IoC容器:IoC容器幫助應用程序管理對象以及對象之間的依賴關係,對象之間的依賴關係若是發生了改變只須要修改配置文件而不是修改代碼,由於代碼的修改可能意味着項目的從新構建和完整的迴歸測試。有了IoC容器,程序員不再須要本身編寫工廠、單例,這一點特別符合Spring的精神"不要重複的發明輪子"。
  • AOP(面向切面編程):將全部的橫切關注功能封裝到切面(aspect)中,經過配置的方式將橫切關注功能動態添加到目標代碼上,進一步實現了業務邏輯和系統服務之間的分離。另外一方面,有了AOP程序員能夠省去不少本身寫代理類的工做。
  • MVC:Spring的MVC框架是很是優秀的,從各個方面均可以甩Struts 2幾條街,爲Web表示層提供了更好的解決方案。
  • 事務管理:Spring以寬廣的胸懷接納多種持久層技術,而且爲其提供了聲明式的事務管理,在不須要任何一行代碼的狀況下就可以完成事務管理。
  • 其餘:選擇Spring框架的緣由還遠不止於此,Spring爲Java企業級開發提供了一站式選擇,你能夠在須要的時候使用它的部分和所有,更重要的是,你甚至能夠在感受不到Spring存在的狀況下,在你的項目中使用Spring提供的各類優秀的功能。

160、Spring IoC容器配置Bean的方式?
答:

  • 基於XML文件進行配置。
  • 基於註解進行配置。
  • 基於Java程序進行配置(Spring 3+)

package com.jackfrued.bean;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Person {
private String name;
private int age;
@Autowired
private Car car;

public Person(String name, int age) {
    this.name = name;
    this.age = age;
}

public void setCar(Car car) {
    this.car = car;
}

@Override
public String toString() {
    return "Person [name=" + name + ", age=" + age + ", car=" + car + "]";
}

}

package com.jackfrued.bean;

import org.springframework.stereotype.Component;

@Component
public class Car {
private String brand;
private int maxSpeed;

public Car(String brand, int maxSpeed) {
    this.brand = brand;
    this.maxSpeed = maxSpeed;
}

@Override
public String toString() {
    return "Car [brand=" + brand + ", maxSpeed=" + maxSpeed + "]";
}

}

package com.jackfrued.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.jackfrued.bean.Car;
import com.jackfrued.bean.Person;

@Configuration
public class AppConfig {

@Bean
public Car car() {
    return new Car("Benz", 320);
}

@Bean
public Person person() {
    return new Person("駱昊", 34);
}

}

package com.jackfrued.test;

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import com.jackfrued.bean.Person;
import com.jackfrued.config.AppConfig;

class Test {

public static void main(String[] args) {
    // TWR (Java 7+)
    try(ConfigurableApplicationContext factory = new AnnotationConfigApplicationContext(AppConfig.class)) {
        Person person = factory.getBean(Person.class);
        System.out.println(person);
    }
}

}

16一、闡述Spring框架中Bean的生命週期?
答:
① Spring IoC容器找到關於Bean的定義並實例化該Bean。
② Spring IoC容器對Bean進行依賴注入。
③ 若是Bean實現了BeanNameAware接口,則將該Bean的id傳給setBeanName方法。
④ 若是Bean實現了BeanFactoryAware接口,則將BeanFactory對象傳給setBeanFactory方法。
⑤ 若是Bean實現了BeanPostProcessor接口,則調用其postProcessBeforeInitialization方法。
⑥ 若是Bean實現了InitializingBean接口,則調用其afterPropertySet方法。
⑦ 若是有和Bean關聯的BeanPostProcessors對象,則這些對象的postProcessAfterInitialization方法被調用。
⑧ 當銷燬Bean實例時,若是Bean實現了DisposableBean接口,則調用其destroy方法。

16二、依賴注入時如何注入集合屬性?
答:能夠在定義Bean屬性時,經過 / / / 分別爲其注入列表、集合、映射和鍵值都是字符串的映射屬性。

16三、Spring中的自動裝配有哪些限制?
答:

  • 若是使用了構造器注入或者setter注入,那麼將覆蓋自動裝配的依賴關係。
  • 基本數據類型的值、字符串字面量、類字面量沒法使用自動裝配來注入。
  • 優先考慮使用顯式的裝配來進行更精確的依賴注入而不是使用自動裝配。

16四、在Web項目中如何得到Spring的IoC容器?
答:

WebApplicationContext ctx =
WebApplicationContextUtils.getWebApplicationContext(servletContext);

  1. 大型網站在架構上應當考慮哪些問題?
    答:
  • 分層:分層是處理任何複雜系統最多見的手段之一,將系統橫向切分紅若干個層面,每一個層面只承擔單一的職責,而後經過下層爲上層提供的基礎設施和服務以及上層對下層的調用來造成一個完整的複雜的系統。計算機網絡的開放系統互聯參考模型(OSI/RM)和Internet的TCP/IP模型都是分層結構,大型網站的軟件系統也能夠使用分層的理念將其分爲持久層(提供數據存儲和訪問服務)、業務層(處理業務邏輯,系統中最核心的部分)和表示層(系統交互、視圖展現)。須要指出的是:(1)分層是邏輯上的劃分,在物理上能夠位於同一設備上也能夠在不一樣的設備上部署不一樣的功能模塊,這樣能夠使用更多的計算資源來應對用戶的併發訪問;(2)層與層之間應當有清晰的邊界,這樣分層纔有意義,才更利於軟件的開發和維護。
  • 分割:分割是對軟件的縱向切分。咱們能夠將大型網站的不一樣功能和服務分割開,造成高內聚低耦合的功能模塊(單元)。在設計初期能夠作一個粗粒度的分割,將網站分割爲若干個功能模塊,後期還能夠進一步對每一個模塊進行細粒度的分割,這樣一方面有助於軟件的開發和維護,另外一方面有助於分佈式的部署,提供網站的併發處理能力和功能的擴展。
  • 分佈式:除了上面提到的內容,網站的靜態資源(JavaScript、CSS、圖片等)也能夠採用獨立分佈式部署並採用獨立的域名,這樣能夠減輕應用服務器的負載壓力,也使得瀏覽器對資源的加載更快。數據的存取也應該是分佈式的,傳統的商業級關係型數據庫產品基本上都支持分佈式部署,而新生的NoSQL產品幾乎都是分佈式的。固然,網站後臺的業務處理也要使用分佈式技術,例如查詢索引的構建、數據分析等,這些業務計算規模龐大,能夠使用Hadoop以及MapReduce分佈式計算框架來處理。
  • 集羣:集羣使得有更多的服務器提供相同的服務,能夠更好的提供對併發的支持。
  • 緩存:所謂緩存就是用空間換取時間的技術,將數據儘量放在距離計算最近的位置。使用緩存是網站優化的第必定律。咱們一般說的CDN、反向代理、熱點數據都是對緩存技術的使用。
  • 異步:異步是實現軟件實體之間解耦合的又一重要手段。異步架構是典型的生產者消費者模式,兩者之間沒有直接的調用關係,只要保持數據結構不變,彼此功能實現能夠隨意變化而不互相影響,這對網站的擴展很是有利。使用異步處理還能夠提升系統可用性,加快網站的響應速度(用Ajax加載數據就是一種異步技術),同時還能夠起到削峯做用(應對瞬時高併發)。&quot;能推遲處理的都要推遲處理"是網站優化的第二定律,而異步是踐行網站優化第二定律的重要手段。
  • 冗餘:各類服務器都要提供相應的冗餘服務器以便在某臺或某些服務器宕機時還能保證網站能夠正常工做,同時也提供了災難恢復的可能性。冗餘是網站高可用性的重要保證。

16六、你用過的網站前端優化的技術有哪些?
答:
① 瀏覽器訪問優化:

  • 減小HTTP請求數量:合併CSS、合併JavaScript、合併圖片(CSS Sprite)
  • 使用瀏覽器緩存:經過設置HTTP響應頭中的Cache-Control和Expires屬性,將CSS、JavaScript、圖片等在瀏覽器中緩存,當這些靜態資源須要更新時,能夠更新HTML文件中的引用來讓瀏覽器從新請求新的資源
  • 啓用壓縮
  • CSS前置,JavaScript後置
  • 減小Cookie傳輸
    ② CDN加速:CDN(Content Distribute Network)的本質仍然是緩存,將數據緩存在離用戶最近的地方,CDN一般部署在網絡運營商的機房,不只能夠提高響應速度,還能夠減小應用服務器的壓力。固然,CDN緩存的一般都是靜態資源。
    ③ 反向代理:反向代理至關於應用服務器的一個門面,能夠保護網站的安全性,也能夠實現負載均衡的功能,固然最重要的是它緩存了用戶訪問的熱點資源,能夠直接從反向代理將某些內容返回給用戶瀏覽器。

16七、你使用過的應用服務器優化技術有哪些?
答:
① 分佈式緩存:緩存的本質就是內存中的哈希表,若是設計一個優質的哈希函數,那麼理論上哈希表讀寫的漸近時間複雜度爲O(1)。緩存主要用來存放那些讀寫比很高、變化不多的數據,這樣應用程序讀取數據時先到緩存中讀取,若是沒有或者數據已經失效再去訪問數據庫或文件系統,並根據擬定的規則將數據寫入緩存。對網站數據的訪問也符合二八定律(Pareto分佈,冪律分佈),即80%的訪問都集中在20%的數據上,若是可以將這20%的數據緩存起來,那麼系統的性能將獲得顯著的改善。固然,使用緩存須要解決如下幾個問題:

  • 頻繁修改的數據;
  • 數據不一致與髒讀;
  • 緩存雪崩(能夠採用分佈式緩存服務器集羣加以解決,memcached是普遍採用的解決方案);
  • 緩存預熱;
  • 緩存穿透(惡意持續請求不存在的數據)。
    ② 異步操做:能夠使用消息隊列將調用異步化,經過異步處理將短期高併發產生的事件消息存儲在消息隊列中,從而起到削峯做用。電商網站在進行促銷活動時,能夠將用戶的訂單請求存入消息隊列,這樣能夠抵禦大量的併發訂單請求對系統和數據庫的衝擊。目前,絕大多數的電商網站即使不進行促銷活動,訂單系統都採用了消息隊列來處理。
    ③ 使用集羣。
    ④ 代碼優化:
  • 多線程:基於Java的Web開發基本上都經過多線程的方式響應用戶的併發請求,使用多線程技術在編程上要解決線程安全問題,主要能夠考慮如下幾個方面:A. 將對象設計爲無狀態對象(這和麪向對象的編程觀點是矛盾的,在面向對象的世界中被視爲不良設計),這樣就不會存在併發訪問時對象狀態不一致的問題。B. 在方法內部建立對象,這樣對象由進入方法的線程建立,不會出現多個線程訪問同一對象的問題。使用ThreadLocal將對象與線程綁定也是很好的作法,這一點在前面已經探討過了。C. 對資源進行併發訪問時應當使用合理的鎖機制。
  • 非阻塞I/O: 使用單線程和非阻塞I/O是目前公認的比多線程的方式更能充分發揮服務器性能的應用模式,基於Node.js構建的服務器就採用了這樣的方式。Java在JDK 1.4中就引入了NIO(Non-blocking I/O),在Servlet 3規範中又引入了異步Servlet的概念,這些都爲在服務器端採用非阻塞I/O提供了必要的基礎。
  • 資源複用:資源複用主要有兩種方式,一是單例,二是對象池,咱們使用的數據庫鏈接池、線程池都是對象池化技術,這是典型的用空間換取時間的策略,另外一方面也實現對資源的複用,從而避免了沒必要要的建立和釋放資源所帶來的開銷。

16八、什麼是XSS攻擊?什麼是SQL注入攻擊?什麼是CSRF攻擊?
答:

  • XSS(Cross Site Script,跨站腳本攻擊)是向網頁中注入惡意腳本在用戶瀏覽網頁時在用戶瀏覽器中執行惡意腳本的攻擊方式。跨站腳本攻擊分有兩種形式:反射型攻擊(誘使用戶點擊一個嵌入惡意腳本的連接以達到攻擊的目標,目前有不少攻擊者利用論壇、微博發佈含有惡意腳本的URL就屬於這種方式)和持久型攻擊(將惡意腳本提交到被攻擊網站的數據庫中,用戶瀏覽網頁時,惡意腳本從數據庫中被加載到頁面執行,QQ郵箱的早期版本就曾經被利用做爲持久型跨站腳本攻擊的平臺)。XSS雖然不是什麼新鮮玩意,可是攻擊的手法卻不斷翻新,防範XSS主要有兩方面:消毒(對危險字符進行轉義)和HttpOnly(防範XSS攻擊者竊取Cookie數據)。
  • SQL注入攻擊是注入攻擊最多見的形式(此外還有OS注入攻擊(Struts 2的高危漏洞就是經過OGNL實施OS注入攻擊致使的)),當服務器使用請求參數構造SQL語句時,惡意的SQL被嵌入到SQL中交給數據庫執行。SQL注入攻擊須要攻擊者對數據庫結構有所瞭解才能進行,攻擊者想要得到表結構有多種方式:(1)若是使用開源系統搭建網站,數據庫結構也是公開的(目前有不少現成的系統能夠直接搭建論壇,電商網站,雖然方便快捷可是風險是必需要認真評估的);(2)錯誤回顯(若是將服務器的錯誤信息直接顯示在頁面上,攻擊者能夠經過非法參數引起頁面錯誤從而經過錯誤信息瞭解數據庫結構,Web應用應當設置友好的錯誤頁,一方面符合最小驚訝原則,一方面屏蔽掉可能給系統帶來危險的錯誤回顯信息);(3)盲注。防範SQL注入攻擊也能夠採用消毒的方式,經過正則表達式對請求參數進行驗證,此外,參數綁定也是很好的手段,這樣惡意的SQL會被當作SQL的參數而不是命令被執行,JDBC中的PreparedStatement就是支持參數綁定的語句對象,從性能和安全性上都明顯優於Statement。
  • CSRF攻擊(Cross Site Request Forgery,跨站請求僞造)是攻擊者經過跨站請求,以合法的用戶身份進行非法操做(如轉帳或發帖等)。CSRF的原理是利用瀏覽器的Cookie或服務器的Session,盜取用戶身份,其原理以下圖所示。防範CSRF的主要手段是識別請求者的身份,主要有如下幾種方式:(1)在表單中添加令牌(token);(2)驗證碼;(3)檢查請求頭中的Referer(前面提到防圖片盜連接也是用的這種方式)。令牌和驗證都具備一次消費性的特徵,所以在原理上一致的,可是驗證碼是一種糟糕的用戶體驗,不是必要的狀況下不要輕易使用驗證碼,目前不少網站的作法是若是在短期內屢次提交一個表單未得到成功後纔要求提供驗證碼,這樣會得到較好的用戶體驗。

這裏寫圖片描述

補充:防火牆的架設是Web安全的重要保障,ModSecurity是開源的Web防火牆中的佼佼者。企業級防火牆的架設應當有兩級防火牆,Web服務器和部分應用服務器能夠架設在兩級防火牆之間的DMZ,而數據和資源服務器應當架設在第二級防火牆以後。

  1. 什麼是領域模型(domain model)?貧血模型(anaemic domain model)和充血模型(rich domain model)有什麼區別?
    答:領域模型是領域內的概念類或現實世界中對象的可視化表示,又稱爲概念模型或分析對象模型,它專一於分析問題領域自己,發掘重要的業務領域概念,並創建業務領域概念之間的關係。貧血模型是指使用的領域對象中只有setter和getter方法(POJO),全部的業務邏輯都不包含在領域對象中而是放在業務邏輯層。有人將咱們這裏說的貧血模型進一步劃分紅失血模型(領域對象徹底沒有業務邏輯)和貧血模型(領域對象有少許的業務邏輯),咱們這裏就不對此加以區分了。充血模型將大多數業務邏輯和持久化放在領域對象中,業務邏輯(業務門面)只是完成對業務邏輯的封裝、事務和權限等的處理。下面兩張圖分別展現了貧血模型和充血模型的分層架構。

貧血模型
這裏寫圖片描述

充血模型
這裏寫圖片描述

貧血模型下組織領域邏輯一般使用事務腳本模式,讓每一個過程對應用戶可能要作的一個動做,每一個動做由一個過程來驅動。也就是說在設計業務邏輯接口的時候,每一個方法對應着用戶的一個操做,這種模式有如下幾個有點:

  • 它是一個大多數開發者都可以理解的簡單過程模型(適合國內的絕大多數開發者)。
  • 它可以與一個使用行數據入口或表數據入口的簡單數據訪問層很好的協做。
  • 事務邊界的顯而易見,一個事務開始於腳本的開始,終止於腳本的結束,很容易經過代理(或切面)實現聲明式事務。
    然而,事務腳本模式的缺點也是不少的,隨着領域邏輯複雜性的增長,系統的複雜性將迅速增長,程序結構將變得極度混亂。開源中國社區上有一篇很好的譯文《貧血領域模型是如何致使糟糕的軟件產生》對這個問題作了比較細緻的闡述。
  1. 談一談測試驅動開發(TDD)的好處以及你的理解。
    答:TDD是指在編寫真正的功能實現代碼以前先寫測試代碼,而後根據須要重構實現代碼。在JUnit的做者Kent Beck的大做《測試驅動開發:實戰與模式解析》(Test-Driven Development: by Example)一書中有這麼一段內容:「消除恐懼和不肯定性是編寫測試驅動代碼的重要緣由」。由於編寫代碼時的恐懼會讓你當心試探,讓你迴避溝通,讓你羞於獲得反饋,讓你變得焦躁不安,而TDD是消除恐懼、讓Java開發者更加自信更加樂於溝通的重要手段。TDD會帶來的好處可能不會立刻呈現,可是你在某個時候必定會發現,這些好處包括:
  • 更清晰的代碼 — 只寫須要的代碼
  • 更好的設計
  • 更出色的靈活性 — 鼓勵程序員面向接口編程
  • 更快速的反饋 — 不會到系統上線時才知道bug的存在

補充:敏捷軟件開發的概念已經有不少年了,並且也部分的改變了軟件開發這個行業,TDD也是敏捷開發所倡導的。

TDD能夠在多個層級上應用,包括單元測試(測試一個類中的代碼)、集成測試(測試類之間的交互)、系統測試(測試運行的系統)和系統集成測試(測試運行的系統包括使用的第三方組件)。TDD的實施步驟是:紅(失敗測試)- 綠(經過測試) - 重構。
在使用TDD開發時,常常會遇到須要被測對象須要依賴其餘子系統的狀況,可是你但願將測試代碼跟依賴項隔離,以保證測試代碼僅僅針對當前被測對象或方法展開,這時候你須要的是測試替身。測試替身能夠分爲四類:

  • 虛設替身:只傳遞可是不會使用到的對象,通常用於填充方法的參數列表
  • 存根替身:老是返回相同的預設響應,其中可能包括一些虛設狀態
  • 假裝替身:能夠取代真實版本的可用版本(比真實版本仍是會差不少)
  • 模擬替身:能夠表示一系列指望值的對象,而且能夠提供預設響應 Java世界中實現模擬替身的第三方工具很是多,包括EasyMock、Mockito、jMock等。