答:封裝、繼承和多態_(應要多算一個那就是抽象)_java
答:面向過程是一種站在過程的角度思考問題的思想,強調的是功能行爲,功能的執行過程,即先幹啥,後幹啥。程序員
面向過程的設計:最小的程序單元是函數,每一個函數負責完成某一個功能,用以接受輸入數據,函數對輸入數據進行處理,而後輸出結果數據。整個軟件系統由一個個的函數組成,其中做爲程序入口的函數稱之爲主函數,主函數依次調用其餘函數,普通函數之間能夠相互調用,從而實現整個系統功能。web
面向過程最大的問題在於隨着系統的膨脹,面向過程將沒法應付,最終致使系統的崩潰。爲了解決這一種軟件危機,咱們提出面向對象思想。面試
面向對象是一種基於面向過程的新的編程思想,是一種站在對象的角度思考問題的思想,咱們把多個功能合理的放到不一樣對象裏,強調的是具有某些功能的對象。算法
看到知乎上有一句有意思的話:你的程序要完成一個任務,至關於講一個故事。sql
面向過程:編年體;
面向對象:紀傳體。編程而對於複雜的程序/宏大的故事,事實都證實了,面向對象/紀傳是更合理的表述方法。數組
擴展閱讀:面向過程 VS 面向對象緩存
解析:這是考察一些基本的概念安全
答:Java 運行時環境(JRE-Java Runtime Environment),它包括 Java 虛擬機、Java 核心類庫和支持文件,但並不包含開發工具(JDK-Java Development Kit)——編譯器、調試器和其餘工具。
Java 開發工具包(JDK)是完整的 Java 軟件開發包,包含了 JRE,編譯器和其餘的工具(好比 JavaDoc, Java 調試器),可讓開發者開發、編譯、執行 Java 應用程序。
- 還有其餘的一些名詞也能夠再看一下:
解析:覆蓋和重載是比較重要的基礎知識點,而且容易混淆,因此面試中常見。
答:覆蓋(Override)是指子類對父類方法的一種重寫,只能比父類拋出更少的異常,訪問權限不能比父類的小,被覆蓋的方法不能是 private 的,不然只是在子類中從新定義了一個新方法。
重載(Overload)表示同一個類中能夠有多個名稱相同的方法,但這些方法的參數列表各不相同。
面試官: 那麼構成重載的條件有哪些?
答:參數類型不一樣、參數個數不一樣、參數順序不一樣。
面試官: 函數的返回值不一樣能夠構成重載嗎?爲何?
答:不能夠,由於 Java 中調用函數並不須要強制賦值。舉例以下:
以下兩個方法:
void f(){} int f(){ return 1; }
只要編譯器能夠根據語境明確判斷出語義,好比在 int x = f();
中,那麼的確能夠據此區分重載方法。不過, 有時你並不關心方法的返回值,你想要的是方法調用的其餘效果 (這常被稱爲 「爲了反作用而調用」 ),這時你可能會調用方法而忽略其返回值,因此若是像下面的調用:
f();
此時 Java 如何才能判斷調用的是哪個 f()
呢?別人如何理解這種代碼呢?因此,根據方法返回值來區分重載方法是行不通的。
答:
面試官:抽象類和接口如何選擇?
答:
解析:雖然咱們不太懂C++,可是就是會這麼問,尤爲是三面(總監級別)面試中。
答:
答:「static」 關鍵字代表一個成員變量或者是成員方法能夠在沒有所屬的類的實例變量的狀況下被訪問。
面試官:Java中是否能夠覆蓋(override)一個 private 或者是 static 的方法?
答:Java 中 static 方法不能被覆蓋,由於方法覆蓋是基於運行時動態綁定的,而 static 方法是編譯時靜態綁定的。static 方法跟類的任何實例都不相關,因此概念上不適用。
Java 中也不能夠覆蓋 private 的方法,由於 private 修飾的變量和方法只能在當前類中使用,若是是其餘的類繼承當前類是不能訪問到 private 變量或方法的,固然也不能覆蓋。
擴展閱讀:從新認識java(六) ---- java中的另類:static關鍵字(附代碼塊知識)
解析:這類題目,面試官會手寫一個例子,讓你說出函數執行結果。
答:值傳遞是對基本型變量而言的,傳遞的是該變量的一個副本,改變副本不影響原變量。引用傳遞通常是對於對象型變量而言的,傳遞的是該對象地址的一個副本, 並非原對象自己 。
通常認爲,Java 內的傳遞都是值傳遞.,Java 中實例對象的傳遞是引用傳遞,Java 是值傳遞的!
- 咱們先來看一個例子:
這是一個很經典的例子,咱們但願在調用了 swap() 方法以後交換 arg1 和 arg2 的值,但事實上並無,爲何會這樣?
這就是由於 Java 是值傳遞的,也就是說,咱們在調用一個須要傳遞參數的函數時,傳遞給函數的參數並非咱們傳遞進去的參數自己,而是它的一個副本,咱們改變了數據其實只是改變了副本的數據而已,並不會對原來的參數有任何的改變。
- 再來看一個例子:
咱們本身定義了一個內部類 Person ,該類只有一個 int 類型的 age 屬性,而後有 getter/setter ,咱們但願經過 changeAge() 函數來改變 Person 對象的 age 屬性,爲何此次成功了呢?
你依然能夠理解爲,主函數將 person 複製了一份到 changeAge 函數中去,最終仍是隻改變了 changeAge 中複製的那一份參數的值,而本來的參數並無改變,但 changeAge 中的那一份和本來的參數指向了同一個內存區域!
答:java.lang、java.util、java.io、java.net、java.sql。
答:JDK 是 Java 開發工具包,是 Java 開發環境的核心組件,並提供編譯、調試和運行一個 Java 程序所須要的全部工具,可執行文件和二進制文件,是一個平臺特定的軟件。
JRE 是 Java 運行時環境,是 JVM 的實施實現,提供了運行 Java 程序的平臺。JRE 包含了 JVM,可是不包含 Java 編譯器 / 調試器之類的開發工具。
JVM 是 Java 虛擬機,當咱們運行一個程序時,JVM 負責將字節碼轉換爲特定機器代碼,JVM 提供了內存管理 / 垃圾回收和安全機制等。
這種獨立於硬件和操做系統,正是 Java 程序能夠一次編寫多處執行的緣由。
區別:
1. JDK 用於開發,JRE 用於運行 Java 程序;
2. JDK 和 JRE 中都包含 JVM;
3. JVM 是 Java 編程語言的核心而且具備平臺獨立性。
解析:考察的是對源碼的熟悉程度
- 看一個例子:
第一個返回true很好理解,就像上面講的,a和b指向相同的地址。第二個返回false是爲何呢?這是由於 Integer 有緩存機制,在 JVM 啓動初期就緩存了 -128 到 127 這個區間內的全部數字。
第三個返回false是由於用了new關鍵字來開闢了新的空間,i和j兩個對象分別指向堆區中的兩塊內存空間。
咱們能夠跟蹤一下Integer的源碼,看看到底怎麼回事。在IDEA中,你只須要按住Ctrl而後點擊Integer,就會自動進入jar包中對應的類文件。
跟蹤到文件的700多行,你會看到這麼一段,感興趣能夠仔細讀一下,不用去讀也沒有關係,由於你只須要知道這是 Java 的一個緩存機制。Integer 類的內部類緩存了 -128 到 127 的全部數字。(事實上,Integer類的緩存上限是能夠經過修改系統來更改的,瞭解就好了,沒必要去深究。)
// 第一種:直接賦一個字面量 String str1 = "ABCD"; // 第二種:經過構造器建立 String str2 = new String("ABCD");
解析:考察的是對 String 對象和 JVM 內存劃分的知識。
答:String str1 = "ABCD";
最多建立一個String對象,最少不建立String對象.若是常量池中,存在」ABCD」,那麼str1直接引用,此時不建立String對象.不然,先在常量池先建立」ABCD」內存空間,再引用.
String str2 = new String("ABCD");
最多建立兩個String對象,至少建立一個String對象。new關鍵字絕對會在堆空間建立一塊新的內存區域,因此至少建立一個String對象。
咱們來看圖理解一下:
String 對象是一個特殊的存在,須要注意的知識點也比較多,這裏給一個以前寫的 String 詳解的文章連接:傳送門 其中包含的問題大概有:1)「+」 怎麼鏈接字符串;2)字符串的比較;3)StringBuilder/StringBuffer/String 的區別;
解析:對於這兩個的區別,熟悉的表述是:前置++是先將變量的值加 1,而後使用加 1 後的值參與運算,然後置++則是先使用該值參與運算,而後再將該值加 1 .但事實上,前置++和後置++同樣,在參與運算以前都會將變量的值加 1
答:實際上,無論是前置 ++,仍是後置 ++,都是先將變量的值加 1,而後才繼續計算的。兩者之間真正的區別是:前置 ++ 是將變量的值加 1 後,使用增值後的變量進行運算的,然後置 ++ 是首先將變量賦值給一個臨時變量,接下來對變量的值加 1,而後使用那個臨時變量進行運算。
答:
- 第一種:經過第三個變量
public class Test{ public static void main(String[] args) { int x = 5; int y = 10; swap(x,y); System.out.println(x); System.out.println(y); Value v = new Value(5,10); swap(v); System.out.println(v.x); System.out.println(v.y); } // 無效的交換:形參的改變沒法副作用於實參 public static void swap(int x,int y) { int temp = x; x = y; y = temp; } // 有效的交換:經過引用(變量指向一個對象)來修改爲員變量 public static void swap(Value value) { int temp = value.x; value.x = value.y; value.y = temp; } } class Value{ int x; int y; public Value(int x,int y) { this.x = x; this.y = y; } }
輸出的結果:
5
10
10
5
這有點相似於C/C++語言中的指針,不過相對來講更加安全。
事實上,其實若是把基礎類型int改爲對應的包裝類的話其實能夠更加簡單的完成這個操做,不過須要付出更多的內存代價。
第二種:經過經過相加的方式(相同的 Value 類再也不重複展現)
public class Test{ public static void main(String[] args) { Value v1 = new Value(5,10); swap(v1); System.out.println("v1交換以後的結果爲:"); System.out.println(v1.x); System.out.println(v1.y); } public static void swap(Value v) { v.x = v.x + v.y; v.y = v.x - v.y; v.x = v.x - v.y; } }
輸出的結果:
v1的交換結果:
10
5
核心的算法就是swap方法:
v.x = v.x + v.y; // 把v.x與v.y的和存儲在v.x中 v.y = v.x - v.y; // v.x減掉v.y原本的值即爲v.x v.x = v.x - v.y; // v.x減掉v.y的值也就是之前x.y的值
這樣就能夠不經過臨時變量,來達到交換兩個變量的目的,若是以爲上面的方法不太容易理解,咱們也能夠用另外一個參數z來表示上述過程:
int z = v.x + v.y; // 把v.x與v.y的和存儲在z中 v.y = z - v.y; // z減掉之前的v.y就等於v.x v.x = z - v.y; // z減掉如今的v.y即之前的v.x,即爲v.y
但並不推薦這種作法,緣由在於當數值很大的時候,16進制的求和運算可能形成數據的溢出,雖然最後的結果依然會是咱們所指望的那樣,但仍然不是十分可取。
- 第三種:經過異或的方式:
位異或運算符(^)有這樣的一個性質,就是兩個整型的數據x與y,有: (x ^ y ^ y) == x
這說明,若是一個變量x異或另一個變量y兩次,結果爲x。經過這一點,能夠實現交換兩個變量的值:
public class Test{ public static void main(String[] args) { Value v1 = new Value(5,10); swap(v1); System.out.println("v1交換以後的結果爲:"); System.out.println(v1.x); System.out.println(v1.y); } public static void swap(Value v) { v.x = v.x ^ v.y; v.y = v.x ^ v.y; v.x = v.x ^ v.y; } }
輸出的結果:
v1交換以後的結果爲:
10
5
跟上面相加的方式過程幾乎相似,只不過運算的方式不一樣而已。異或的方法比相加更加可取的地方在於,異或不存在數據溢出。
答:不考慮靜態成員的初始化,調用一個對象的構造函數時,程序先調用父類的構造函數(能夠經過super關鍵字指定父類的構造函數,不然默認調用無參的構造函數,而且須要在子類的構造函數的第一行調用),以後靜態成員變量的初始化函數和靜態初始化塊則按照在代碼當中的順序執行,成員變量若是沒有指定值的話則賦予默認值,即基本數據類型爲0或false等,對象則爲null;最後調用自身構造函數。
- 咱們能夠寫一段程序來對初始化順序進行一個簡單的驗證:
public class Derive extends Base { private Member m1 = new Member("Member 1"); { System.out.println("Initial Block()"); } public Derive() { System.out.println("Derive()"); } private Member m2 = new Member("Member 2"); private int i = getInt(); private int getInt() { System.out.println("getInt()"); return 2; } public static void main(String[] args) { new Derive(); } } class Base { public Base() { System.out.println("Base()"); } } class Member { public Member(String m) { System.out.println("Member() "+m); } }
程序的輸出結果是:
Base()
Member() Member 1
Initial Block()
Member() Member 2
getInt()
Derive()
答:不是。true、false 是布爾類型的字面常量,null 是引用類型的字面常量。
面試官:那 goto 與 const 呢?
答:是。goto 與 const 均是 Java 語言保留的關鍵字,即沒有任何語法應用。
答:exception 和 error都是 Throwable 的子類。exception 用於用戶程序能夠捕獲的異常狀況;error 定義了不指望被用戶程序捕獲的異常。
exception 表示一種設計或設計的問題,也就是說只要程序正常運行,從不會發生的狀況;而 error 表示回覆不是不可能可是很困難的狀況下的一種嚴重問題,好比內存溢出,不可能期望程序處理這樣的狀況。
答:throw 關鍵字用來在程序中明確的拋出異常,相反,throws 語句用來代表方法不能處理的異常。每個方法都必需要指定哪些異常不能處理,因此方法的調用者纔可以確保處理可能發生的異常,多個異常是用逗號分隔的。
小結:本節主要闡述了 Java 基礎知識,並無涉及到一些高級的特性,這些問題通常難度不大,適當複習下,應該沒問題。
集合這方面的考察至關多,這部分是面試中必考的知識點。
答:Map接口和Collection接口是全部集合框架的父接口:
答:
答:在Java8以前,其底層實現是數組+鏈表實現,Java8使用了數組+鏈表+紅黑樹實現。此時你能夠簡單的在紙上畫圖分析:
答:ConcurrentHashMap 結合了 HashMap 和 HashTable 兩者的優點。HashMap 沒有考慮同步,HashTable 考慮了同步的問題。可是 HashTable 在每次同步執行時都要鎖住整個結構。 ConcurrentHashMap 鎖的方式是稍微細粒度的。 ConcurrentHashMap 將 hash 表分爲 16 個桶(默認值),諸如get,put,remove 等經常使用操做只鎖當前須要用到的桶。
面試官:ConcurrentHashMap的具體實現知道嗎?
答:
1. 該類包含兩個靜態內部類 HashEntry 和 Segment ;前者用來封裝映射表的鍵值對,後者用來充當鎖的角色;
2. Segment 是一種可重入的鎖 ReentrantLock,每一個 Segment 守護一個HashEntry 數組裏得元素,當對 HashEntry 數組的數據進行修改時,必須首先得到對應的 Segment 鎖。
答:
1. 經過將 Key 的 hash 值與 length - 1 進行 & 運算,實現了當前 Key 的定位,2 的冪次方能夠減小衝突(碰撞)的次數,提升 HashMap 查詢效率
2. 若是 length 爲 2 的次冪 則 length-1 轉化爲二進制一定是 11111……的形式,在於 h 的二進制與操做效率會很是的快,並且空間不浪費;若是 length 不是 2 的次冪,好比 length 爲 15,則 length - 1 爲 14,對應的二進制爲 1110,在於 h 與操做,最後一位都爲 0 ,而 0001,0011,0101,1001,1011,0111,1101 這幾個位置永遠都不能存放元素了,空間浪費至關大,更糟的是這種狀況中,數組可使用的位置比數組長度小了不少,這意味着進一步增長了碰撞的概率,減慢了查詢的效率!這樣就會形成空間的浪費。
答:List元素是有序的,能夠重複;Set元素是無序的,不能夠重複。
答:
1. List
2. Set
HashSet,初始容量爲16,加載因子爲0.75; 擴容增量:原容量的 1 倍; 如 HashSet的容量爲16,一次擴容後容量爲32
3. Map
HashMap,初始容量16,加載因子爲0.75; 擴容增量:原容量的 1 倍; 如 HashMap的容量爲16,一次擴容後容量爲32
答:
1. 前者簡單,可是若是須要從新定義比較類型時,須要修改源代碼。
2. 後者不須要修改源代碼,自定義一個比較器,實現自定義的比較方法。 具體解析參考博客:Java集合框架—Set
答:
是java集合的一種錯誤檢測機制,當多個線程對集合進行結構上的改變的操做時,有可能會產生 fail-fast 機制。
例如:假設存在兩個線程(線程一、線程2),線程1經過Iterator在遍歷集合A中的元素,在某個時候線程2修改了集合A的結構(是結構上面的修改,而不是簡單的修改集合元素的內容),那麼這個時候程序就會拋出 ConcurrentModificationException 異常,從而產生fail-fast機制。
緣由:迭代器在遍歷時直接訪問集合中的內容,而且在遍歷過程當中使用一個 modCount 變量。集合在被遍歷期間若是內容發生變化,就會改變modCount的值。每當迭代器使用hashNext()/next()遍歷下一個元素以前,都會檢測modCount變量是否爲expectedmodCount值,是的話就返回遍歷;不然拋出異常,終止遍歷。
解決辦法:
1. 在遍歷過程當中,全部涉及到改變modCount值得地方所有加上synchronized。
2. 使用CopyOnWriteArrayList來替換ArrayList
答:
這兩個類都實現了 List 接口(List 接口繼承了 Collection 接口),他們都是有序集合,即存儲在這兩個集合中的元素位置都是有順序的,至關於一種動態的數組,咱們之後能夠按位置索引來取出某個元素,而且其中的數據是容許重複的,這是與 HashSet 之類的集合的最大不一樣處,HashSet 之類的集合不能夠按索引號去檢索其中的元素,也不容許有重複的元素。
ArrayList 與 Vector 的區別主要包括兩個方面:
面試官:那 ArrayList 和 LinkedList 的區別呢?
答:
面試官:Array 和 ArrayList 有什麼區別?何時該應 Array 而不是 ArrayList 呢?
答:它們的區別是:
對於基本類型數據,集合使用自動裝箱來減小編碼工做量。可是,當處理固定大小的基本數據類型的時候,這種方式相對比較慢。
答:
Vector newVector = new Vector(); for (int i = 0; i < vector.size(); i++) { Object obj = vector.get(i); if (!newVector.contains(obj)) { newVector.add(obj); } }
還有一種簡單的方式,利用了 Set 不容許重複元素的特性:
HashSet set = new HashSet(vector);
小結:本小節是 Java 中關於集合的考察,是 Java 崗位面試中必考的知識點,除了應該掌握以上的問題,包括各個集合的底層實現也建議各位同窗閱讀,加深理解。
答:有序數組最大的好處在於查找的時間複雜度是O(log n),而無序數組是O(n)。有序數組的缺點是插入操做的時間複雜度是O(n),由於值大的元素須要日後移動來給新元素騰位置。相反,無序數組的插入時間複雜度是常量O(1)。