Java面試題總結(基礎面試題完結版,2020-10-13)

素小暖發表於2020-10-14

一、迭代器 Iterator 是什麼?

為了方便的處理集合中的元素,Java中出現了一個物件,該物件提供了一些方法專門處理集合中的元素.例如刪除和獲取集合中的元素.該物件就叫做迭代器(Iterator)。

二、Iterator 怎麼使用?有什麼特點?

Iterator 介面原始碼中的方法:

  1. java.lang.Iterable 介面被 java.util.Collection 介面繼承,java.util.Collection 介面的 iterator() 方法返回一個 Iterator 物件
  2. next() 方法獲得集合中的下一個元素
  3. hasNext() 檢查集合中是否還有元素
  4. remove() 方法將迭代器新返回的元素刪除

三、Iterator 和 ListIterator 有什麼區別?

1、ListIterator 繼承 Iterator

2、ListIterator 比 Iterator多方法

  • add(E e)  將指定的元素插入列表,插入位置為迭代器當前位置之前
  • set(E e)  迭代器返回的最後一個元素替換引數e
  • hasPrevious()  迭代器當前位置,反向遍歷集合是否含有元素
  • previous()  迭代器當前位置,反向遍歷集合,下一個元素
  • previousIndex()  迭代器當前位置,反向遍歷集合,返回下一個元素的下標
  • nextIndex()  迭代器當前位置,返回下一個元素的下標

3、使用範圍不同,Iterator可以迭代所有集合;ListIterator 只能用於List及其子類

  • ListIterator 有 add 方法,可以向 List 中新增物件;Iterator 不能
  • ListIterator 有 hasPrevious() 和 previous() 方法,可以實現逆向遍歷;Iterator不可以
  • ListIterator 有 nextIndex() 和previousIndex() 方法,可定位當前索引的位置;Iterator不可以
  • ListIterator 有 set()方法,可以實現對 List 的修改;Iterator 僅能遍歷,不能修改。

四、怎麼確保一個集合不能被修改?

我們很容易想到用final關鍵字進行修飾,我們都知道

final關鍵字可以修飾類,方法,成員變數,final修飾的類不能被繼承,final修飾的方法不能被重寫,final修飾的成員變數必須初始化值,如果這個成員變數是基本資料型別,表示這個變數的值是不可改變的,如果說這個成員變數是引用型別,則表示這個引用的地址值是不能改變的,但是這個引用所指向的物件裡面的內容還是可以改變的

那麼,我們怎麼確保一個集合不能被修改?首先我們要清楚,集合(map,set,list…)都是引用型別,所以我們如果用final修飾的話,集合裡面的內容還是可以修改的。

我們可以做一個實驗:

可以看到:我們用final關鍵字定義了一個map集合,這時候我們往集合裡面傳值,第一個鍵值對1,1;我們再修改後,可以把鍵為1的值改為100,說明我們是可以修改map集合的值的。

那我們應該怎麼做才能確保集合不被修改呢?
我們可以採用Collections包下的unmodifiableMap方法,通過這個方法返回的map,是不可以修改的。他會報 java.lang.UnsupportedOperationException錯。

同理:Collections包也提供了對list和set集合的方法。
Collections.unmodifiableList(List)
Collections.unmodifiableSet(Set)

五、佇列和棧是什麼?有什麼區別?

1、佇列先進先出,棧先進後出。

2、遍歷資料速度不同。

棧只能從頭部取資料 也就最先放入的需要遍歷整個棧最後才能取出來,而且在遍歷資料的時候還得為資料開闢臨時空間,保持資料在遍歷前的一致性;

佇列則不同,他基於地址指標進行遍歷,而且可以從頭或尾部開始遍歷,但不能同時遍歷,無需開闢臨時空間,因為在遍歷的過程中不影像資料結構,速度要快的多

六、什麼是 Java 的記憶體模型?

在瞭解什麼是 Java 記憶體模型之前,先了解一下為什麼要提出 Java 記憶體模型。

之前提到過併發程式設計有三大問題

  • CPU 快取,在多核 CPU 的情況下,帶來了可見性問題
  • 作業系統對當前執行執行緒的切換,帶來了原子性問題
  • 譯器指令重排優化,帶來了有序性問題

為了解決併發程式設計的三大問題,提出了 JSR-133,新的 Java 記憶體模型,JDK 5 開始使用。

簡單總結下

  • Java 記憶體模型是 JVM 的一種規範
  • 定義了共享記憶體在多執行緒程式中讀寫操作行為的規範
  • 遮蔽了各種硬體和作業系統的訪問差異,保證了 Java 程式在各種平臺下對記憶體的訪問效果一致
  • 解決併發問題採用的方式:限制處理器優化和使用記憶體屏障
  • 增強了三個同步原語(synchronized、volatile、final)的記憶體語義
  • 定義了 happens-before 規則

七、既然 volatile 能夠保證執行緒間的變數可見性,是不是就意味著基於 volatile 變數的運算就是併發安全的?

volatile修飾的變數在各個執行緒的工作記憶體中不存在一致性的問題(在各個執行緒工作的記憶體中,volatile修飾的變數也會存在不一致的情況,但是由於每次使用之前都會先重新整理主存中的資料到工作記憶體,執行引擎看不到不一致的情況,因此可以認為不存在不一致的問題),但是java的運算並非原子性的操作,導致volatile在併發下並非是執行緒安全的。

八、請談談 ThreadLocal 是怎麼解決併發安全的?

在java程式中,常用的有兩種機制來解決多執行緒併發問題,一種是sychronized方式,通過鎖機制,一個執行緒執行時,讓另一個執行緒等待,是以時間換空間的方式來讓多執行緒序列執行。而另外一種方式就是ThreadLocal方式,通過建立執行緒區域性變數,以空間換時間的方式來讓多執行緒並行執行。兩種方式各有優劣,適用於不同的場景,要根據不同的業務場景來進行選擇。

    在spring的原始碼中,就使用了ThreadLocal來管理連線,在很多開源專案中,都經常使用ThreadLocal來控制多執行緒併發問題,因為它足夠的簡單,我們不需要關心是否有執行緒安全問題,因為變數是每個執行緒所特有的。

九、很多人都說要慎用 ThreadLocal,談談你的理解,使用 ThreadLocal 需要注意些什麼?

ThreadLocal 變數解決了多執行緒環境下單個執行緒中變數的共享問題,使用名為ThreadLocalMap的雜湊表進行維護(key為ThreadLocal變數名,value為ThreadLocal變數的值);

使用時需要注意以下幾點:

  • 執行緒之間的threadLocal變數是互不影響的,
  • 使用private final static進行修飾,防止多例項時記憶體的洩露問題
  • 執行緒池環境下使用後將threadLocal變數remove掉或設定成一個初始值

十、什麼是 XSS 攻擊,如何避免?

xss(Cross Site Scripting),即跨站指令碼攻擊,是一種常見於web應用程式中的電腦保安漏洞。指的是在使用者瀏覽器上,在渲染DOM樹的時候,執行了不可預期的JS指令碼,從而發生了安全問題。

XSS就是通過在使用者端注入惡意的可執行指令碼,若服務端對使用者的輸入不進行處理,直接將使用者的輸入輸出到瀏覽器,然後瀏覽器將會執行使用者注入的指令碼。 所以XSS攻擊的核心就是瀏覽器渲染DOM的時候將文字資訊解析成JS指令碼從而引發JS指令碼注入,那麼XSS攻擊的防禦手段就是基於瀏覽器渲染這一步去做防禦。只要我們使用HTML編碼將瀏覽器需要渲染的資訊編碼後,瀏覽器在渲染DOM元素的時候,會自動解碼需要渲染的資訊,將上述資訊解析成字串而不是JS指令碼,這就是我們防禦XSS攻擊的核心想法。

預防:
1、獲取使用者的輸入,不用innerHtml,用innerText.
2、對使用者的輸入進行過濾,如對& < > " ' /等進行轉義;

十一、什麼是 CSRF 攻擊,如何避免?

跨站請求偽造(英語:Cross-site request forgery),也被稱為 one-click attack 或者 session riding,通常縮寫為 CSRF 或者 XSRF, 是一種挾制使用者在當前已登入的Web應用程式上執行非本意的操作的攻擊方法。跟跨網站指令碼(XSS)相比,XSS 利用的是使用者對指定網站的信任,CSRF 利用的是網站對使用者網頁瀏覽器的信任。

