溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Java后端面試題和答案

發(fā)布時(shí)間:2020-06-03 11:14:18 來源:億速云 閱讀:307 作者:Leah 欄目:編程語言
這篇文章給大家分享的是有關(guān)Java后端面試題和答案的內(nèi)容。小編覺得挺實(shí)用的,因此分享給大家學(xué)習(xí)。如下資料是關(guān)于Java后端面試題和答案的內(nèi)容。
  1. 如何用數(shù)組實(shí)現(xiàn)隊(duì)列?

用數(shù)組實(shí)現(xiàn)隊(duì)列時(shí)要注意 溢出 現(xiàn)象,這時(shí)我們可以采用循環(huán)數(shù)組的方式來解決,即將數(shù)組收尾相接。使用front指針指向隊(duì)列首位,tail指針指向隊(duì)列末位。

  1. 內(nèi)部類訪問局部變量的時(shí)候,為什么變量必須加上final修飾?

因?yàn)樯芷诓煌?。局部變量在方法結(jié)束后就會(huì)被銷毀,但內(nèi)部類對(duì)象并不一定,這樣就會(huì)導(dǎo)致內(nèi)部類引用了一個(gè)不存在的變量。

所以編譯器會(huì)在內(nèi)部類中生成一個(gè)局部變量的拷貝,這個(gè)拷貝的生命周期和內(nèi)部類對(duì)象相同,就不會(huì)出現(xiàn)上述問題。

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

在JDK8之后,編譯器不要求內(nèi)部類訪問的局部變量必須被final修飾,但局部變量值不能被修改(無論是方法中還是內(nèi)部類中),否則會(huì)報(bào)編譯錯(cuò)誤。利用javap查看編譯后的字節(jié)碼可以發(fā)現(xiàn),編譯器已經(jīng)加上了final。

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

根據(jù)代碼的計(jì)算結(jié)果,s的值應(yīng)該是-1371654655,這是由于Java中右側(cè)值的計(jì)算默認(rèn)是int類型。

  1. NIO相關(guān),Channels、Buffers、Selectors

NIO(Non-blocking IO)為所有的原始類型提供(Buffer)緩存支持,字符集編碼解碼解決方案。Channel :一個(gè)新的原始I/O 抽象。支持鎖和內(nèi)存映射文件的文件訪問接口。提供多路(non-bloking) 非阻塞式的高伸縮性網(wǎng)絡(luò)I/O 。
Java后端面試題和答案
I

Java NIO和IO之間第一個(gè)最大的區(qū)別是,IO是面向流的,NIO是面向緩沖區(qū)的。Java IO面向流意味著每次從流中讀一個(gè)或多個(gè)字節(jié),直至讀取所有字節(jié),它們沒有被緩存在任何地方。此外,它不能前后移動(dòng)流中的數(shù)據(jù)。如果需要前后移動(dòng)從流中讀取的數(shù)據(jù),需要先將它緩存到一個(gè)緩沖區(qū)。

Java NIO的緩沖導(dǎo)向方法略有不同。數(shù)據(jù)讀取到一個(gè)它稍后處理的緩沖區(qū),需要時(shí)可在緩沖區(qū)中前后移動(dòng)。這就增加了處理過程中的靈活性。但是,還需要檢查是否該緩沖區(qū)中包含所有您需要處理的數(shù)據(jù)。而且,需確保當(dāng)更多的數(shù)據(jù)讀入緩沖區(qū)時(shí),不要覆蓋緩沖區(qū)里尚未處理的數(shù)據(jù)。

阻塞與非阻塞IO

Java IO的各種流是阻塞的。這意味著,當(dāng)一個(gè)線程調(diào)用read() 或 write()時(shí),該線程被阻塞,直到有一些數(shù)據(jù)被讀取,或數(shù)據(jù)完全寫入。該線程在此期間不能再干任何事情了。Java NIO的非阻塞模式,是線程向某通道發(fā)送請(qǐng)求讀取數(shù)據(jù),僅能得到目前可用的數(shù)據(jù),如果目前沒有數(shù)據(jù)可用時(shí),就什么都不會(huì)獲取,當(dāng)然它不會(huì)保持線程阻塞。所以直至數(shù)據(jù)變的可以讀取之前,該線程可以繼續(xù)做其他的事情。非阻塞寫也是如此。所以一個(gè)單獨(dú)的線程現(xiàn)在可以管理多個(gè)輸入和輸出通道。

選擇器(Selectors)

