Java基礎常見面試題一
1、Java 語言有哪些特點?
- 面向物件(封裝,繼承,多型);
- 跨平臺( Java 虛擬機器實現平臺無關性);
- 可靠性;
- 安全性;
- 支援網路程式設計並且很方便( Java 語言誕生本身就是為簡化網路程式設計設計的,因此 Java 語言不僅支援網路程式設計而且很方便);
- 編譯與解釋並存;
2、排序演算法
3、雙重指標
一種演算法解題思想,利用兩個指標同時移動,達到O(n)的複雜度解決問題。比如歸併排序,快速排序等等就利用這種思想。4、Java Object類有什麼方法
hashcode(), equals(), clone(), toString(), getClass(), finalize(), notify(), notifyAll(), wait()
5、解釋一下 JVM
Java 虛擬機器(JVM)是執行 Java 位元組碼的虛擬機器。JVM 有針對不同系統的特定實現(Windows,Linux,macOS),目的是隻要使用相同的位元組碼,在任何平臺都會給出相同的結果。不同系統的 JVM 實現是 Java 語言“一次編譯,隨處可以執行”的關鍵所在。6、 JRE和JDK的區別
JRE是Java的執行環境,其中包含了JVM 和 執行java所需的核心類庫,JDK是Java開發工具包,除有Javac這樣的開發工具還包含了JRE這個執行環境為什麼JDK中包含一個JRE呢?
開發完的程式,需要執行一下看看效果7、Java 與 C++ 的區別
- Java 是純粹的面嚮物件語言,所有的物件都繼承自 java.lang.Object,C++ 為了相容 C 既支援面向物件也支援
- Java 通過虛擬機器從而實現跨平臺特性,但是 C++ 依賴於特定的平臺。
- Java 的 goto 是保留字,但是不可用,C++ 可以使用 goto。
- Java 支援自動垃圾回收,而 C++ 需要手動回收。
- Java 沒有指標,它的引用可以理解為安全指標,而 C++ 具有和 C 一樣的指標。
- Java 不支援多重繼承,只能通過實現多個介面來達到相同目的,而 C++ 支援多重繼承。
- Java 不支援操作符過載,雖然可以對兩個 String 物件執行加法運算,但是這是語言內建支援的操作,不屬於操作符過載,而 C++ 可以。
- Java 不支援條件編譯,C++ 通過 #ifdef #ifndef 等預處理命令從而實現條件編譯。
8、面向物件和麵向過程的區別
面向物件: 資料與操作分離面向過程:資料與操作緊密結合9、c++11新特性
long long型別,nullptr常量,auto關鍵字,範圍for迴圈,lambda表示式,string數值轉換函式,智慧指標10、C++裡的HashMap底層是怎麼實現的
陣列加紅黑樹11、C++的記憶體分割槽
在C++中,記憶體分成5個區,他們分別是堆、棧、自由儲存區、全域性/靜態儲存區和常量儲存區1.棧,就是那些由編譯器在需要的時候分配,在不需要的時候自動清除的變數的儲存區。裡面的變數通常是區域性變數、函式引數等。2.堆,就是那些由new分配的記憶體塊,他們的釋放編譯器不去管,由我們的應用程式去控制,一般一個new就要對應一個delete。如果程式設計師沒有釋放掉,那麼在程式結束後,作業系統會自動回收。3.自由儲存區,就是那些由malloc等分配的記憶體塊,他和堆是十分相似的,不過它是用free來結束自己的生命的。4.全域性/靜態儲存區,全域性變數和靜態變數被分配到同一塊記憶體中,在以前的C語言中,全域性變數又分為初始化的和未初始化的,在C++裡面沒有這個區分了,他們共同佔用同一塊記憶體區。5.常量儲存區,這是一塊比較特殊的儲存區,他們裡面存放的是常量,不允許修改(當然,你要通過非正當手段也可以修改)原文連結:https://blog.csdn.net/metheir/article/details/5262943712、覺得編碼時要遵循哪些設計原則
- 1.模組化程式設計,提高模組的獨立性,從而增強程式碼的可讀性和可維護性
- 2. 模組規模應該適中,且應該極力降低模組介面的複雜程度
- 3. 命名規範,比如使用對應的英文單詞或者使用駝峰命名
- 4. 註釋規範,比如註釋不能有歧義等
13、JDK 5 - JDK 8 的新特性
JDK1.5新特性:
1.自動裝箱與拆箱:
2.列舉
3.靜態匯入,如:import staticjava.lang.System.out
4.可變引數(Varargs)
5.泛型(Generic)(包括通配型別/邊界型別等)
6.For-Each迴圈
7.註解
JDK1.7 新特性
1.對Java集合(Collections)的增強支援,可直接採用[]、{}的形式存入物件,採用[]的形式按照索引、鍵值來獲取集合中的物件。如:
List<String>list=[“item1”,”item2”];//存
Stringitem=list[0];//直接取
Set<String>set={“item1”,”item2”,”item3”};//存
Map<String,Integer> map={“key1”:1,”key2”:2};//存
Intvalue=map[“key1”];//取
2.在Switch中可用String
3.數值可加下劃線用作分隔符(編譯時自動被忽略)
4.支援二進位制數字,如:int binary= 0b1001_1001;
JDK1.8 新特性
1. 簡化了Lamda 表示式的語法沒必要使用這種傳統的匿名物件的方式了,Java 8提供了更簡潔的語法Collections.sort(names, (String a, String b) -> {
return b.compareTo(a);
})
對於函式體只有一行程式碼的,你可以去掉大括號{}以及return關鍵字。如:
Collections.sort(names,(String a, String b) -> b.compareTo(a));
或:Collections.sort(names, (a, b) -> b.compareTo(a));
2. 一組全新的時間日期API。
3 .多重註解:允許我們把同一個型別的註解使用多次,只需要給該註解標註一下@Repeatable即可
4. 介面的預設方法
5.並行Streams
前面提到過Stream有序列和並行兩種,序列Stream上的操作是在一個執行緒中依次完成,而並行Stream則是在多個執行緒上同時執行。
14、字元型常量和字串常量的區別?
- 形式上: 字元常量是單引號引起的一個字元; 字串常量是雙引號引起的若干個字元
- 含義上: 字元常量相當於一個整型值( ASCII 值),可以參加表示式運算; 字串常量代表一個地址值(該字串在記憶體中存放位置)
- 佔記憶體大小 字元常量只佔 2 個位元組; 字串常量佔若干個位元組 (注意: char 在 Java 中佔兩個位元組)
15、 構造器 Constructor 是否可被 override?
Constructor 不能被 override(重寫),但是可以 overload(過載),所以你可以看到一個類中有多個建構函式的情況。
16、過載與重寫
重寫:
存在於繼承體系中,指子類實現了一個與父類在方法宣告上完全相同的一個方法。為了滿足裡式替換原則,重寫有以下三個限制:- 子類方法的訪問許可權必須大於等於父類方法;
- 子類方法的返回型別必須是父類方法返回型別或為其子型別。
- 子類方法丟擲的異常型別必須是父類丟擲異常型別或為其子型別
過載:
存在於同一個類中,指一個方法與已經存在的方法名稱上相同,但是引數型別、個數、順序至少有一個不同。應該注意的是,返回值不同,其它都相同不算是過載。
17、 怎麼解釋“過載是編譯時的多型性,重寫是執行時的多型性”
在 Class 檔案常量池裡,每個方法有它的名稱、描述符(引數型別+返回值型別)。名稱加上描述符等於方法的簽名,因為過載在編譯時每個方法的簽名就已經確定是不一樣的,所以在編譯時就能確定呼叫哪個方法,所以說過載是編譯時的多型而重寫的話,因為是父類引用指向子類物件,通過這個父類引用去呼叫被子類重寫了的方法。因為只有在執行時才知道指向是的哪個子類物件,所以只有在執行時才能確定執行哪個方法,所以說重寫是執行時的多型。參考:過載(編譯期多型),重寫(執行時多型)
18、 Java 面向物件程式設計三大特性: 封裝 繼承 多型
封裝
封裝把一個物件的屬性私有化,同時提供一些可以被外界訪問屬性的方法,保證了屬性的安全
繼承
繼承一個類使用 extends 關鍵字與另一個類建立的某種聯絡,子類的定義可以增加新的資料或新的功能,也可以用父類的功能,不能繼承多個父類。通過使用繼承我們能夠非常方便地複用以前的程式碼。多型
就是用基類的引用指向子類的物件,具體訪問時實現方法的動態繫結。在需要使用子類的地方宣告為父類的引用,這樣傳入不同的子類物件,可以有不同的解釋,產生不同的執行結果,這就是多型。
多型有什麼好處?
- 多型使用了繼承,所以有繼承的好處,即子類可以複用父類的程式碼,大大提高程式的可複用性。//繼承
- 父類的方法或引用變數可以呼叫被子類重寫的功能,在需要使用子類的地方宣告為父類的引用,可以提高可擴充性和可維護性,降低程式與物件的耦合度。//多型的真正作用
19、簡述一下 快排的過程
1. 首先對整個陣列選定一個主元,然後根據這個主元將整個陣列分成小於主元的左區間,和所有元素都大於主元的右區間,然後分別對左右區間遞迴進行剛才的選主元劃分左右集合的操作。
快排的最壞複雜度是多少
快排最好的情況是,每次正好中分,複雜度為O(nlogn)。最差情況,就是已經有序的情況下,如果每次選定的主元是區間的第一個元素,那麼複雜度為O(n^2),退化成氣泡排序,因為每次快排都必須遍歷整個區間的所有元素,所以這裡就有一個O(n)的演算法複雜度,但是因為它不能將元素劃分成左右兩個集合,所以不能將遞迴深度降到O(logn), 而是O(n),所以複雜度是O(n^2).
為什麼快排的複雜度是O(nlogn)
對每次選定一個主元后都必須遍歷這個區間內的所有元素,所以這裡就有一個O(n)的演算法複雜度,另外,對左右集合分別遞迴分治進行快排操作,這會產生一個深度為O(logn)的複雜度,所以總的來說快排的複雜度是 O(nlogn)如何優化快排
1. 隨機選取主元
可以避免陣列已經有序而退化成氣泡排序2. 小陣列切換到插入排序
對於小陣列,插入排序比快速排序的效能更好3. 三數取中法
最好的情況下是每次都能取陣列的中位數作為切分元素,但是計算中位數的代價很高。一種折中方法是取 3 個元素,並將大小居中的元素作為主元。20、==和equals的區別?
== 判斷是否是同一個物件,equals 判段物件是否等價
21、講一講你對hashcode和equal這兩個函式的認識
hashCode() 返回雜湊值,而 equals() 是用來判斷兩個物件是否等價。等價的兩個物件雜湊值一定相同,但是雜湊值相同的兩個物件不一定等價。其中 “等價的兩個物件雜湊值一定相同” 是一個約定,而不是一個定理。等價的兩個物件雜湊值也可以不相同,比如直接讓 equals 返回 true ,這樣所有物件都是等價的,但是他們的 hash 值不相等。但是我們約定在覆蓋 equals() 方法時應當總是覆蓋 hashCode() 方法,保證等價的兩個物件雜湊值也相等。比如新建了兩個等價的物件,並將它們新增到 HashSet 中。我們希望將這兩個物件當成一樣的,只在集閤中新增一個物件,但是因為 沒有實現hashCode() 方法,因此這兩個物件的雜湊值是不同的,最終導致集合添加了兩個等價的物件因為存在 hash 衝突的情況,所以hash 值相同的兩個物件不一定等價。
22、String、StringBuffer、StringBuilder三者的區別?
1. 可變性
String 不可變StringBuffer 和 StringBuilder 可變
2. 執行緒安全
String 不可變,因此是執行緒安全的StringBuilder 不是執行緒安全的StringBuffer 是執行緒安全的,內部使用 synchronized 進行同步
23、抽象類與介面的區別
比較
- 從設計層面上看,抽象類提供了一種 IS-A 關係(父子關係),那麼就必須滿足裡式替換原則,即子類物件必須能夠替換掉所有父類物件,子類會繼承父類所有的屬性和方法。而介面更像是一種 LIKE-A 關係(師徒關係),它只是提供一種方法實現契約,需要實現什麼樣的功能就實現什麼樣的介面。
- 從使用上來看,一個類可以實現多個介面,但是不能繼承多個抽象類。
- 介面的欄位只能是 static 和 final 型別的,而抽象類的欄位沒有這種限制。
- 介面的成員只能是 public 的,而抽象類的成員可以有多種訪問許可權。
- 在 jdk8 以前,介面中的方法預設全是抽象方法,不能有方法體,而 抽象類中可以有不宣告為抽象方法的方法,這些方法可以有方法體。但是jdk 8以後,介面也支援有方法體的預設方法了。
- 介面的體量相對都比較小,而類的體量相對就比較大,體量大的話會增大維護成本和降低使用效率
使用選擇
使用介面的場景:
需要讓不相關的類都實現一個方法,增加某個功能時。例如不相關的類都可以實現 Compareable 介面中的 compareTo() 方法;需要使用多重繼承。
使用抽象類的場景:
需要在幾個相關的類中共享程式碼。需要能控制繼承來的成員的訪問許可權,而不是都為 public。
需要繼承非靜態和非常量欄位。
在很多情況下,介面優先於抽象類。因為介面沒有抽象類嚴格的類層次結構要求,可以靈活地為一個類新增行為。並且從 Java 8 開始,介面也可以有預設的方法實現,使得修改介面的成本也變的很低
24、基礎資料型別和包裝類物件,需要使用包裝類的場景
有了基本型別為什麼還要有包裝型別呢?
我們知道Java是一個面相物件的程式語言,基本型別並不具有物件的性質,為了讓基本型別也具有物件的特徵,就出現了包裝型別(如我們在使用集合型別Collection時就一定要使用包裝型別而非基本型別),它相當於將基本型別“包裝起來”,使得它具有了物件的性質,並且為其添加了屬性和方法,豐富了基本型別的操作。另外,當需要往ArrayList,HashMap中放東西時,像int,double這種基本型別是放不進去的,因為容器都是裝object的,這是就需要這些基本型別的包裝器類了。基本型別與包裝型別的異同:
1、在Java中,一切皆物件,但八大基本型別卻不是物件。
2、宣告方式的不同,基本型別無需通過new關鍵字來建立,而封裝型別則需new關鍵字。
3、儲存方式及位置的不同,基本型別是直接儲存變數的值,儲存在堆疊中能高效的存取;封裝型別需要通過引用指向例項,具體的例項儲存在堆中;
4、初始值的不同,封裝型別的初始值為null,基本型別的的初始值視具體的型別而定,比如int型別的初始值為0,boolean型別為false;
5、使用場景的不同,包裝類的使用場景更加多樣。比如與集合類合作使用時只能使用包裝型別。
6、什麼時候該用包裝類,什麼時候該用基本型別,看基本的業務來定:這個欄位允不允許null值,如果允許,則必然要用封裝類;否則,基本型別就可以了。如果用到比如泛型和反射呼叫函式,就需要用包裝類!
包裝類的使用場景:
- 集合類中只能使用包裝類
- 泛型中使用包裝類
- 反射呼叫函式中使用包裝類
- 資料庫查詢的結果為null時,不能賦值給基本型別,應該使用包裝類
25、在一個靜態方法內呼叫一個非靜態成員為什麼是非法的?
因為非靜態成員只有存在於一個物件中才有意義,但是由於靜態方法可以不通過物件進行呼叫,因此在靜態方法裡,不能呼叫其他非靜態變數,也不可以訪問非靜態成員方法。
26、 在 Java 中定義一個不做事且沒有引數的構造方法的作用
為了子類物件能夠正確的初始化。在執行子類的構造方法之前,如果沒有顯示的呼叫super()函式
來指定所呼叫的父類特定的構造方法,則會預設呼叫父類中“沒有引數的構造方法”。因此,如果父類中只定義了有引數的構造方法,則編譯時將發生錯誤,因為 Java 程式在父類中找不到沒有引數的構造方法可供執行。解決辦法是在父類里加上一個不做事且沒有引數的構造方法。
27、構造方法作用和有哪些特性?
作用是完成對類例項物件的初始化工作特性:
- 名字與類名相同。
- 沒有返回值,但不能用 void 宣告建構函式。
- 生成類的例項
- 物件時自動執行,無需呼叫。
28、靜態方法和例項方法有何不同
- 靜態方法屬於類,而例項方法屬於物件,例項方法只能通過物件呼叫,而靜態方法還可以通過類名.靜態方法來使用
- 靜態方法在訪問本類的成員時,只允許訪問靜態成員(即靜態成員變數和靜態方法),而不允許訪問例項成員變數和例項方法;例項方法則無此限制。
29、final關鍵字的作用
final 關鍵字主要用在三個地方:變數、方法、類。1. 變數
宣告資料為常量,可以是編譯時常量,也可以是在執行時被初始化後不能被改變的常量。- 對於基本型別,final 使數值不變;
- 對於引用型別,final 使引用不變,也就不能引用其它物件,但是被引用的物件本身是可以修改的。
2. 方法
宣告方法不能被子類重寫。private 方法隱式地被指定為 final,如果在子類中定義的方法和基類中的一個 private 方法簽名相同,此時子類的方法不是重寫基類方法,而是在子類中定義了一個新的方法。3. 類
宣告類不允許被繼承。30、異常體系
1. 所有異常繼承公共祖先【throwable類】
2. 異常分為兩大類 : Exception & Error
(1) Error 是【程式無法處理】的錯誤,大部分是程式碼在JVM執行時出現問題。程式碼邏輯錯誤或者外部環境錯誤
如 程式申請記憶體時,記憶體資源不足。丟擲OOM
Error發生時,JVM一般會【終止】執行緒
(2) Exception是【程式可以處理】的錯誤
常見Exception:
ArithmeticException 算數異常
NullPointException 空指標異常
ArrayIndexOutOfBoundsException 下標越界
31、記憶體洩漏
- 程式沒有釋放已經不再使用的記憶體,由於設計錯誤,導致在釋放該段記憶體之前就失去了對該段記憶體的控制,因此這段記憶體一直被佔用,無法釋放,造成空間的浪費。
- 長生命週期的引用指向了短生命週期的物件
記憶體洩漏案例
1. 單例模式
Instance可能早已不被使用,
但是類仍持有Instance的【引用】。
因此Intance【生命週期】和引用相同,造成記憶體洩漏。
2. 容器
容器內的【鍵值對】不被使用時,
Map仍持有key物件& value物件的【引用】,
則會造成記憶體洩漏。
怎麼查記憶體洩漏
《Java線上記憶體溢位問題排查步驟》
32、記憶體溢位
要求分配的記憶體超過了系統能給我的,系統不能滿足需求。記憶體洩漏的堆積如果不及時處理最終會導致記憶體溢位
33、Java 中 IO 流分為幾種?
- 按照流的流向分,可以分為輸入流和輸出流;
- 按照操作單元劃分,可以劃分為位元組流和字元流;
35、為什麼 Java 不支援運算子過載? 另一個類似棘手的 Java 問題。為什麼C++支援運算子過載而Java不支援?
有人可能會說+運算子在Java中已被過載用於字串連線,不要被這些論據所欺騙。 與C++不同,Java不支援運算子過載。Java不能為程式設計師提供自由的標準算術運算子過載,例如+,-,*和/等。如果你以前用過C++,那麼Java與C++相比少了很多功能,例如Java不支援多重繼承,Java 中沒有指標,Java 中沒有引用傳遞。另一個類似的問題是關於Java通過引用傳遞,這主要表現為Java是通過值還是引用傳參。雖然我不知道背後的真正原因,但我認為以下說法有些道理,為什麼Java不支援運算子過載。
(1)簡單性和清晰性。
清晰性是 Java 設計者的目標之一。設計者不是隻想複製語言,而是希望擁有一種清晰,真正面向物件的語言。新增運算子過載比沒有它肯定會使設計更復雜,並且它可能導致更復雜的編譯器,或減慢JVM,因為它需要做額外的工作來識別運算子的實際含義,並減少優化的機會,以保證Java中運算子的行為。
(2)避免程式設計錯誤。
Java不允許使用者定義的運算子過載,因為如果允許程式設計師進行運算子過載,將為同一運算子賦予多種含義,這將使任何開發人員的學習曲線變得陡峭,事情變得更加混亂。據觀察,當語言支援運算子過載時,程式設計錯誤會增加,從而增加了開發和交付時間。由於Java和JVM已經承擔了大多數開發人員的責任,如在通過提供垃圾收集器進行記憶體管理時,因為這個功能增加汙染程式碼的機會,成為程式設計錯誤之源,因此沒有多大意義。
(3)JVM 複雜性。
從 JVM 的角度來看,支援運算子過載使問題變得更加困難。通過更直觀,更乾淨的方式使用方法過載也能實現同樣的事情,因此不支援Java中的運算子過載是有意義的。與相對簡單的JVM相比,複雜的JVM可能導致JVM更慢,併為保證在Java中運算子行為的確定性從而減少了優化程式碼的機會。 4)讓開發工具處理更容易。這是在Java中不支援運算子過載的另一個好處。省略運算子過載使語言更容易處理,這反過來又更容易開發處理語言的工具,例如IDE或重構工具。Java中的重構工具遠勝於C++。
36、假設你有一個類,它序列化並存儲在永續性中,然後修改了該類以新增新欄位。如果對已序列化的物件進行反序列化,會發生什麼情況
這取決於類是否具有其自己的serialVersionUID。正如我們從上面的問題知道,如果我們不提供serialVersionUID,則Java編譯器將生成它,通常它等於物件的雜湊程式碼。通過新增任何新欄位,有可能為該類新版本生成的新serialVersionUID與已序列化的物件不同,在這種情況下,Java序列化API將引發java.io.InvalidClassException,因此建議在程式碼中擁有自己的serialVersionUID,並確保在單個類中始終保持不變。
37、Java 序列化機制中的相容更改和不相容更改是什麼?
真正的挑戰在於通過新增任何欄位、方法或刪除任何欄位或方法來更改類結構,方法是使用已序列化的物件。根據Java序列化規範,新增任何欄位或方法都面臨相容的更改和更改類層次結構或取消實現的可序列化介面,有些介面在非相容更改下。對於相容和非相容更改的完整列表,我建議閱讀Java序列化規範。 4.我們可以通過網路傳輸一個序列化的物件嗎 是的,你可以通過網路傳輸序列化物件,因為Java序列化物件仍以位元組的形式保留,位元組可以通過網路傳送。你還可以將序列化物件儲存在磁碟或資料庫中作為Blob。 在Java序列化期間,哪些變數未序列化? 這個問題問得不同,但目的還是一樣的,Java 開發人員是否知道靜態和瞬態變數的細節。由於靜態變數屬於類,而不是物件,因此它們不是物件狀態的一部分,因此在Java序列化過程中不會儲存它們。由於Java序列化僅保留物件的狀態,而不是物件本身。瞬態變數也不包含在Java序列化過程中,並且不是物件的序列化狀態的一部分。在提出這個問題之後,面試官會詢問後續內容,如果你不儲存這些變數的值,那麼一旦對這些物件進行反序列化並重新建立這些變數,這些變數的價值是多少?這是你們要考慮的。
38、Java 中,巢狀公共靜態類與頂級類有什麼不同?
類的內部可以有多個巢狀公共靜態類,但是一個 Java 原始檔只能有一個頂級公共類,並且頂級公共類的名稱與原始檔名稱必須一致。