1、攻擊細節

跨站請求攻擊,簡單地說,是攻擊者通過一些技術手段欺騙使用者的瀏覽器去訪問一個自己曾經認證過的網站並執行一些操作(如發郵件,發訊息,甚至財產操作如轉賬和購買商品)。由於瀏覽器曾經認證過,所以被訪問的網站會認為是真正的使用者操作而去執行。這利用了web中使用者身份驗證的一個漏洞:簡單的身份驗證只能保證請求發自某個使用者的瀏覽器,卻不能保證請求本身是使用者自願發出的。

例子

假如一家銀行用以執行轉賬操作的URL地址如下:http://www.examplebank.com/withdraw?account=AccoutName&amount=1000&for=PayeeName

那麼,一個惡意攻擊者可以在另一個網站上放置如下程式碼: <img src="http://www.examplebank.com/withdraw?account=Alice&amount=1000&for=Badman">

如果有賬戶名為Alice的使用者訪問了惡意站點,而她之前剛訪問過銀行不久,登入資訊尚未過期,那麼她就會損失1000資金。

這種惡意的網址可以有很多種形式,藏身於網頁中的許多地方。此外,攻擊者也不需要控制放置惡意網址的網站。例如他可以將這種地址藏在論壇,部落格等任何使用者生成資訊的網站中。這意味著如果服務端沒有合適的防禦措施的話,使用者即使訪問熟悉的可信網站也有受攻擊的危險。

透過例子能夠看出,攻擊者並不能通過CSRF攻擊來直接獲取使用者的賬戶控制權,也不能直接竊取使用者的任何資訊。他們能做到的,是欺騙使用者瀏覽器,讓其以使用者的名義執行操作。

2、防禦措施

檢查Referer欄位

HTTP頭中有一個Referer欄位,這個欄位用以標明請求來源於哪個地址。在處理敏感資料請求時,通常來說,Referer欄位應和請求的地址位於同一域名下。以上文銀行操作為例,Referer欄位地址通常應該是轉賬按鈕所在的網頁地址,應該也位於www.examplebank.com之下。而如果是CSRF攻擊傳來的請求,Referer欄位會是包含惡意網址的地址,不會位於www.examplebank.com之下,這時候伺服器就能識別出惡意的訪問。

這種辦法簡單易行,工作量低,僅需要在關鍵訪問處增加一步校驗。但這種辦法也有其侷限性,因其完全依賴瀏覽器傳送正確的Referer欄位。雖然http協議對此欄位的內容有明確的規定,但並無法保證來訪的瀏覽器的具體實現,亦無法保證瀏覽器沒有安全漏洞影響到此欄位。並且也存在攻擊者攻擊某些瀏覽器,篡改其Referer欄位的可能。

3、新增校驗token

由於CSRF的本質在於攻擊者欺騙使用者去訪問自己設定的地址,所以如果要求在訪問敏感資料請求時,要求使用者瀏覽器提供不儲存在cookie中,並且攻擊者無法偽造的資料作為校驗,那麼攻擊者就無法再執行CSRF攻擊。這種資料通常是窗體中的一個資料項。伺服器將其生成並附加在窗體中,其內容是一個偽隨機數。當客戶端通過窗體提交請求時,這個偽隨機數也一併提交上去以供校驗。正常的訪問時,客戶端瀏覽器能夠正確得到並傳回這個偽隨機數,而通過CSRF傳來的欺騙性攻擊中,攻擊者無從事先得知這個偽隨機數的值,服務端就會因為校驗token的值為空或者錯誤,拒絕這個可疑請求。

十二、如何實現跨域?說一下 JSONP 實現原理?

1、jsonp原理詳解——終於搞清楚jsonp是啥了

2、最流行的跨域方案cors

cors是目前主流的跨域解決方案,跨域資源共享(CORS) 是一種機制,它使用額外的 HTTP 頭來告訴瀏覽器 讓執行在一個 origin (domain) 上的Web應用被准許訪問來自不同源伺服器上的指定的資源。當一個資源從與該資源本身所在的伺服器不同的域、協議或埠請求一個資源時,資源會發起一個跨域 HTTP 請求。

3、最方便的跨域方案Nginx

nginx是一款極其強大的web伺服器,其優點就是輕量級、啟動快、高併發。