Java NIO 的 選擇器允許一個(gè)單獨(dú)的線程來監(jiān)視多個(gè)輸入通道,你可以注冊(cè)多個(gè)通道使用一個(gè)選擇器,然后使用一個(gè)單獨(dú)的線程來“選擇”通道:這些通道里已經(jīng)有可以處理的輸入,或者選擇已準(zhǔn)備寫入的通道。這種選擇機(jī)制,使得一個(gè)單獨(dú)的線程很容易來管理多個(gè)通道。

  1. 反射的用途

Java反射機(jī)制可以讓我們?cè)诰幾g期(Compile Time)之外的運(yùn)行期(Runtime)檢查類,接口,變量以及方法的信息。反射還可以讓我們?cè)谶\(yùn)行期實(shí)例化對(duì)象,調(diào)用方法,通過調(diào)用get/set方法獲取變量的值。同時(shí)我們也可以通過反射來獲取泛型信息,以及注解。還有更高級(jí)的應(yīng)用–動(dòng)態(tài)代理和動(dòng)態(tài)類加載(ClassLoader.loadclass())。

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

getFields:獲取所有 public 的變量。
getDeclaredFields:獲取所有包括 private , protected 權(quán)限的變量。
setAccessible:設(shè)置為 true 可以跳過Java權(quán)限檢查,從而訪問private權(quán)限的變量。
getAnnotations:獲取注解,可以用在類和方法上。
獲取方法的泛型參數(shù):

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);
        }
    }
}

動(dòng)態(tài)代理:

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

    //創(chuàng)建動(dòng)態(tài)代理對(duì)象
    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;
    //調(diào)用之前
    doBefore();
    //調(diào)用原始對(duì)象的方法
    result=method.invoke(obj, args);
    //調(diào)用之后
    doAfter();
    return result;
}

通過反射獲取方法注解的參數(shù):

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());
   }
}

非靜態(tài)內(nèi)部類能定義靜態(tài)方法嗎?

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

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

以上代碼會(huì)出現(xiàn)編譯錯(cuò)誤,因?yàn)橹挥徐o態(tài)內(nèi)部類才能定義靜態(tài)方法。

  1. Lock 和 Synchronized 有什么區(qū)別?

使用方法的區(qū)別

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

synchronized是tuo管給JVM執(zhí)行的,而lock是java寫的控制鎖的代碼。在Java1.5中,synchronize是性能低效的。因?yàn)檫@是一個(gè)重量級(jí)操作,需要調(diào)用操作接口,導(dǎo)致有可能加鎖消耗的系統(tǒng)時(shí)間比加鎖以外的操作還多。相比之下使用Java提供的Lock對(duì)象,性能更高一些。但是到了Java1.6,發(fā)生了變化。synchronize在語義上很清晰,可以進(jìn)行很多優(yōu)化,有適應(yīng)自旋,鎖消除,鎖粗化,輕量級(jí)鎖,偏向鎖等等。導(dǎo)致在Java1.6上synchronize的性能并不比Lock差。

Synchronized:采用的是CPU悲觀鎖機(jī)制,即線程獲得的是獨(dú)占鎖。獨(dú)占鎖意味著 其他線程只能依靠阻塞來等待線程釋放鎖。而在CPU轉(zhuǎn)換線程阻塞時(shí)會(huì)引起線程上下文切換,當(dāng)有很多線程競(jìng)爭(zhēng)鎖的時(shí)候,會(huì)引起CPU頻繁的上下文切換導(dǎo)致效率很低。
Lock:用的是樂觀鎖方式。所謂樂觀鎖就是,每次不加鎖而是假設(shè)沒有沖突而去完成某項(xiàng)操作,如果因?yàn)闆_突失敗就重試,直到成功為止。樂觀鎖實(shí)現(xiàn)的機(jī)制就是CAS操作。我們可以進(jìn)一步研究ReentrantLock的源代碼,會(huì)發(fā)現(xiàn)其中比較重要的獲得鎖的一個(gè)方法是compareAndSetState。這里其實(shí)就是調(diào)用的CPU提供的特殊指令。
ReentrantLock:具有更好的可伸縮性:比如時(shí)間鎖等候、可中斷鎖等候、無塊結(jié)構(gòu)鎖、多個(gè)條件變量或者鎖投票。

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

folat類型的還有double類型的,這些小數(shù)類型在趨近于0的時(shí)候直接等于0的可能性很小,一般都是無限趨近于0,因此不能用==來判斷。應(yīng)該用|x-0|<err來判斷,這里|x-0|表示絕對(duì)值,err表示限定誤差。

//用程序表示就是

fabs(x) < 0.00001f

  1. 如何新建非靜態(tài)內(nèi)部類?

