【經典】《Java170道面試筆試題全面含答案》涉及java/數據庫/Spring框架/JVM/數據結構算法/設計模式相關
《Java170道面試筆試題全集》 -更新版-8.30css
2018/4/7 平常修復html
2017/12/28 更新文章前端
一、添加二級目錄java
二、對部分問題進行了補充mysql
9/24緊急修改如下問題(存在嚴重錯誤)問題3;
完善
問題十、11
問題目錄:
一、面向對象的特徵有哪些方面?二、訪問修飾符public,private,protected,以及不寫(默認)時的區別?
三、String 是最基本的數據類型嗎?
四、float f=3.4;是否正確?
五、short s1 = 1; s1 = s1 + 1;有錯嗎?short s1 = 1; s1 += 1;有錯嗎?
六、Java有沒有goto?
七、int和Integer有什麼區別?
八、&和&&的區別?
九、解釋內存中的棧(stack)、堆(heap)和靜態區(static area)的用法。
十、Math.round(11.5) 等於多少?Math.round(-11.5)等於多少?
十一、switch 是否能做用在byte 上,是否能做用在long 上,是否能做用在String上?
十二、用最有效率的方法計算2乘以8?
1三、數組有沒有length()方法?String有沒有length()方法?
1四、在Java中,如何跳出當前的多重嵌套循環?
1五、構造器(constructor)是否可被重寫(override)?
1六、兩個對象值相同(x.equals(y) == true),但卻可有不一樣的hash code,這句話對不對?
1七、是否能夠繼承String類?
1八、當一個對象被看成參數傳遞到一個方法後,此方法可改變這個對象的屬性,並可返回變化後的結果,那麼這裏究竟是值傳遞仍是引用傳遞?
1九、String和StringBuilder、StringBuffer的區別?
20、重載(Overload)和重寫(Override)的區別。重載的方法可否根據返回類型進行區分?
2一、描述一下JVM加載class文件的原理機制?
2二、char 型變量中能不能存貯一箇中文漢字,爲何?
2三、抽象類(abstract class)和接口(interface)有什麼異同?
2四、靜態嵌套類(Static Nested Class)和內部類(Inner Class)的不一樣?
2五、Java 中會存在內存泄漏嗎,請簡單描述。
2六、抽象的(abstract)方法是否可同時是靜態的(static),是否可同時是本地方法(native),是否可同時被synchronized修飾?
2七、闡述靜態變量和實例變量的區別。
2八、是否能夠從一個靜態(static)方法內部發出對非靜態(non-static)方法的調用?
2九、如何實現對象克隆?
3一、String s = new String("xyz");建立了幾個字符串對象?
3二、接口是否可繼承(extends)接口?抽象類是否可實現(implements)接口?抽象類是否可繼承具體類(concrete class)?
3三、一個".java"源文件中是否能夠包含多個類(不是內部類)?有什麼限制?
3四、Anonymous Inner Class(匿名內部類)是否能夠繼承其它類?是否能夠實現接口?
3五、內部類能夠引用它的包含類(外部類)的成員嗎?有沒有什麼限制?
3六、Java 中的final關鍵字有哪些用法?android
3七、運行結果題git
3八、數據類型之間的轉換:3九、如何實現字符串的反轉及替換?
40、怎樣將smxfl.cn編碼的字符串轉換爲lrzqb.cn編碼的字符串?
4一、日期和時間:
4二、打印昨天的當前時刻。
4三、比較一下Java和JavaSciprt。
4四、何時用斷言(assert)?
4五、Error和Exception有什麼區別?
4六、try{}裏有一個return語句,那麼緊跟在這個try後的finally{}裏的代碼會不會被執行,何時被執行,在return前仍是後?
4七、Java語言如何進行異常處理,關鍵字:throws、throw、try、catch、finally分別如何使用?
4八、運行時異常與受檢異常有何異同?
4九、列出一些你常見的運行時異常?
50、闡述final、finally、finalize的區別。
5一、類ExampleA繼承Exception,類ExampleB繼承ExampleA。
請問執行此段代碼的輸出是什麼?
5二、List、Set、Map是否繼承自Collection接口?
5三、闡述ArrayList、Vector、LinkedList的存儲性能和特性。
5四、Collection和Collections的區別?
5五、List、Map、Set三個接口存取元素時,各有什麼特色?
5六、TreeMap和TreeSet在排序時如何比較元素?Collections工具類中的sort()方法如何比較元素?
5七、Thread類的sleep()方法和對象的wait()方法均可以讓線程暫停執行,它們有什麼區別?
5八、線程的sleep()方法和yield()方法有什麼區別?
5九、當一個線程進入一個對象的synchronized方法A以後,其它線程是否可進入此對象的synchronized方法B?
60、請說出與線程同步以及線程調度相關的方法。
6一、編寫多線程程序有幾種實現方式?
6二、synchronized關鍵字的用法?
6三、舉例說明同步和異步。
6四、啓動一個線程是調用run()仍是start()方法?
6五、什麼是線程池?
6六、線程的基本狀態以及狀態之間的關係?
6七、簡述synchronized 和java.util.concurrent.locks.Lock的異同?
6八、Java中如何實現序列化,有什麼意義?
6九、Java中有幾種類型的流?
70、寫一個方法,輸入一個文件名和一個字符串,統計這個字符串在這個文件中出現的次數。
7一、如何用Java代碼列出一個目錄下全部的文件?
7二、用Java的套接字編程實現一個多線程的回顯(tqkdz.cn)服務器。
7三、XML文檔定義有幾種形式?它們之間有何本質區別?解析XML文檔有哪幾種方式?
7四、你在項目中哪些地方用到了XML?
7五、闡述JDBC操做數據庫的步驟。
7六、Statement和PreparedStatement有什麼區別?哪一個性能更好?
7七、使用JDBC操做數據庫時,如何提高讀取數據的性能?如何提高更新數據的性能?
7八、在進行數據庫編程時,鏈接池有什麼做用?
7九、什麼是DAO模式?
80、事務的ACID是指什麼?
8一、JDBC中如何進行事務處理?
8二、JDBC可否處理Blob和bqcrg.cn
8三、簡述正則表達式及其用途。
8四、Java中是如何支持正則表達式操做的?
8五、得到一個類的類對象有哪些方式?
8六、如何經過反射建立對象?
8七、如何經過反射獲取和設置對象私有字段的值?
8八、如何經過反射調用對象的方法?
8九、簡述一下面向對象的"六原則一法則"。
90、簡述一下你瞭解的設計模式。
9一、用Java寫一個單例類。
9二、什麼是UML?
9三、UML中有哪些經常使用的圖?
9四、用Java寫一個冒泡排序。
9五、用Java寫一個折半查找。
9六、闡述Servlet和CGI的區別?
9七、Servlet接口中有哪些方法?
9八、轉發和重定向的區別?
9九、JSP有哪些內置對象?做用分別是什麼?
100、get和post請求的區別?
10一、經常使用的Web服務器有哪些?
10二、JSP和Servlet是什麼關係?
10三、講解JSP中的四種做用域。
10四、如何實現JSP或Servlet的單線程模式?
10五、實現會話跟蹤的技術有哪些?
10六、過濾器有哪些做用和用法?
10七、監聽器有哪些做用和用法?
10八、web.xml文件中能夠配置哪些內容?
10九、你的項目中使用過哪些JSTL標籤?
1十、使用標籤庫有什麼好處?如何自定義JSP標籤?
1十一、說一下表達式語言(EL)的隱式對象及其做用。
1十二、表達式語言(EL)支持哪些運算符?
11三、Java Web開發的Model 1和Model 2分別指的是什麼?
11四、Servlet 3中的異步處理指的是什麼?
11五、如何在基於Java的Web項目中實現文件上傳和下載?
11六、服務器收到用戶提交的表單數據,究竟是調用Servlet的doGet()仍是doPost()方法?
11七、JSP中的靜態包含和動態包含有什麼區別?
11八、Servlet中如何獲取用戶提交的查詢參數或表單數據?
11九、Servlet中如何獲取用戶配置的初始化參數以及服務器上下文參數?
120、如何設置請求的編碼以及響應內容的類型?
12一、解釋一下網絡應用的模式及其特色。
12二、什麼是Web Service(Web服務)?
12三、概念解釋:SOAP、WSDL、UDDI。
12四、Java規範中和Web Service相關的規範有哪些?
12五、介紹一下你瞭解的Java領域的Web Service框架。
12六、什麼是ORM?
12七、持久層設計要考慮的問題有哪些?你用過的持久層框架有哪些?
12八、Hibernate中SessionFactory是線程安全的嗎?Session是線程安全的嗎(兩個線程可以共享同一個Session嗎)?
12九、Hibernate中Session的load和get方法的區別是什麼?
130、fyrqh.cn的save()、update()、merge()、lock()、saveOrUpdate()和persist()方法分別是作什麼的?有什麼區別?
13一、闡述Session加載實體對象的過程。
13二、Query接口的list方法和iterate方法有什麼區別?
13三、Hibernate如何實現分頁查詢?
13四、鎖機制有什麼用?簡述Hibernate的悲觀鎖和樂觀鎖機制。
13五、闡述實體對象的三種狀態以及轉換關係。
13六、如何理解Hibernate的延遲加載機制?在實際應用中,延遲加載與Session關閉的矛盾是如何處理的?
13七、舉一個多對多關聯的例子,並說明如何實現多對多關聯映射。
13八、談一下你對繼承映射的理解。
13九、簡述Hibernate常見優化策略。
140、談一談Hibernate的一級緩存、二級緩存和查詢緩存。
14一、Hibernate中DetachedCriteria類是作什麼的?
14二、@OneToMany註解的mappedBy屬性有什麼做用?
14三、MyBatis中使用#和$書寫佔位符有什麼區別?
14四、解釋一下MyBatis中命名空間(namespace)的做用。
14五、MyBatis中的動態SQL是什麼意思?
14六、什麼是IoC和DI?DI是如何實現的?
14七、Spring中Bean的做用域有哪些?
14八、解釋一下什麼叫AOP(面向切面編程)?
14九、你是如何理解"橫切關注"這個概念的?
150、你如何理解AOP中的鏈接點(Joinpoint)、切點(Pointcut)、加強(Advice)、引介(Introduction)、織入(Weaving)、切面(Aspect)這些概念?
15一、Spring中自動裝配的方式有哪些?
15二、Spring中如何使用註解來配置Bean?有哪些相關的註解?
15三、Spring支持的事務管理類型有哪些?你在項目中使用哪一種方式?
15四、如何在Web項目中配置Spring的IoC容器?
15五、如何在Web項目中配置Spring MVC?
15六、Spring MVC的工做原理是怎樣的?
15七、如何在容器中配置數據源?
15八、如何配置配置事務加強?
15九、選擇使用Spring框架的緣由(Spring框架爲企業級開發帶來的好處有哪些)?
160、Spring IoC容器配置Bean的方式?
16一、闡述Spring框架中Bean的生命週期?
16二、依賴注入時如何注入集合屬性?
16三、Spring中的自動裝配有哪些限制?
16四、在Web項目中如何得到Spring的IoC容器?
165. 大型網站在架構上應當考慮哪些問題?
16六、你用過的網站前端優化的技術有哪些?
16七、你使用過的應用服務器優化技術有哪些?
16八、什麼是攻擊?什麼是SQL注入攻擊?什麼是CSRF攻擊?
169. 什麼是領域模型?貧血模型(anaemic domain model)和充血模型(rich domain model)有什麼區別?
170. 談一談測試驅動開發(TDD)的好處以及你的理解。
對以上問題的解答:
一、面向對象的特徵有哪些方面?
答:面向對象的特徵主要有如下幾(四)個方面:- 抽象:抽象是將一類對象的共同特徵總結出來構造類的過程,包括數據抽象和行爲抽象兩方面。抽象只關注對象有哪些屬性和行爲,並不關注這些行爲的細節是什麼。
-
繼承:繼承是從已有類獲得繼承信息建立新類的過程。提供繼承信息的類被稱爲父類(超類、基類);獲得繼承信息的類被稱爲子類(派生類)。繼承讓變化中的軟件系統有了必定的延續性,同時繼承也是封裝程序中可變因素的重要手段(若是不能理解請閱讀閻宏博士的《Java與模式》或《設計模式精解》中關於橋樑模式的部分)。is-a
-
封裝:一般認爲封裝是把數據和操做數據的方法綁定起來,對數據的訪問只能經過已定義的接口。面向對象的本質就是將現實世界描繪成一系列徹底自治、封閉的對象。咱們在類中編寫的方法就是對實現細節的一種封裝;咱們編寫一個類就是對數據和數據操做的封裝。能夠說,封裝就是隱藏一切可隱藏的東西,只向外界提供最簡單的編程接口(能夠想一想普通洗衣機和全自動洗衣機的差異,明顯全自動洗衣機封裝更好所以操做起來更簡單;咱們如今使用的智能手機也是封裝得足夠好的,由於幾個按鍵就搞定了全部的事情)。
-
多態性:多態性是指容許不一樣子類型的對象對同一消息做出不一樣的響應。簡單的說就是用一樣的對象引用調用一樣的方法可是作了不一樣的事情。多態性分爲編譯時的多態性和運行時的多態性。若是將對象的方法視爲對象向外界提供的服務,那麼運行時的多態性能夠解釋爲:當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或默認,類的成員(包括內部類)的修飾符能夠是以上四種
三、String 是最基本的數據類型嗎?
答:不是。Java中的基本數據類型只有8個:byte、short、int、long、float、double、char、boolean;除了基本類型(primitive提type),剩下的都是引用類型(reference type)(包含枚舉類型(enumerationtype))。程序員
四、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/const?
答:goto ,const是Java中的保留字,在目前版本的Java中沒有使用。(根據James Gosling(Java之父)編寫的《The JavaProgrammingLanguage》一書的附錄中給出了一個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
- 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比較
- }
- }
運行結果:
false
true
最近還遇到一個面試題,也是和自動裝箱和拆箱有點關係的,代碼以下所示:
- public class Test03 {
- public static void main(String[] args) {
- Integer f1 = 100, f2 = 100,f3 = 128, f4 = 128;
- System.out.println(f1 == f2);
- System.out.println(f3 == f4);
- }
- }
運行結果:
true
false
若是不明就裏很容易認爲兩個輸出要麼都是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的內部類,其代碼以下所示:
!存在緩存!
- /**
- * Cache to support the object identity semantics of autoboxing for values between
- * -128 and 127 (inclusive) as required by JLS.
- *
- * The cache is initialized on first usage. The size of the cache
- * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
- * During VM initialization, java.lang.Integer.IntegerCache.high property
- * may be set and saved in the private system properties in the
- * sun.misc.VM class.
- */
- 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之間(閉區間),cache有緩存機制,不會new新的Integer對象,而是直接引用常量池中的Integer對象,因此上面的面試題中f1==f2的結果是true,而f3==f4的結果是false。
java中除了對Integer有緩存機制外,其中還有ByteCache,ShortCache,LongCache,CharacterCache分別對其對應的類型進行緩存,其中Byte,Short,Long的緩存範圍都爲-128——127,Character爲0——127。特別要注意的是這幾個緩存中,只有Integer的緩存上限(high)能夠設置,其餘的都不能進行設置,爲固定範圍。
- public static void main(String[] args) {
- Integer f1 = 100, f2 = 100, f3 = 127, f4 = 127;
- System.out.println(f1 == f2);
- System.out.println(f3 == f4);
- }
運行結果:
true
true
提醒:越是貌似簡單的面試題其中的玄機就越多,須要面試者有至關深厚的功力。
八、&和&&的區別?
答:&運算符有兩種用法:(1)按位與;(2)邏輯與。&&運算符是短路與運算。邏輯與跟短路與的差異是很是巨大的,雖然兩者都要求運算符左右兩端的布爾值都是true整個表達式的值纔是true。&&之因此稱爲短路運算是由於,若是&&左邊的表達式的值是false,右邊的表達式會被直接短路掉,不會進行運算。不少時候咱們可能都須要用&&而不是&,例如在驗證用戶登陸時斷定用戶名不是null並且不是空字符串,應當寫爲:usernamex!=null&&!username.equals(""),兩者的順序不能交換,更不能用&運算符,由於第一個條件若是不成立,根本不能進行字符串的equals比較,不然會產生NullPointerException異常。注意:邏輯或運算符(|)和短路或運算符(||)的差異也是如此。補充:若是你熟悉JavaScript,那你可能更能感覺到短路運算的強大,想成爲JavaScript的高手就先從玩轉短路運算開始吧。
九、解釋內存中的棧(stack)、堆(heap)和靜態區(static area)的用法。
答:一般咱們定義一個基本數據類型的變量,一個對象的引用,還有就是函數調用的現場保存都使用內存中的棧空間;而經過new關鍵字和構造器建立的對象放在堆空間;程序中的字面量(literal)如直接書寫的100、"hello"和常量都是放在靜態區中。棧空間操做起來最快可是棧很小,一般大量的對象都是放在堆空間,理論上整個內存沒有被其餘進程使用的空間甚至硬盤上的虛擬內存均可以被當成堆空間來使用。String str = new String("hello");
上面的語句中變量str放在棧上,用new建立出來的字符串對象放在堆上,而"hello"這個字面量放在靜態區。
--!補充
- class abc{
- private String a="a";
- public void f1(){
- String b="b";
- final String c="c";
- }
請問a,b,c存放在內存哪塊?
堆棧棧
!補充:較新版本的Java(從Java 6的某個更新開始)中使用了一項叫"逃逸分析"的技術,能夠將一些不會逃逸到方法外的變量(原本應該放堆),放在棧上以提高對象的操做性能。
十、Math.round(11.5) 等於多少?Math.round(-11.5)等於多少?
答:Math.round(11.5)的返回值是12,Math.round(-11.5)的返回值是-11。四捨五入的原理是在參數上加0.5而後進行下取整。--補充!github
ceil>=rount>=floor
十一、switch 是否能做用在byte 上,是否能做用在long 上,是否能做用在String上?
答:在Java 5之前,switch(expr)中,expr只能是byte、short、char、int。從Java5開始,Java中引入了枚舉類型,expr也能夠是enum類型,從Java
7開始,expr還能夠是字符串(String),可是長整型(long)和float在目前全部的版本中都是不能夠的。
十二、用最有效率的方法計算2乘以16?
答: 2 << 4(左移4位至關於乘以2的4次方,右移4位至關於除以2的4次方)。--!補充 如何最有效率計算2乘以3/2?
補充:咱們爲編寫的類重寫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,而後用breakA;能夠跳出多重循環。(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,它們的哈希碼(hashcode)應當相同。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;
- amespace 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方法鏈接字符串性能更好?(String沒有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());
- }
FLASE
TRUE
TRUE
補充: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)有什麼異同?
答:抽象類和接口都不可以實例化,但能夠定義抽象類和接口類型的引用。1)一個類若是繼承了某個抽象類或者實現了某個接口都須要對其中的抽象方法所有進行實現,不然該類仍然須要被聲明爲抽象類。
2)接口比抽象類更加抽象,由於抽象類中能夠定義構造器,能夠有抽象方法和具體方法,而接口中不能定義構造器並且其中的方法所有都是抽象方法(只能定義)。
抽象類中的成員能夠是private、默認、protected、public的,而接口中的成員全都是public的。
3)抽象類中能夠定義成員變量,而接口中定義的成員變量實際上都是常量。
4)有抽象方法的類必須被聲明爲抽象類,而抽象類未必要有抽象方法。
5)實現、繼承多個接口,繼承一個類;
6)繼承Is-A,實現Like-A關係;
2四、靜態嵌套類(Static Nested Class)和內部類(Inner Class)的不一樣?
答:Static NestedClass是被聲明爲靜態(static)的內部類,它能夠不依賴於外部類實例被實例化。而一般的內部類須要在外部類實例化後才能實例化,其語法看起來挺詭異的,以下所示。
- /**
- * 撲克類(一副撲克)
- * @author Yien
- *
- */
- 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 Yien
- *
- */
- 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<T> {
- 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)。在支持垃圾回收的語言中,內存泄露是很隱蔽的,這種內存泄露其實就是無心識的對象保持。若是一個對象引用被無心識的保留起來了,那麼垃圾回收器不會處理這個對象,也不會處理該對象引用的其餘對象,即便這樣的對象只有少數幾個,也可能會致使不少的對象被排除在垃圾回收以外,從而對性能形成重大影響,極端狀況下會引起Diska
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 Yien
- *
- */
- 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 Yien
- *
- */
- 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()8或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"時是一個,若無兩個對象,一個是靜態區的"xyz",一個是用new建立在堆上的對象。
3二、接口是否可繼承(extends)接口?抽象類是否可實現(implements)接口?抽象類是否可繼承具體類(concrete class)?
答:接口能夠繼承接口,並且支持多重繼承(繼承多接口)。抽象類能夠實現(implements)接口,抽象類可繼承具體類也能夠繼承抽象類。!--- 詳細補充
一道java 常見面試題,網上找到的幾乎每一個 java 面試筆試題大全或集錦裏都能找到這道題。
題目以下:
問: 抽象類是否可繼承實體類 (concrete class)
答: 抽象類是能夠繼承實體類,但前提是實體類必須有明確的構造函數
答案很明確,能夠繼承。其實從Object就是個實體類,java的API文檔裏,每一個抽象類的條目裏都明確寫着直接或間接繼承自Object,因此這點是沒有疑問的。
關鍵在於這答案裏所說的「前提是實體類必須有明確的構造函數」一句,是什麼意思。
通常學習者會寫的簡單試驗代碼:
class A{}
abstract class B extends A{}
結果徹底正常,編譯經過。彷佛和「實體類必須有明確的構造函數」徹底沒有關係。
這個問題涉及到兩個個基礎知識:
1.
全部的class都必須有一個構造方法,若是你沒有在代碼裏聲明構造方法,系統會自動給你生成一個公有無參的構造方法。而只要你本身聲明瞭一個構造方法,不管有參無參,私有公有,系統就再也不幫你生成默認無參構造器了。
2.
全部的子類構造器都要求在第一行代碼中調用父類構造器,若是不寫,系統默認去調用父類的無參構造器。
因此,若是把系統默認配給的方法也算進去,class A{}的代碼其實是
class A{
public A(){}
}
B繼承 A 的時候,則是
abstract class B extends A{
public B(){
super();
}
}
要試驗出這繼承規則的內部狀況,也很簡單,在最上面那個簡單試驗代碼裏,加上個私有構造器,有參無參都行。
class A{
private A(){}
}
這個時候,如基礎知識(1) 中所說,系統再也不給你默認無參構造器, B的構造器根據(2)中的規則去調用super(),卻找不到A的無參構造器,因此致使abstract class B extends A{} 編譯不能經過。(由於A中沒有任何構造器可供子類調用,其實這個時候A只可以供內部類繼承,我用的Eclipse的3.4版本會建議給B更名,可是這解決不了這個問題。)
如今,你應該瞭解了資料給的那句語焉不詳的「實體類必須有明確的構造函數」的含義:
1.沒寫構造器的,那是擁有默認無參公有構造函數的,子類能夠什麼都不寫,讓默認構造器去調用它。這是最初那兩行代碼的狀況。
2.寫了子類可訪問的無參構造器的,也是同樣,子類裏能夠什麼都不寫,用默認機制調用。
3.寫了 有參構造器卻沒寫無參構造器的,父類裏沒有子類可訪問的無參構造器,子類必須在子類構造器裏的第一句寫明,調用父類有參構造器,並把參數傳進去。
4.聲明爲final的以及全部構造器都不在子類訪問權限以內的類沒法繼承
其實只要是在類的繼承中,不管抽象仍是實體,都須要符合這個規則的。在這個繼承試驗中隨時刪掉或是加上abstract的前綴,結果都沒有變化。我的以爲「實體類必須有明確的構造函數」一句實在是沒法把這個狀況表達清楚,因此廣大求職者仍是寫得清楚些好。
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 8123123
問題3:代碼以下所示。
Calendar time = Calendar.getInstance();
time.getActualMaximum(Calendar.DAY_OF_MONTH);1212
問題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等類,這些的類的設計都使用了不變模式,所以是線程安全的設計。若是不理解這些內容,能夠參考個人另外一篇文章《關於Java併發編程的總結和思考》。
4二、打印昨天的當前時刻。
答:- import java.util.Calendar;
- lass 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;
- lass 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 是原SunMicrosystems公司推出的面向對象的程序設計語言,特別適合於互聯網應用程序開發;而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 <= 011
斷言能夠有兩種形式:
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類型的異常)
- 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");
- System.out.println(a instanceof Sneeze);
- throw a;
- }
- }
- catch ( Sneeze s ) {
- System.out.println("Caught Sneeze");
- return ;
- }
- finally {
- System.out.println("Hello World!");
- }
- }
- }
Caught Annoyance
true
Caught Sneeze
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類型,可是Java3
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<Student> {
- 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 + "]";
- }
- }
- 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()方法)時才能喚醒等待池中的線程進入等鎖池(lockpool),若是線程從新得到對象的鎖就能夠進入就緒狀態。
補充:可能很多人對什麼是進程,什麼是線程還比較模糊,對於爲何須要多線程編程也不是特別理解。簡單的說:進程是具備必定獨立功能的程序關於某個數據集合上的一次運行活動,是操做系統進行資源分配和調度的一個獨立單位;線程是進程的一個實體,是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多線程和併發編程的問題,建議你們看個人另外一篇文章《關於Java併發編程的總結和思考》。
補充:Java 5經過Lock接口提供了顯式的鎖機制(explicit
lock),加強了靈活性以及對線程的協調。Lock接口中定義了加鎖(lock())和解鎖(unlock())的方法,同時還提供了newCondition()方法來產生用於線程之間通訊的Condition對象;此外,Java的
5還提供了信號量機制(semaphore),信號量能夠用來限制對某個共享資源進行訪問的線程的數量。在對資源進行訪問以前,線程必須獲得信號量的許可(調用Semaphore對象的acquire()方法);在完成對資源的訪問後,線程必須向信號量歸還許可(調用Semaphore對象的release()方法)。
下面的例子演示了100個線程同時向一個銀行帳戶中存入1元錢,在沒有使用同步機制和使用同步機制狀況下的執行狀況。
銀行帳戶類:
- /**
- * 銀行帳戶
- * @author Yien
- *
- */
- 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 Yien
- *
- */
- 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 Yien
- *
- */
- 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 Yien
- *
- */
- 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 Yien
- *
- */
- 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;
- }
- }
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<Integer> {
- 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六、線程的基本狀態以及狀態之間的關係?
答:說明:
1. 新建(new):新建立了一個線程對象。
2. 可運行/就緒(runnable):線程對象建立後,其餘線程(好比main線程)調用了該對象的start()方法。該狀態的線程位於可運行線程池中,等待被線程調度選中,獲取cpu 的使用權 。
3. 運行(running):可運行狀態(runnable)的線程得到了cpu 時間片(timeslice) ,執行程序代碼。
4. 阻塞(block):阻塞狀態是指線程由於某種緣由放棄了cpu 使用權,也即讓出了cpu timeslice,暫時中止運行。直到線程進入可運行(runnable)狀態,纔有機會再次得到cpu timeslice 轉到運行(running)狀態。阻塞的狀況分三種:
(一). 等待阻塞:運行(running)的線程執行o.wait()方法,JVM會把該線程放入等待隊列(waitting queue)中。
(二). 同步阻塞:運行(running)的線程在獲取對象的同步鎖時,若該同步鎖被別的線程佔用,則JVM會把該線程放入鎖池(lock pool)中。
(三). 其餘阻塞:運行(running)的線程執行Thread.sleep(long ms)或t.join()方法,或者發出了I/O請求時,JVM會把該線程置爲阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程從新轉入可運行(runnable)狀態。
5. 死亡(dead):線程run()、main() 方法執行結束,或者因異常退出了run()方法,則該線程結束生命週期。死亡的線程不可再次復
3. 運行(running):可運行狀態(runnable)的線程得到了cpu 時間片(timeslice) ,執行程序代碼。
4. 阻塞(block):阻塞狀態是指線程由於某種緣由放棄了cpu 使用權,也即讓出了cpu timeslice,暫時中止運行。直到線程進入可運行(runnable)狀態,纔有機會再次得到cpu timeslice 轉到運行(running)狀態。阻塞的狀況分三種:
(一). 等待阻塞:運行(running)的線程執行o.wait()方法,JVM會把該線程放入等待隊列(waitting queue)中。
(二). 同步阻塞:運行(running)的線程在獲取對象的同步鎖時,若該同步鎖被別的線程佔用,則JVM會把該線程放入鎖池(lock pool)中。
(三). 其餘阻塞:運行(running)的線程執行Thread.sleep(long ms)或t.join()方法,或者發出了I/O請求時,JVM會把該線程置爲阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程從新轉入可運行(runnable)狀態。
5. 死亡(dead):線程run()、main() 方法執行結束,或者因異常退出了run()方法,則該線程結束生命週期。死亡的線程不可再次復
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(文檔對象模型,DocumentObject Model)、SAX(Simple API for XML)和StAX(Java 6中引入的新的解析XML的方式,Streaming API
for
XML),其中DOM處理大型文件時其性能降低的很是厲害,這個問題是由DOM樹結構佔用的內存較多形成的,並且DOM解析方式必須在解析文件以前把整個文檔裝入內存,適合對XML的隨機訪問(典型的用空間換取時間的策略);SAX是事件驅動型的XML解析方式,它順序讀取XML文件,不須要一次所有裝載整個文件。當遇到像文件開頭,文檔結束,或者標籤開頭與標籤結束時,它會觸發一個事件,用戶經過事件回調代碼來處理XML文件,適合對XML的順序訪問;顧名思義,StAX把重點放在流上,實際上StAX與其餘解析方式的本質區別就在於應用程序可以把XML做爲一個事件流來處理。將XML做爲一組事件來處理的想法並不新穎(SAX就是這樣作的),但不一樣之處在於StAX容許應用程序代碼把這些事件逐個拉出來,而不用提供在解析器方便時從解析器中接收事件的處理程序。
7四、你在項目中哪些地方用到了XML?
答:XML的主要做用有兩個方面:數據交換和信息配置。在作數據交換時,XML將數據用標籤組裝成起來,而後壓縮打包加密後經過網絡傳送給接收者,接收解密與解壓縮後再從XML文件中還原相關信息進行處理,XML曾經是異構系統間交換數據的事實標準,但此項功能幾乎已經被JSON(JavaScriptObject
Notation)取而代之。固然,目前不少軟件仍然使用XML來存儲配置信息,咱們在不少項目中一般也會將做爲配置信息的硬代碼寫在XML文件中,Java的不少框架也是這麼作的,並且這些框架都選擇了dom4j做爲處理XML的工具,由於Sun公司的官方API實在不怎麼好用。
補充:如今有不少時髦的軟件(如Sublime)已經開始將配置文件書寫成JSON格式,咱們已經強烈的感覺到XML的另外一項功能也將逐漸被業界拋棄。
7五、闡述JDBC操做數據庫的步驟。
答:下面的代碼以鏈接本機的Oracle數據庫爲例,演示JDBC操做數據庫的步驟。加載驅動。
- Class.forName("oracle.jdbc.driver.OracleDriver");11 建立鏈接。
- Connection con = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl", "scott", "tiger");11 建立語句。
- PreparedStatement ps = con.prepareStatement("select * from emp where sal between ? and ?");
- ps.setInt(1, 1000);
- ps.setInt(2, 3000); 執行語句。
- ResultSet rs = ps.executeQuery();11 處理結果。
- 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類丟失更新)。
數據併發訪問所產生的問題,在有些場景下多是容許的,可是有些場景下可能就是致命的,數據庫一般會經過鎖機制來解決數據併發訪問問題,按鎖定對象不一樣能夠分爲表級鎖和行級鎖;按併發事務鎖定關係能夠分爲共享鎖和獨佔鎖,具體的內容你們能夠自行查閱資料進行了解。
直接使用鎖是很是麻煩的,爲此數據庫爲用戶提供了自動鎖機制,只要用戶指定會話的事務隔離級別,數據庫就會經過分析SQL語句而後爲事務訪問的資源加上合適的鎖,此外,數據庫還會維護這些鎖經過各類手段提升系統的性能,這些對用戶來講都是透明的(就是說你不用理解,事實上我確實也不知道)。ANSI/ISO
SQL 92標準定義了4個等級的事務隔離級別,
隔離等級 | 髒讀 | 不可重複讀 | 幻讀 |
---|---|---|---|
讀未提交 | YES | YES | YES |
讀已提交 | NO | YES | YES |
可重複讀 | NO | NO | YES |
串行化 | NO | NO | NO |
須要說明的是,事務隔離級別和數據訪問的併發性是對立的,事務隔離級別越高併發性就越差。因此要根據具體的應用來肯定合適的事務隔離級別,這個地方沒有萬能的原則。
8一、JDBC中如何進行事務處理?
答:Connection提供了事務處理的方法,經過調用setAutoCommit(false)能夠設置手動提交事務;當事務完成後用commit()顯式提交事務;若是在事務處理過程當中發生異常則經過rollback()進行事務回滾。除此以外,從JDBC3.0中還引入了Savepoint(保存點)的概念,容許經過代碼設置保存點並讓事務回滾到指定的保存點。
8二、JDBC可否處理Blob和Clob?
答: Blob是指二進制大對象(Binary Large Object),而Clob是指大字符對象(Character LargeObjec),所以其中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, "Yien"); // 將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());
- }
- }
- }
8五、得到一個類的類對象有哪些方式?
答:- 方法1:類型.class,例如:String.class
- 方法2:對象.getClass(),例如:"hello".getClass()
- 方法3:Class.forName(),例如:Class.forName("java.lang.String")
8六、如何經過反射建立對象?
答:- 方法1:經過類對象調用newInstance()方法,例如:String.class.newInstance()
!--補充:Object cls = (Object)Class.forName("輸入類").newInstance();
System.out.println(cls.getClass().getName());
(輸入該類並獲取實例顯示類名)
-
方法2:經過類對象的getConstructor()或getDeclaredConstructor()方法得到構造器(Constructor)對象並調用其newInstance()方法建立對象,例如:String.class.getConstructor(String.class).newInstance("Hello");
!--補充- Object clsa = (Object)Class.forName("輸入類").getConstructor().newInstance("");
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 Yien
- *
- */
- 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-OrientedSoftware》中給出了三類(建立型[對類的實例化過程的抽象化]、結構型[描述如何將類或對象結合在一塊兒造成更大的結構]、行爲型[對在不一樣的對象之間劃分責任和算法的抽象化])共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)、狀態圖(statechartdiagram)、活動圖(activity diagram)、構件圖(component diagram)、部署圖(deployment
diagram)等。在這些圖形化符號中,有三種圖最爲重要,分別是:用例圖(用來捕獲需求,描述系統的功能,經過該圖能夠迅速的瞭解系統的功能模塊及其關係)、類圖(描述類以及類與類之間的關係,經過該圖能夠快速瞭解系統)、時序圖(描述執行特定任務時對象之間的交互關係以及執行順序,經過該圖能夠了解對象能接收的消息也就是說對象可以向外界提供的服務)。
9四、用Java寫一個冒泡排序。
答:冒泡排序幾乎是個程序員都寫得出來,可是面試的時候如何寫一個逼格高的冒泡排序卻不是每一個人都能作到,下面提供一個參考代碼:- import java.util.Comparator;
- /**
- * 排序器接口(策略模式: 將算法封裝到具備共同接口的獨立的類中使得它們能夠相互替換)
- * @authorYien
- *
- */
- public interface Sorter {
- /**
- * 排序
- * @param list 待排序的數組
- */
- public <T extends Comparable<T>> void sort(T[] list);
- /**
- * 排序
- * @param list 待排序的數組