精選21道Java後端面試題,看完你也能唬住面試官拿30K

  1. 如何用數組實現隊列?

用數組實現隊列時要注意 溢出 現象,這時我們可以採用循環數組的方式來解決,即將數組收尾相接。使用front指針指向隊列首位,tail指針指向隊列末位。

  1. 內部類訪問局部變量的時候,爲什麼變量必須加上final修飾?

因爲生命週期不同。局部變量在方法結束後就會被銷燬,但內部類對象並不一定,這樣就會導致內部類引用了一個不存在的變量。

所以編譯器會在內部類中生成一個局部變量的拷貝,這個拷貝的生命週期和內部類對象相同,就不會出現上述問題。

但這樣就導致了其中一個變量被修改,兩個變量值可能不同的問題。爲了解決這個問題,編譯器就要求局部變量需要被final修飾,以保證兩個變量值相同。

在JDK8之後,編譯器不要求內部類訪問的局部變量必須被final修飾,但局部變量值不能被修改(無論是方法中還是內部類中),否則會報編譯錯誤。利用javap查看編譯後的字節碼可以發現,編譯器已經加上了final。

  1. long s = 499999999 * 499999999 在上面的代碼中,s的值是多少?

根據代碼的計算結果,s的值應該是-1371654655,這是由於Java中右側值的計算默認是int類型。

  1. NIO相關,Channels、Buffers、Selectors

NIO(Non-blocking IO)爲所有的原始類型提供(Buffer)緩存支持,字符集編碼解碼解決方案。Channel :一個新的原始I/O 抽象。支持鎖和內存映射文件的文件訪問接口。提供多路(non-bloking) 非阻塞式的高伸縮性網絡I/O 。
精選21道Java後端面試題,看完你也能唬住面試官拿30K
I

Java NIO和IO之間第一個最大的區別是,IO是面向流的,NIO是面向緩衝區的。Java IO面向流意味着每次從流中讀一個或多個字節,直至讀取所有字節,它們沒有被緩存在任何地方。此外,它不能前後移動流中的數據。如果需要前後移動從流中讀取的數據,需要先將它緩存到一個緩衝區。

Java NIO的緩衝導向方法略有不同。數據讀取到一個它稍後處理的緩衝區,需要時可在緩衝區中前後移動。這就增加了處理過程中的靈活性。但是,還需要檢查是否該緩衝區中包含所有您需要處理的數據。而且,需確保當更多的數據讀入緩衝區時,不要覆蓋緩衝區裏尚未處理的數據。

阻塞與非阻塞IO

Java IO的各種流是阻塞的。這意味着,當一個線程調用read() 或 write()時,該線程被阻塞,直到有一些數據被讀取,或數據完全寫入。該線程在此期間不能再幹任何事情了。Java NIO的非阻塞模式,是線程向某通道發送請求讀取數據,僅能得到目前可用的數據,如果目前沒有數據可用時,就什麼都不會獲取,當然它不會保持線程阻塞。所以直至數據變的可以讀取之前,該線程可以繼續做其他的事情。非阻塞寫也是如此。所以一個單獨的線程現在可以管理多個輸入和輸出通道。

選擇器(Selectors)

Java NIO 的 選擇器允許一個單獨的線程來監視多個輸入通道,你可以註冊多個通道使用一個選擇器,然後使用一個單獨的線程來“選擇”通道:這些通道里已經有可以處理的輸入,或者選擇已準備寫入的通道。這種選擇機制,使得一個單獨的線程很容易來管理多個通道。

  1. 反射的用途

Java反射機制可以讓我們在編譯期(Compile Time)之外的運行期(Runtime)檢查類,接口,變量以及方法的信息。反射還可以讓我們在運行期實例化對象,調用方法,通過調用get/set方法獲取變量的值。同時我們也可以通過反射來獲取泛型信息,以及註解。還有更高級的應用–動態代理和動態類加載(ClassLoader.loadclass())。

下面列舉一些比較重要的方法:

getFields:獲取所有 public 的變量。
getDeclaredFields:獲取所有包括 private , protected 權限的變量。
setAccessible:設置爲 true 可以跳過Java權限檢查,從而訪問private權限的變量。
getAnnotations:獲取註解,可以用在類和方法上。
獲取方法的泛型參數:

method = Myclass.class.getMethod("setStringList", List.class);

Type[] genericParameterTypes = method.getGenericParameterTypes();

for(Type genericParameterType : genericParameterTypes){
    if(genericParameterType instanceof ParameterizedType){
        ParameterizedType aType = (ParameterizedType) genericParameterType;
        Type[] parameterArgTypes = aType.getActualTypeArguments();
        for(Type parameterArgType : parameterArgTypes){
            Class parameterArgClass = (Class) parameterArgType;
            System.out.println("parameterArgClass = " + parameterArgClass);
        }
    }
}

動態代理:

//Main.java
public static void main(String[] args) {
    HelloWorld helloWorld=new HelloWorldImpl();
    InvocationHandler handler=new HelloWorldHandler(helloWorld);

    //創建動態代理對象
    HelloWorld proxy=(HelloWorld)Proxy.newProxyInstance(
            helloWorld.getClass().getClassLoader(),
            helloWorld.getClass().getInterfaces(),
            handler);
    proxy.sayHelloWorld();
}

//HelloWorldHandler.java
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    Object result = null;
    //調用之前
    doBefore();
    //調用原始對象的方法
    result=method.invoke(obj, args);
    //調用之後
    doAfter();
    return result;
}

通過反射獲取方法註解的參數:

Class aClass = TheClass.class;
Annotation[] annotations = aClass.getAnnotations();

for(Annotation annotation : annotations){
   if(annotation instanceof MyAnnotation){
       MyAnnotation myAnnotation = (MyAnnotation) annotation;
       System.out.println("name: " + myAnnotation.name());
       System.out.println("value: " + myAnnotation.value());
   }
}

非靜態內部類能定義靜態方法嗎?

public class OuterClass{
    private static float f = 1.0f;

    class InnerClass{
        public static float func(){return f;}
    }
}

以上代碼會出現編譯錯誤,因爲只有靜態內部類才能定義靜態方法。

  1. Lock 和 Synchronized 有什麼區別?

使用方法的區別

Synchronized:在需要同步的對象中加入此控制,synchronized可以加在方法上,也可以加在特定代碼塊中,括號中表示需要鎖的對象。
Lock:需要顯示指定起始位置和終止位置。一般使用ReentrantLock類做爲鎖,多個線程中必須要使用一個ReentrantLock類做爲對象才能保證鎖的生效。且在加鎖和解鎖處需要通過lock()和unlock()顯示指出。所以一般會在finally塊中寫unlock()以防死鎖。
性能的區別

synchronized是tuo管給JVM執行的,而lock是java寫的控制鎖的代碼。在Java1.5中,synchronize是性能低效的。因爲這是一個重量級操作,需要調用操作接口,導致有可能加鎖消耗的系統時間比加鎖以外的操作還多。相比之下使用Java提供的Lock對象,性能更高一些。但是到了Java1.6,發生了變化。synchronize在語義上很清晰,可以進行很多優化,有適應自旋,鎖消除,鎖粗化,輕量級鎖,偏向鎖等等。導致在Java1.6上synchronize的性能並不比Lock差。

Synchronized:採用的是CPU悲觀鎖機制,即線程獲得的是獨佔鎖。獨佔鎖意味着 其他線程只能依靠阻塞來等待線程釋放鎖。而在CPU轉換線程阻塞時會引起線程上下文切換,當有很多線程競爭鎖的時候,會引起CPU頻繁的上下文切換導致效率很低。
Lock:用的是樂觀鎖方式。所謂樂觀鎖就是,每次不加鎖而是假設沒有衝突而去完成某項操作,如果因爲衝突失敗就重試,直到成功爲止。樂觀鎖實現的機制就是CAS操作。我們可以進一步研究ReentrantLock的源代碼,會發現其中比較重要的獲得鎖的一個方法是compareAndSetState。這裏其實就是調用的CPU提供的特殊指令。
ReentrantLock:具有更好的可伸縮性:比如時間鎖等候、可中斷鎖等候、無塊結構鎖、多個條件變量或者鎖投票。

  1. float 變量如何與 0 比較?

folat類型的還有double類型的,這些小數類型在趨近於0的時候直接等於0的可能性很小,一般都是無限趨近於0,因此不能用==來判斷。應該用|x-0|<err來判斷,這裏|x-0|表示絕對值,err表示限定誤差。

//用程序表示就是

fabs(x) < 0.00001f

  1. 如何新建非靜態內部類?

內部類在聲明的時候必須是 Outer.Inner a,就像int a 一樣,至於靜態內部類和非靜態內部類new的時候有點區別:

Outer.Inner a = new Outer().new Inner()(非靜態,先有Outer對象才能 new 內部類)
Outer.Inner a = new Outer.Inner()(靜態內部類)

  1. Java標識符命名規則

可以包含:字母、數字、$、_(下劃線),不可用數字開頭,不能是 Java 的關鍵字和保留字。

  1. 你知道哪些JDK中用到的設計模式?

裝飾模式:java.io
單例模式:Runtime類
簡單工廠模式:Integer.valueOf方法
享元模式:String常量池、Integer.valueOf(int i)、Character.valueOf(char c)
迭代器模式:Iterator
職責鏈模式:ClassLoader的雙親委派模型
解釋器模式:正則表達式java.util.regex.Pattern

  1. ConcurrentHashMap如何保證線程安全

JDK 1.7及以前:

ConcurrentHashMap允許多個修改操作併發進行,其關鍵在於使用了鎖分離技術。它使用了多個鎖來控制對hash表的不同部分進行的修改。ConcurrentHashMap內部使用段(Segment)來表示這些不同的部分,每個段其實就是一個小的hash table,它們有自己的鎖。只要多個修改操作發生在不同的段上,它們就可以併發進行。

JDK 1.8:

Segment雖保留,但已經簡化屬性,僅僅是爲了兼容舊版本。

插入時使用CAS算法:unsafe.compareAndSwapInt(this, valueOffset, expect, update)。CAS(Compare And Swap)意思是如果valueOffset位置包含的值與expect值相同,則更新valueOffset位置的值爲update,並返回true,否則不更新,返回false。插入時不允許key或value爲null

與Java8的HashMap有相通之處,底層依然由“數組”+鏈表+紅黑樹;

底層結構存放的是TreeBin對象,而不是TreeNode對象;

CAS作爲知名無鎖算法,那ConcurrentHashMap就沒用鎖了麼?當然不是,當hash值與鏈表的頭結點相同還是會synchronized上鎖,鎖鏈表。

  1. i++在多線程環境下是否存在問題,怎麼解決?

雖然遞增操作++i是一種緊湊的語法,使其看上去只是一個操作,但這個操作並非原子的,因而它並不會作爲一個不可分割的操作來執行。實際上,它包含了三個獨立的操作:讀取count的值,將值加1,然後將計算結果寫入count。這是一個“讀取 - 修改 - 寫入”的操作序列,並且其結果狀態依賴於之前的狀態。所以在多線程環境下存在問題。

要解決自增操作在多線程環境下線程不安全的問題,可以選擇使用Java提供的原子類,如AtomicInteger或者使用synchronized同步方法。

  1. new與newInstance()的區別

new是一個關鍵字,它是調用new指令創建一個對象,然後調用構造方法來初始化這個對象,可以使用帶參數的構造器

newInstance()是Class的一個方法,在這個過程中,是先取了這個類的不帶參數的構造器Constructor,然後調用構造器的newInstance方法來創建對象。

Class.newInstance不能帶參數,如果要帶參數需要取得對應的構造器,然後調用該構造器的Constructor.newInstance(Object … initargs)方法

  1. 你瞭解哪些JDK1.8的新特性?

接口的默認方法和靜態方法,JDK8允許我們給接口添加一個非抽象的方法實現,只需要使用default關鍵字即可。也可以定義被static修飾的靜態方法。
對HashMap進行了改進,當單個桶的元素個數大於6時就會將實現改爲紅黑樹實現,以避免構造重複的hashCode的***
多併發進行了優化。如ConcurrentHashMap實現由分段加鎖、鎖分離改爲CAS實現。
JDK8拓寬了註解的應用場景,註解幾乎可以使用在任何元素上,並且允許在同一個地方多次使用同一個註解
Lambda表達式

  1. 你用過哪些JVM參數?

