花了近十年的時間,整理出史上最全面Java面試題

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

2、float f=3.4;是否正確?
不正確。3.4是雙精度數,將雙精度型(double)賦值給浮點型(float)屬於下轉型(down-casting,也稱爲窄化)會造成精度損失,因此需要強制類型轉換float f =(float)3.4; 或者寫成float f =3.4F;。

3、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);其中有隱含的強制類型轉換。

4、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
5、&和&&的區別?
&運算符有兩種用法:(1)按位與;(2)邏輯與。&&運算符是短路與運算。邏輯與跟短路與的差別是非常巨大的,雖然二者都要求運算符左右兩端的布爾值都是true整個表達式的值纔是true。&&之所以稱爲短路運算是因爲,如果&&左邊的表達式的值是false,右邊的表達式會被直接短路掉,不會進行運算。很多時候我們可能都需要用&&而不是&,例如在驗證用戶登錄時判定用戶名不是null而且不是空字符串,應當寫爲:username != null &&!username.equals(“”),二者的順序不能交換,更不能用&運算符,因爲第一個條件如果不成立,根本不能進行字符串的equals比較,否則會產生NullPointerException異常。注意:邏輯或運算符(|)和短路或運算符(||)的差別也是如此。

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

7、switch 是否能作用在byte 上,是否能作用在long,float 上,是否能作用在String上?
在Java 5以前,switch(expr)中,expr只能是byte、short、char、int。從Java 5開始,Java中引入了枚舉類型,expr也可以是enum類型,從Java 7開始,expr還可以是字符串(String),但是長整型(long),浮點數(float)在目前所有的版本中都是不可以的。

8、兩個對象值相同(x.equals(y) == true),但卻可有不同的hash code,這句話對不對?
不對,如果兩個對象x和y滿足x.equals(y) == true,它們的哈希碼(hash code)應當相同。

Java對於eqauls方法和hashCode方法是這樣規定的:

(1)如果兩個對象相同(equals方法返回true),那麼它們的hashCode值一定要相同;

(2)如果兩個對象的hashCode相同,它們並不一定相同。當然,你未必要按照要求去做,但是如果你違背了上述原則就會發現在使用容器時,相同的對象可以出現在Set集合中,同時增加新元素的效率會大大下降(對於使用哈希存儲的系統,如果哈希碼頻繁的衝突將會造成存取性能急劇下降)。

補充:關於equals和hashCode方法,很多Java程序都知道,但很多人也就是僅僅知道而已,在Joshua Bloch的大作《Effective Java》(很多軟件公司,《Effective Java》、《Java編程思想》以及《重構:改善既有代碼質量》是Java程序員必看書籍,如果你還沒看過,那就趕緊去亞馬遜買一本吧)中是這樣介紹equals方法的:首先equals方法必須滿足自反性(x.equals(x)必須返回true)、對稱性(x.equals(y)返回true時,y.equals(x)也必須返回true)、傳遞性(x.equals(y)和y.equals(z)都返回true時,x.equals(z)也必須返回true)和一致性(當x和y引用的對象信息沒有被修改時,多次調用x.equals(y)應該得到同樣的返回值),而且對於任何非null值的引用x,x.equals(null)必須返回false。

實現高質量的equals方法的訣竅包括:

使用==操作符檢查”參數是否爲這個對象的引用”;
使用instanceof操作符檢查”參數是否爲正確的類型”;
對於類中的關鍵屬性,檢查參數傳入對象的屬性是否與之相匹配;
編寫完equals方法後,問自己它是否滿足對稱性、傳遞性、一致性;
重寫equals時總是要重寫hashCode;
不要將equals方法參數中的Object對象替換爲其他的類型,在重寫時不要忘掉@Override註解。
當一個對象被當作參數傳遞到一個方法後,此方法可改變這個對象的屬性,並可返回變化後的結果,那麼這裏到底是值傳遞還是引用傳遞?
是值傳遞。Java語言的方法調用只支持參數的值傳遞。當一個對象實例作爲一個參數被傳遞到方法中時,參數的值就是對該對象的引用。對象的屬性可以在被調用過程中被改變,但對對象引用的改變是不會影響到調用者的。C++和C#中可以通過傳引用或傳輸出參數來改變傳入的參數的值。

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

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

