JavaSE 基础面试题
JDK 和 JRE 的區別是什么?
Java 運行時環境 (JRE)。它包括 Java 虛擬機、Java 核心類庫和支持文件。它不包含開發工具(JDK)–編譯器、調試器和其他工具。
Java 開發工具包 (JDK) 是完整的 Java 軟件開發包,包含了 JRE,編譯器和其他的工具(比如:JavaDoc,Java 調試器),可以讓開發者開發、編譯、執行 Java 應用程序。
float f = 3.4;是否正確?
不正確。3.4 是雙精度數,將雙精度型(double)賦值給浮點型(float)屬于下轉型(down-casting,也稱為窄化)會造成精度損失,因此需要強制類型轉換 float f =(float)3.4; 或者寫成 float f =3.4F;。
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
有關的一個面試題 (這個題目挺有意思的,大家要多加注意):
如果不明就里很容易認為兩個輸出要么都是 true 要么都是 false。首先需要注意的是 f1、f2、f3、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 propertyint 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_VALUEh = Math.min(i, Integer.MAX_VALUE - (-low) -1);} catch( NumberFormatException nfe) {// If the property cannot be parsed into an int, ignore it.}}high = h;cache = new Integer[(high - low) + 1];int j = low;for(int k = 0; k < cache.length; k++)cache[k] = new Integer(j++);// range [-128, 127] must be interned (JLS7 5.1.7)assert IntegerCache.high >= 127;}private IntegerCache() {}}簡單的說,如果整型字面量的值在-128 到 127 之間,那么不會 new 新的 Integer 對象,而是直接引用常量池中的 Integer 對象,所以上面的面試題中 f1==f2 的結果是 true,而 f3==f4 的結果是 false。
== 與 equals() 的區別?
== 比較的是兩個變量之間的值是否相等,類比講的話也就是說比較這兩個對象是不是同一個人?;緮祿愋捅容^的是兩個變量的值;如果是引用數據類型,比較的是對應引用類型的首地址。
public class TestString {public static void main(String[] args) {Integer a1 = 1;Integer a2 = 1;Integer b1 = 150;Integer b2 = 150;System.out.println(a1 == a2);System.out.println(b1 == b2);} }這個例子你可以類比上面的那個題目,最好兩個題目都看一下,a1 == a2 的結果為 true ,因為他們兩個指向的是同一個對象。b1 == b2 結果為 false,因為 b1 與 b2 是兩個不同的對象所以它們的首地址并不相同故結果為 false。至于 a1 與 a2 也是引用類型的數據為什么比較的結果是 true,這是由 Integer 包裝類的特性決定的,你可以參考上面的題目。
equals() 比較的是兩個對象對否具有某些一樣的特征,類比講的話也就是比較這兩個人是否一樣。其實 Object 類中的 equals() 方法調用的就是 this == obj,你可以通過重寫 equals() 方法自定義你想要比較的某些特征,但是要注意的是如果你重寫了 equals() 方法,那么也要重寫 hashCode() 方法。
switch 是否能作用在 byte 上,是否能作用在 long 上,是否能作用在 String 上?
在 Java 5 以前,switch(expr) 中,expr 只能是 byte、short、char、int(及其包裝類型)。從 Java 5 開始,Java 中引入了枚舉類型,expr 也可以是 enum 類型,從 Java 7 開始,expr 還可以是字符串(String),但是長整型(long)在目前所有的版本中都是不可以的。 當你指定 expr 為 long 時會在編譯期獲得一個錯誤:
required: ‘char, byte, short, int, Character, Byte, Short, Integer, String, or an enum
在一個技術群里看到一個朋友說他在面試的時候遇到一個有意思的題目,問 if() 中寫什么條件會打印 AB?
public class PrintAB {public static void main(String[] args) {if( ? ){System.out.print("A");}else {System.out.print("B");}} }System.out.printf(“A”) == null ,我們來看一看 System.out.printf() 在源碼中的實現,這個方法返回了一個 PrintStream 打印流對象,并且這個方法允許傳入可變的參數列表。在執行的時候需要注意打印的是 if() 條件里面的‘’A‘’(不得不吐槽一句,現在都這么會玩的嗎)。
public PrintStream printf(String format, Object ... args) {return format(format, args);}如果兩個對象經過判斷 (x.equals(y) == true),x 與 y 必須要有相同的 hashCode 嗎?
是,官方 API 中 Object 中的 hashCode() 方法中這樣說道:
如果根據 equals(Object) 方法判定兩個對象是相等的,那么調用 hashCode 方法每一個對象必須產生相同的整數結果。如果按照 equals(java.lang.Object) 方法判定兩個對象是不平等的,然后調用 hashCode 方法每一個對象必須產生不同的整數結果。
但是反過來是正確的:如果兩個對象 x 與 y 的 hashCode() 相同,那么 x.equals(y) 不一定為 true。如果你了解 HashMap 中的鍵值存儲那么這個問題時很容易理解的。還有一點要注意的是如果你重寫了 equals() 方法,那么也要重寫 hashCode() 方法。
抽象類與接口
從設計層面來說,抽象是對類的抽象,是一種模板設計,接口是行為的抽象,是一種行為的規范。
如何權衡是使用無序的數組還是有序的數組?
有序數組最大的好處在于查找的時間復雜度是 O(log n),而無序數組是 O(n)。有序數組的缺點是插入操作的時間復雜度是 O(n),因為值大的元素需要往后移動來給新元素騰位置。相反,無序數組的插入時間復雜度是常量 O(1)。
Iterator 和 ListIterator 的區別是什么?
- Iterator 可用來遍歷 Set 和 List 集合,但是 ListIterator 只能用來遍歷 List
- Iterator 對集合只能是前向遍歷,ListIterator 既可以前向也可以后向
- ListIterator 實現了 Iterator 接口,并包含其他的功能,比如:增加元素,替換元素,獲取前一個和后一個元素的索引,等等
HashSet 和 TreeSet 有什么區別?
HashSet 是由一個 hash 表來實現的,因此,它的元素是無序的。add(),remove(),contains() 方法的時間復雜度是 O(1)。
另一方面,TreeSet 是由一個樹形的結構來實現的,它里面的元素是有序的。因此,add(),remove(),contains() 方法的時間復雜度是 O(logn)。
HashMap 和 Hashtable 有什么區別?
- HashMap 允許鍵和值是 null,而 Hashtable 不允許鍵或者值是 null
- Hashtable 是同步的,而 HashMap 不是。因此,HashMap 更適合于單線程環境,而 Hashtable 適合于多線程環境
- HashMap 提供了可供應用迭代的鍵的集合,因此,HashMap 是快速失敗的。另一方面,Hashtable 提供了對鍵的列舉 (Enumeration)
- 一般認為 Hashtable 是一個遺留的類
快速失敗 (fail-fast) 和安全失敗 (fail-safe) 的區別是什么?
Iterator 的安全失敗是基于對底層集合做拷貝,因此,它不受源集合上修改的影響。java.util 包下面的所有的集合類都是快速失敗的,而 java.util.concurrent 包下面的所有的類都是安全失敗的??焖偈〉牡鲿伋?ConcurrentModificationException 異常,而安全失敗的迭代器永遠不會拋出這樣的異常。
Error 和 Exception 有什么區別?
Error 類一般是指與虛擬機相關的問題,如系統崩潰,虛擬機錯誤,內存空間不足,方法調用棧溢等。對于這類錯誤的導致的應用程序中斷,僅靠程序本身無法恢復和和預防,遇到這樣的錯誤,建議讓程序終止。
Exception 類表示程序可以處理的異常,可以捕獲且可能恢復。遇到這類異常,應該盡可能處理異常,使程序恢復運行,而不應該隨意終止異常。
寫出 5 個你遇見過的運行時異常
ClassCastException:類轉換異常
IndexOutOfBoundsException:數組越界
NullPointerException:空指針
ArrayStoreException:數據存儲異常,操作數組時類型不一致
BufferOverflowException :I/O 異常
ConcurrentModificationException:并發修改時拋出
IllegalArgumentException:參數不合適時拋出
IllegalStateException:對象狀態不合適時拋出
Java 中的兩種異常類型是什么?他們有什么區別?
Java 中有兩種異常:受檢查的 (checked) 異常和不受檢查的 (unchecked) 異常。也可以稱為運行時異常RuntimeException 和非運行時異常。
不受檢查的異常不需要在方法或者是構造函數上聲明,就算方法或者是構造函數的執行可能會拋出這樣的異常,并且不受檢查的異??梢詡鞑サ椒椒ɑ蛘呤菢嬙旌瘮档耐饷?。
相反,受檢查的異常必須要用throws語句在方法或者是構造函數上聲明。
異常處理完成以后,Exception 對象會發生什么變化?
Exception對象會在下一個垃圾回收過程中被回收掉。
try{} 里有一個 return 語句,那么緊跟在這個 try 后的 finally{} 里的代碼會不會被執行? 有哪些情況下 finally 語句塊是不執行的
會執行,如果在 try 里有返回值,并且 finally{} 中也有返回值,那么得到最終的值是 finally 中返回的數據,但是并不建議這么做。下面補充兩個 finally{} 中不執行的情況:
1:在 try{} 里執行 system.exit() 語句,表示終止當前虛擬機。
2:守護線程中的 finally{} 不執行 (守護線程執行的情況下)。
并發與并行的區別
并發:并發是指兩個或更多任務可以在重疊時間段內啟動,運行并完成。 這并不一定意味著他們都會在同一時刻運行。 例如,在單核機器上進行多任務處理。
并行:并行是指任務在同一時間運行,例如在多核處理器上運行。
Thread 類的 sleep() 方法和對象的 wait() 方法都可以讓線程暫停執行,它們有什么區別?
sleep() 方法(休眠)是線程類(Thread)的靜態方法, 調用此方法會讓當前線程暫停執行指定的時間,將執行機會(CPU)讓給其他線程,但是對象的鎖依然保持,因此休眠時間結束后會自動恢復。wait() 是 Object 類的方法,調用對象的 wait() 方法導致當前線程放棄對象的鎖(線程暫停執行),進入對象的等待池(wait pool),只有調用對象的 notify() 方法(或 notifyAll() 方法)時才能喚醒等待池中的線程進入等鎖池(lock pool),如果線程重新獲得對象的鎖就可以進入就緒狀態。
補充:可能不少人對什么是進程,什么是線程還比較模糊,對于為什么需要多線程編程也不是特別理解。簡單的說:進程是具有一定獨立功能的程序關于某個數據集合上的一次運行活動,是操作系統進行資源分配和調度的一個獨立單位;線程是進程的一個實體,是 CPU 調度和分派的基本單位,是比進程更小的能獨立運行的基本單位。線程的劃分尺度小于進程,這使得多線程程序的并發性高;進程在執行時通常擁有獨立的內存單元,而線程之間可以共享內存。使用多線程的編程通常能夠帶來更好的性能和用戶體驗,但是多線程的程序對于其他程序是不友好的,因為它可能占用了更多的 CPU 資源。當然,也不是線程越多,程序的性能就越好,因為線程之間的調度和切換也會浪費 CPU 時間。時下很時髦的 Node.js 就采用了單線程異步 I/O 的工作模式。
什么是線程池(thread pool)
在面向對象編程中,創建和銷毀對象是很費時間的,因為創建一個對象要獲取內存資源或者其它更多資源。在 Java 中更是如此,虛擬機將試圖跟蹤每一個對象,以便能夠在對象銷毀后進行垃圾回收。所以提高服務程序效率的一個手段就是盡可能減少創建和銷毀對象的次數,特別是一些很耗資源的對象創建和銷毀,這就是”池化資源”技術產生的原因。線程池顧名思義就是事先創建若干個可執行的線程放入一個池(容器)中,需要的時候從池中獲取線程不用自行創建,使用完畢不需要銷毀線程而是放回池中,從而減少創建和銷毀線程對象的開銷。
Java 5+中的 Executor 接口定義一個執行線程的工具。它的子類型即線程池接口是 ExecutorService。要配置一個線程池是比較復雜的,尤其是對于線程池的原理不是很清楚的情況下,因此在工具類 Executors 面提供了一些靜態工廠方法,生成一些常用的線程池,如下所示:
- newSingleThreadExecutor:創建一個單線程的線程池。這個線程池只有一個線程在工作,也就是相當于單線程串行執行所有任務。如果這個唯一的線程因為異常結束,那么會有一個新的線程來替代它。此線程池保證所有任務的執行順序按照任務的提交順序執行。
- newFixedThreadPool:創建固定大小的線程池。每次提交一個任務就創建一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,如果某個線程因為執行異常而結束,那么線程池會補充一個新線程。
- newCachedThreadPool:創建一個可緩存的線程池。如果線程池的大小超過了處理任務所需要的線程,那么就會回收部分空閑(60 秒不執行任務)的線程,當任務數增加時,此線程池又可以智能的添加新線程來處理任務。此線程池不會對線程池大小做限制,線程池大小完全依賴于操作系統(或者說 JVM)能夠創建的最大線程大小。
- newScheduledThreadPool:創建一個大小無限的線程池。此線程池支持定時以及周期性執行任務的需求。
概括的解釋下線程的幾種可用狀態。
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 () 方法,則該線程結束生命周期。死亡的線程不可再次復生。
同步方法和同步代碼塊的區別是什么?
1.同步方法默認用 this 或者當前類 class 對象作為鎖
2.同步代碼塊可以選擇以什么來加鎖,比同步方法要更細顆粒度,我們可以選擇只同步會發生同步問題的部分代碼而不是整個方法
3.同步方法使用關鍵字 synchronized修飾方法,而同步代碼塊主要是修飾需要進行同步的代碼,用 synchronized(object){代碼內容}進行修飾
在監視器 (Monitor) 內部,是如何做線程同步的?程序應該做哪種級別的同步?
在 java 虛擬機中, 每個對象 ( Object 和 class ) 通過某種邏輯關聯監視器,每個監視器和一個對象引用相關聯, 為了實現監視器的互斥功能, 每個對象都關聯著一把鎖.
一旦方法或者代碼塊被 synchronized 修飾, 那么這個部分就放入了監視器的監視區域, 確保一次只能有一個線程執行該部分的代碼, 線程在獲取鎖之前不允許執行該部分的代碼
總結
以上是生活随笔為你收集整理的JavaSE 基础面试题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 特种兵在都市夜十三有声小说原声(特种兵在
- 下一篇: 途游斗地主2016官方版下载(途游斗地主