內(nèi)部類在聲明的時(shí)候必須是 Outer.Inner a,就像int a 一樣,至于靜態(tài)內(nèi)部類和非靜態(tài)內(nèi)部類new的時(shí)候有點(diǎn)區(qū)別:

Outer.Inner a = new Outer().new Inner()(非靜態(tài),先有Outer對(duì)象才能 new 內(nèi)部類)
Outer.Inner a = new Outer.Inner()(靜態(tài)內(nèi)部類)

  1. Java標(biāo)識(shí)符命名規(guī)則

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

  1. 你知道哪些JDK中用到的設(shè)計(jì)模式?

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

  1. ConcurrentHashMap如何保證線程安全

JDK 1.7及以前:

ConcurrentHashMap允許多個(gè)修改操作并發(fā)進(jìn)行,其關(guān)鍵在于使用了鎖分離技術(shù)。它使用了多個(gè)鎖來控制對(duì)hash表的不同部分進(jìn)行的修改。ConcurrentHashMap內(nèi)部使用段(Segment)來表示這些不同的部分,每個(gè)段其實(shí)就是一個(gè)小的hash table,它們有自己的鎖。只要多個(gè)修改操作發(fā)生在不同的段上,它們就可以并發(fā)進(jìn)行。

JDK 1.8:

Segment雖保留,但已經(jīng)簡(jiǎn)化屬性,僅僅是為了兼容舊版本。

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

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

底層結(jié)構(gòu)存放的是TreeBin對(duì)象,而不是TreeNode對(duì)象;

CAS作為知名無鎖算法,那ConcurrentHashMap就沒用鎖了么?當(dāng)然不是,當(dāng)hash值與鏈表的頭結(jié)點(diǎn)相同還是會(huì)synchronized上鎖,鎖鏈表。

  1. i++在多線程環(huán)境下是否存在問題,怎么解決?

雖然遞增操作++i是一種緊湊的語法,使其看上去只是一個(gè)操作,但這個(gè)操作并非原子的,因而它并不會(huì)作為一個(gè)不可分割的操作來執(zhí)行。實(shí)際上,它包含了三個(gè)獨(dú)立的操作:讀取count的值,將值加1,然后將計(jì)算結(jié)果寫入count。這是一個(gè)“讀取 - 修改 - 寫入”的操作序列,并且其結(jié)果狀態(tài)依賴于之前的狀態(tài)。所以在多線程環(huán)境下存在問題。

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

  1. new與newInstance()的區(qū)別

new是一個(gè)關(guān)鍵字,它是調(diào)用new指令創(chuàng)建一個(gè)對(duì)象,然后調(diào)用構(gòu)造方法來初始化這個(gè)對(duì)象,可以使用帶參數(shù)的構(gòu)造器

newInstance()是Class的一個(gè)方法,在這個(gè)過程中,是先取了這個(gè)類的不帶參數(shù)的構(gòu)造器Constructor,然后調(diào)用構(gòu)造器的newInstance方法來創(chuàng)建對(duì)象。

Class.newInstance不能帶參數(shù),如果要帶參數(shù)需要取得對(duì)應(yīng)的構(gòu)造器,然后調(diào)用該構(gòu)造器的Constructor.newInstance(Object … initargs)方法

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

接口的默認(rèn)方法和靜態(tài)方法,JDK8允許我們給接口添加一個(gè)非抽象的方法實(shí)現(xiàn),只需要使用default關(guān)鍵字即可。也可以定義被static修飾的靜態(tài)方法。
對(duì)HashMap進(jìn)行了改進(jìn),當(dāng)單個(gè)桶的元素個(gè)數(shù)大于6時(shí)就會(huì)將實(shí)現(xiàn)改為紅黑樹實(shí)現(xiàn),以避免構(gòu)造重復(fù)的hashCode的***
多并發(fā)進(jìn)行了優(yōu)化。如ConcurrentHashMap實(shí)現(xiàn)由分段加鎖、鎖分離改為CAS實(shí)現(xiàn)。
JDK8拓寬了注解的應(yīng)用場(chǎng)景,注解幾乎可以使用在任何元素上,并且允許在同一個(gè)地方多次使用同一個(gè)注解
Lambda表達(dá)式

  1. 你用過哪些JVM參數(shù)?

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

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

重寫loadClass()方法。

  1. hashCode() && equals()

hashcode() 返回該對(duì)象的哈希碼值,支持該方法是為哈希表提供一些優(yōu)點(diǎn),例如,java.util.Hashtable 提供的哈希表。