10、闡述靜態變量和實例變量的區別。
靜態變量是被static修飾符修飾的變量,也稱爲類變量,它屬於類,不屬於類的任何一個對象,一個類不管創建多少個對象,靜態變量在內存中有且僅有一個拷貝;實例變量必須依存於某一實例,需要先創建對象然後通過對象才能訪問到它。靜態變量可以實現讓多個對象共享內存。

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

11、Object中有哪些公共方法?
equals()
clone()
getClass()
notify(),notifyAll(),wait()
toString()
12、是否可以從一個靜態(static)方法內部發出對非靜態(non-static)方法的調用?
不可以,靜態方法只能訪問靜態成員,因爲非靜態方法的調用要先創建對象,在調用靜態方法時可能對象並沒有被初始化。

13、深拷貝和淺拷貝的區別是什麼?
淺拷貝:被複制對象的所有變量都含有與原來的對象相同的值,而所有的對其他對象的引用仍然指向原來的對象。換言之,淺拷貝僅僅複製所考慮的對象,而不復制它所引用的對象。

深拷貝:被複制對象的所有變量都含有與原來的對象相同的值,而那些引用其他對象的變量將指向被複制過的新對象,而不再是原有的那些被引用的對象。換言之,深拷貝把要複製的對象所引用的對象都複製了一遍。

14、如何實現對象克隆?
有兩種方式:

實現Cloneable接口並重寫Object類中的clone()方法;
實現Serializable接口,通過對象的序列化和反序列化實現克隆,可以實現真正的深度克隆。
代碼如下:

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

public class MyUtil {

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

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

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

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

}
下面是測試代碼:

import java.io.Serializable;

/**

  • 人類
  • @author 駱昊

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

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

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

public String getName() {
    return name;
}

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

public int getAge() {
    return age;
}

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

public Car getCar() {
    return car;
}

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

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

}
/**

  • 小汽車類
  • @author 駱昊

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

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

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

public String getBrand() {
    return brand;
}

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

public int getMaxSpeed() {
    return maxSpeed;
}

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

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

}
class CloneTest {

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

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

15、String s = new String(“xyz”);創建了幾個字符串對象?
兩個對象,一個是靜態區的”xyz”,一個是用new創建在堆上的對象。

16、java中==和eqauls()的區別,equals()和`hashcode的區別
==是運算符,用於比較兩個變量是否相等,而equals是Object類的方法,用於比較兩個對象是否相等.默認Object類的equals方法是比較兩個對象的地址,此時和==的結果一樣.換句話說:基本類型比較用==,比較的是他們的值.默認下,對象用==比較時,比較的是內存地址,如果需要比較對象內容,需要重寫equal方法

17、a==b與a.equals(b)有什麼區別
如果a 和b 都是對象,則 a==b 是比較兩個對象的引用,只有當 a 和 b 指向的是堆中的同一個對象纔會返回 true,而 a.equals(b) 是進行邏輯比較,所以通常需要重寫該方法來提供邏輯一致性的比較。例如,String 類重寫 equals() 方法,所以可以用於兩個不同對象,但是包含的字母相同的比較。

18、接口是否可繼承(extends)接口?抽象類是否可實現(implements)接口?抽象類是否可繼承具體類(concrete class)?
接口可以繼承接口,而且支持多重繼承。抽象類可以實現(implements)接口,抽象類可繼承具體類也可以繼承抽象類。

19、Java 中的final關鍵字有哪些用法?
(1)修飾類:表示該類不能被繼承;

(2)修飾方法:表示方法不能被重寫;

(3)修飾變量:表示變量只能一次賦值以後值不能被修改(常量)。

20、throw和throws的區別
throw用於主動拋出java.lang.Throwable 類的一個實例化對象,意思是說你可以通過關鍵字 throw 拋出一個 Error 或者 一個Exception,如:throw new IllegalArgumentException(“size must be multiple of 2″)。

throws 的作用是作爲方法聲明和簽名的一部分,方法被拋出相應的異常以便調用者能處理。Java 中,任何未處理的受檢查異常強制在 throws 子句中聲明。

21、Error和Exception有什麼區別?
Error表示系統級的錯誤和程序不必處理的異常,是恢復不是不可能但很困難的情況下的一種嚴重問題;比如內存溢出,不可能指望程序能處理這樣的情況;

Exception表示需要捕捉或者需要程序進行處理的異常,是一種設計或實現問題;也就是說,它表示如果程序運行正常,從不會發生的情況。

22、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。

23、運行時異常與受檢異常有何異同?
異常表示程序運行過程中可能出現的非正常狀態,運行時異常表示虛擬機的通常操作中可能遇到的異常,是一種常見運行錯誤,只要程序設計得沒有問題通常就不會發生。受檢異常跟程序運行的上下文環境有關,即使程序設計無誤,仍然可能因使用的問題而引發。Java編譯器要求方法必須聲明拋出可能發生的受檢異常,但是並不要求必須聲明拋出未被捕獲的運行時異常。

異常和繼承一樣,是面向對象程序設計中經常被濫用的東西,在Effective Java中對異常的使用給出了以下指導原則:

不要將異常處理用於正常的控制流(設計良好的API不應該強迫它的調用者爲了正常的控制流而使用異常)
對可以恢復的情況使用受檢異常,對編程錯誤使用運行時異常
避免不必要的使用受檢異常(可以通過一些狀態檢測手段來避免異常的發生)
優先使用標準的異常
每個方法拋出的異常都要有文檔
保持異常的原子性
不要在catch中忽略掉捕獲到的異常
24、列出一些你常見的運行時異常?
ArithmeticException(算術異常)
ClassCastException (類轉換異常)
IllegalArgumentException (非法參數異常)
IndexOutOfBoundsException (下標越界異常)
NullPointerException (空指針異常)
SecurityException (安全異常)
25、闡述final、finally、finalize的區別
final:修飾符(關鍵字)有三種用法:如果一個類被聲明爲final,意味着它不能再派生出新的子類,即不能被繼承,因此它和abstract是反義詞。將變量聲明爲final,可以保證它們在使用中不被改變,被聲明爲final的變量必須在聲明時給定初值,而在以後的引用中只能讀取不可修改。被聲明爲final的方法也同樣只能使用,不能在子類中被重寫。
finally:通常放在try…catch…的後面構造總是執行代碼塊,這就意味着程序無論正常執行還是發生異常,這裏的代碼只要JVM不關閉都能執行,可以將釋放外部資源的代碼寫在finally塊中。
finalize:Object類中定義的方法,Java中允許使用finalize()方法在垃圾收集器將對象從內存中清除出去之前做必要的清理工作。這個方法是由垃圾收集器在銷燬對象時調用的,通過重寫finalize()方法可以整理系統資源或者執行其他清理工作。
26、java當中的四種引用
強引用,軟引用,弱引用,虛引用.不同的引用類型主要體現在GC上:

強引用:如果一個對象具有強引用,它就不會被垃圾回收器回收。即使當前內存空間不足,JVM也不會回收它,而是拋出 OutOfMemoryError 錯誤,使程序異常終止。如果想中斷強引用和某個對象之間的關聯,可以顯式地將引用賦值爲null,這樣一來的話,JVM在合適的時間就會回收該對象

軟引用:在使用軟引用時,如果內存的空間足夠,軟引用就能繼續被使用,而不會被垃圾回收器回收,只有在內存不足時,軟引用纔會被垃圾回收器回收。

弱引用:具有弱引用的對象擁有的生命週期更短暫。因爲當 JVM 進行垃圾回收,一旦發現弱引用對象,無論當前內存空間是否充足,都會將弱引用回收。不過由於垃圾回收器是一個優先級較低的線程,所以並不一定能迅速發現弱引用對象

虛引用:顧名思義,就是形同虛設,如果一個對象僅持有虛引用,那麼它相當於沒有引用,在任何時候都可能被垃圾回收器回收。

更多瞭解參見深入對象引用: http://blog.csdn.net/dd864140130/article/details/49885811

27、爲什麼要有不同的引用類型
不像C語言,我們可以控制內存的申請和釋放,在Java中有時候我們需要適當的控制對象被回收的時機,因此就誕生了不同的引用類型,可以說不同的引用類型實則是對GC回收時機不可控的妥協.有以下幾個使用場景可以充分的說明:

利用軟引用和弱引用解決OOM問題:用一個HashMap來保存圖片的路徑和相應圖片對象關聯的軟引用之間的映射關係,在內存不足時,JVM會自動回收這些緩存圖片對象所佔用的空間,從而有效地避免了OOM的問題.

通過軟引用實現Java對象的高速緩存:比如我們創建了一Person的類,如果每次需要查詢一個人的信息,哪怕是幾秒中之前剛剛查詢過的,都要重新構建一個實例,這將引起大量Person對象的消耗,並且由於這些對象的生命週期相對較短,會引起多次GC影響性能。此時,通過軟引用和 HashMap 的結合可以構建高速緩存,提供性能.

28、內部類的作用
內部類可以有多個實例,每個實例都有自己的狀態信息,並且與其他外圍對象的信息相互獨立.在單個外圍類當中,可以讓多個內部類以不同的方式實現同一接口,或者繼承同一個類.創建內部類對象的時刻不依賴於外部類對象的創建.內部類並沒有令人疑惑的”is-a”關係,它就像是一個獨立的實體.

內部類提供了更好的封裝,除了該外圍類,其他類都不能訪問

29、SimpleDateFormat是線程安全的嗎?
非常不幸,DateFormat 的所有實現,包括 SimpleDateFormat 都不是線程安全的,因此你不應該在多線程序中使用,除非是在對外線程安全的環境中使用,如 將 SimpleDateFormat 限制在 ThreadLocal 中。如果你不這麼做,在解析或者格式化日期的時候,可能會獲取到一個不正確的結果。因此,從日期、時間處理的所有實踐來說,我強力推薦 joda-time 庫。

30、如何格式化日期?
Java 中,可以使用 SimpleDateFormat 類或者 joda-time 庫來格式日期。DateFormat 類允許你使用多種流行的格式來格式化日期。參見答案中的示例代碼,代碼中演示了將日期格式化成不同的格式,如 dd-MM-yyyy 或 ddMMyyyy。

31、說出幾條 Java 中方法重載的最佳實踐?
下面有幾條可以遵循的方法重載的最佳實踐來避免造成自動裝箱的混亂。

不要重載這樣的方法:一個方法接收 int 參數,而另個方法接收 Integer 參數。
不要重載參數數量一致,而只是參數順序不同的方法。
如果重載的方法參數個數多於 5 個,採用可變參數。
32、說說反射的用途及實現
反射機制是Java語言中一個非常重要的特性,它允許程序在運行時進行自我檢查,同時也允許對其內部成員進行操作。

反射機制提供的功能主要有:得到一個對象所屬的類;獲取一個類的所有成員變量和方法;在運行時創建對象;在運行時調用對象的方法;

33、說說自定義註解的場景及實現
登陸、權限攔截、日誌處理,以及各種 Java 框架,如 Spring,Hibernate,JUnit 提到註解就不能不說反射,Java 自定義註解是通過運行時靠反射獲取註解。

實際開發中,例如我們要獲取某個方法的調用日誌,可以通過 AOP(動態代理機制)給方法添加切面,通過反射來獲取方法包含的註解,如果包含日誌註解,就進行日誌記錄。 反射的實現在 Java 應用層面上講,是通過對 Class 對象的操作實現的,Class 對象爲我們提供了一系列方法對類進行操作。在 JVM 這個角度來說,Class 文件是一組以 8 位字節爲基礎單位的二進制流,各個數據項目按嚴格的順序緊湊的排列在 Class 文件中,裏面包含了類、方法、字段等等相關數據。

通過對 Class 數據流的處理我們即可得到字段、方法等數據。

本人免費整理了Java高級資料,涵蓋了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高併發分佈式等教程,一共30G,需要自己領取。
傳送門:https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章