Xms 堆最小值
Xmx 堆最大值
Xmn: 新生代容量
XX:SurvivorRatio 新生代中Eden與Surivor空間比例
Xss 棧容量
XX:PermSize 方法區初始容量
XX:MaxPermSize 方法區最大容量
XX:+PrintGCDetails 收集器日誌參數

  1. 如何打破 ClassLoader 雙親委託?

重寫loadClass()方法。

  1. hashCode() && equals()

hashcode() 返回該對象的哈希碼值,支持該方法是爲哈希表提供一些優點,例如,java.util.Hashtable 提供的哈希表。

在 Java 應用程序執行期間,在同一對象上多次調用 hashCode 方法時,必須一致地返回相同的整數,前提是對象上 equals 比較中所用的信息沒有被修改(equals默認返回對象地址是否相等)。如果根據 equals(Object)方法,兩個對象是相等的,那麼在兩個對象中的每個對象上調用 hashCode 方法都必須生成相同的整數結果。

以下情況不是必需的:如果根據 equals(java.lang.Object) 方法,兩個對象不相等,那麼在兩個對象中的任一對象上調用 hashCode 方法必定會生成不同的整數結果。但是,程序員應該知道,爲不相等的對象生成不同整數結果可以提高哈希表的性能。

實際上,由 Object 類定義的 hashCode 方法確實會針對不同的對象返回不同的整數。(這一般是通過將該對象的內部地址轉換成一個整數來實現的,但是 JavaTM 編程語言不需要這種實現技巧I。)

hashCode的存在主要是用於查找的快捷性,如 Hashtable,HashMap等,hashCode 是用來在散列存儲結構中確定對象的存儲地址的;

如果兩個對象相同,就是適用於 equals(java.lang.Object) 方法,那麼這兩個對象的 hashCode 一定要相同;

如果對象的 equals 方法被重寫,那麼對象的 hashCode 也儘量重寫,並且產生 hashCode 使用的對象,一定要和 equals 方法中使用的一致,否則就會違反上面提到的第2點;

兩個對象的hashCode相同,並不一定表示兩個對象就相同,也就是不一定適用於equals(java.lang.Object) 方法,只能夠說明這兩個對象在散列存儲結構中,如Hashtable,他們“存放在同一個籃子裏”。

  1. Thread.sleep() & Thread.yield()

sleep()和yield()都會釋放CPU。

sleep()使當前線程進入停滯狀態,所以執行sleep()的線程在指定的時間內肯定不會執行;yield()只是使當前線程重新回到可執行狀態,所以執行yield()的線程有可能在進入到可執行狀態後馬上又被執行。

sleep()可使優先級低的線程得到執行的機會,當然也可以讓同優先級和高優先級的線程有執行的機會;yield()只能使同優先級的線程有執行的機會。

  1. #{}和${}的區別是什麼?

{}是預編譯處理,${}是字符串替換。

Mybatis在處理#{}時,會將sql中的#{}替換爲?號,調用PreparedStatement的set方法來賦值;
Mybatis在處理{}替換成變量的值。
使用#{}可以有效的防止SQL注入,提高系統安全性。

  1. 通常一個Xml映射文件,都會寫一個Dao接口與之對應,請問,這個Dao接口的工作原理是什麼?Dao接口裏的方法,參數不同時,方法能重載嗎?

Dao接口,就是人們常說的Mapper接口,接口的全限名,就是映射文件中的namespace的值,接口的方法名,就是映射文件中MappedStatement的id值,接口方法內的參數,就是傳遞給sql的參數。Mapper接口是沒有實現類的,當調用接口方法時,接口全限名+方法名拼接字符串作爲key值,可唯一定位一個MappedStatement,舉例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到namespace爲com.mybatis3.mappers.StudentDao下面id = findStudentById的MappedStatement。在Mybatis中,每一個<select>、<insert>、<update>、<delete>標籤,都會被解析爲一個MappedStatement對象。

Dao接口裏的方法,是不能重載的,因爲是全限名+方法名的保存和尋找策略。

Dao接口的工作原理是JDK動態代理,Mybatis運行時會使用JDK動態代理爲Dao接口生成代理proxy對象,代理對象proxy會攔截接口方法,轉而執行MappedStatement所代表的sql,然後將sql執行結果返回。

面試資料獲取方式:https://shimo.im/docs/TC9Jq63Tp6HvTXdg

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