現在的新專案中nginx幾乎是首選,我們用node或者java開發的服務通常都需要經過nginx的反向代理。

反向代理的原理很簡單,即所有客戶端的請求都必須先經過nginx的處理,nginx作為代理伺服器再講請求轉發給node或者java服務,這樣就規避了同源策略。

十三、在 Java 中,什麼時候用過載,什麼時候用重寫?

1、過載是多型的集中體現,在類中,要以統一的方式處理不同型別資料的時候,可以用過載。

2、重寫的使用是建立在繼承關係上的,子類在繼承父類的基礎上,增加新的功能,可以用重寫。

3、簡單總結:
過載是多樣性,重寫是增強劑;
目的是提高程式的多樣性和健壯性,以適配不同場景使用時,使用過載進行擴充套件;
目的是在不修改原方法及原始碼的基礎上對方法進行擴充套件或增強時,使用重寫;
生活例子:
你想吃一碗麵,我給你提供了拉麵,炒麵,刀削麵,擔擔麵供你選擇,這是過載;
你想吃一碗麵,我不但給你端來了面,還給你加了青菜,加了雞蛋,這個是重寫;
設計模式:
cglib實現動態代理,核心原理用的就是方法的重寫;
詳細解答:
 java的過載(overload) 最重要的應用場景就是構造器的過載,構造器過載後,提供多種形參形式的構造器,可以應對不同的業務需求,加強程式的健壯性和可擴充套件性,比如我們最近學習的Spring原始碼中的ClassPathXmlApplicationContext,它的建構函式使用過載一共提供了10個建構函式,這樣就為業務的選擇提供了多選擇性。在應用到方法中時,主要是為了增強方法的健壯性和可擴充套件性,比如我們在開發中常用的各種工具類,比如我目前工作中的簡訊工具類SMSUtil, 發簡訊的方法就會使用過載,針對不同業務場景下的不同形參,提供簡訊傳送方法,這樣提高了工具類的擴充套件性和健壯性。
總結:過載必須要修改方法(構造器)的形參列表,可以修改方法的返回值型別,也可以修改方法的異常資訊即訪問許可權;使用範圍是在同一個類中,目的是對方法(構造器)進行功能擴充套件,以應對多業務場景的不同使用需求。提高程式的健壯性和擴充套件性。
 java的重寫(override) 只要用於子類對父類方法的擴充套件或修改,但是在我們開發中,為了避免程式混亂,重寫一般都是為了方法的擴充套件,比如在cglib方式實現的動態代理中,代理類就是繼承了目標類,對目標類的方法進行重寫,同時在方法前後進行切面織入。
總結:方法重寫時,引數列表,返回值得型別是一定不能修改的,異常可以減少或者刪除,但是不能丟擲新的異常或者更廣的異常,方法的訪問許可權可以降低限制,但是不能做更嚴格的限制。

4、在里氏替換原則中,子類對父類的方法儘量不要重寫和過載。(我們可以採用final的手段強制來遵循)

十四、舉例說明什麼情況下會更傾向於使用抽象類而不是介面?

介面和抽象類都遵循”面向介面而不是實現編碼”設計原則,它可以增加程式碼的靈活性,可以適應不斷變化的需求。下面有幾個點可以幫助你回答這個問題:在 Java 中,你只能繼承一個類,但可以實現多個介面。所以一旦你繼承了一個類,你就失去了繼承其他類的機會了。

介面通常被用來表示附屬描述或行為如: Runnable 、 Clonable 、 Serializable 等等,因此當你使用抽象類來表示行為時,你的類就不能同時是 Runnable 和 Clonable( 注:這裡的意思是指如果把 Runnable 等實現為抽象類的情況 ) ,因為在 Java 中你不能繼承兩個類,但當你使用介面時,你的類就可以同時擁有多個不同的行為。

在一些對時間要求比較高的應用中,傾向於使用抽象類,它會比介面稍快一點。如果希望把一系列行為都規範在類繼承層次內,並且可以更好地在同一個地方進行編碼,那麼抽象類是一個更好的選擇。有時,介面和抽象類可以一起使用,介面中定義函式,而在抽象類中定義預設的實現。

 

上一篇:Java面試題總結(亂序版,2020-09-29)

下一篇:Java面試題總結(附答案)

相關文章