在 Java 應(yīng)用程序執(zhí)行期間,在同一對(duì)象上多次調(diào)用 hashCode 方法時(shí),必須一致地返回相同的整數(shù),前提是對(duì)象上 equals 比較中所用的信息沒有被修改(equals默認(rèn)返回對(duì)象地址是否相等)。如果根據(jù) equals(Object)方法,兩個(gè)對(duì)象是相等的,那么在兩個(gè)對(duì)象中的每個(gè)對(duì)象上調(diào)用 hashCode 方法都必須生成相同的整數(shù)結(jié)果。

以下情況不是必需的:如果根據(jù) equals(java.lang.Object) 方法,兩個(gè)對(duì)象不相等,那么在兩個(gè)對(duì)象中的任一對(duì)象上調(diào)用 hashCode 方法必定會(huì)生成不同的整數(shù)結(jié)果。但是,程序員應(yīng)該知道,為不相等的對(duì)象生成不同整數(shù)結(jié)果可以提高哈希表的性能。

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

hashCode的存在主要是用于查找的快捷性,如 Hashtable,HashMap等,hashCode 是用來在散列存儲(chǔ)結(jié)構(gòu)中確定對(duì)象的存儲(chǔ)地址的;

如果兩個(gè)對(duì)象相同,就是適用于 equals(java.lang.Object) 方法,那么這兩個(gè)對(duì)象的 hashCode 一定要相同;

如果對(duì)象的 equals 方法被重寫,那么對(duì)象的 hashCode 也盡量重寫,并且產(chǎn)生 hashCode 使用的對(duì)象,一定要和 equals 方法中使用的一致,否則就會(huì)違反上面提到的第2點(diǎn);

兩個(gè)對(duì)象的hashCode相同,并不一定表示兩個(gè)對(duì)象就相同,也就是不一定適用于equals(java.lang.Object) 方法,只能夠說明這兩個(gè)對(duì)象在散列存儲(chǔ)結(jié)構(gòu)中,如Hashtable,他們“存放在同一個(gè)籃子里”。

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

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

sleep()使當(dāng)前線程進(jìn)入停滯狀態(tài),所以執(zhí)行sleep()的線程在指定的時(shí)間內(nèi)肯定不會(huì)執(zhí)行;yield()只是使當(dāng)前線程重新回到可執(zhí)行狀態(tài),所以執(zhí)行yield()的線程有可能在進(jìn)入到可執(zhí)行狀態(tài)后馬上又被執(zhí)行。

sleep()可使優(yōu)先級(jí)低的線程得到執(zhí)行的機(jī)會(huì),當(dāng)然也可以讓同優(yōu)先級(jí)和高優(yōu)先級(jí)的線程有執(zhí)行的機(jī)會(huì);yield()只能使同優(yōu)先級(jí)的線程有執(zhí)行的機(jī)會(huì)。

  1. #{}和${}的區(qū)別是什么?

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

Mybatis在處理#{}時(shí),會(huì)將sql中的#{}替換為?號(hào),調(diào)用PreparedStatement的set方法來賦值;
Mybatis在處理{}替換成變量的值。
使用#{}可以有效的防止SQL注入,提高系統(tǒng)安全性。

  1. 通常一個(gè)Xml映射文件,都會(huì)寫一個(gè)Dao接口與之對(duì)應(yīng),請(qǐng)問,這個(gè)Dao接口的工作原理是什么?Dao接口里的方法,參數(shù)不同時(shí),方法能重載嗎?

Dao接口,就是人們常說的Mapper接口,接口的全限名,就是映射文件中的namespace的值,接口的方法名,就是映射文件中MappedStatement的id值,接口方法內(nèi)的參數(shù),就是傳遞給sql的參數(shù)。Mapper接口是沒有實(shí)現(xiàn)類的,當(dāng)調(diào)用接口方法時(shí),接口全限名+方法名拼接字符串作為key值,可唯一定位一個(gè)MappedStatement,舉例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到namespace為com.mybatis3.mappers.StudentDao下面id = findStudentById的MappedStatement。在Mybatis中,每一個(gè)<select>、<insert>、<update>、<delete>標(biāo)簽,都會(huì)被解析為一個(gè)MappedStatement對(duì)象。

Dao接口里的方法,是不能重載的,因?yàn)槭侨廾?方法名的保存和尋找策略。

Dao接口的工作原理是JDK動(dòng)態(tài)代理,Mybatis運(yùn)行時(shí)會(huì)使用JDK動(dòng)態(tài)代理為Dao接口生成代理proxy對(duì)象,代理對(duì)象proxy會(huì)攔截接口方法,轉(zhuǎn)而執(zhí)行MappedStatement所代表的sql,然后將sql執(zhí)行結(jié)果返回。

關(guān)于Java后端面試題和答案就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果喜歡這篇文章,不如把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI