- 前言:
在遨游了一番 Java Web 的世界之后镶殷,發(fā)現(xiàn)了自己的一些缺失锈拨,所以就著一篇深度好文:知名互聯(lián)網(wǎng)公司校招 Java 開發(fā)崗面試知識點(diǎn)解析 姻蚓,來好好的對 Java 知識點(diǎn)進(jìn)行復(fù)習(xí)和學(xué)習(xí)一番腾窝,大部分內(nèi)容參照自這一篇文章,有一些自己補(bǔ)充的井氢,也算是重新學(xué)習(xí)一下 Java 吧。
(一)Java 基礎(chǔ)知識點(diǎn)
1)面向?qū)ο蟮奶匦杂心男?/h4>
答:封裝岳链、繼承和多態(tài)(應(yīng)要多算一個(gè)那就是抽象)
封裝是指將對象的實(shí)現(xiàn)細(xì)節(jié)隱藏起來花竞,然后通過公共的方法來向外暴露出該對象的功能。
但封裝不僅僅是 private + getter/setter 掸哑,使用封裝可以對 setter 進(jìn)行更深層次的定制约急,例如你可以對執(zhí)行方法的對象做規(guī)定,也可以對數(shù)據(jù)做一定的要求苗分,還可以做類型轉(zhuǎn)換等等厌蔽。使用封裝不僅僅安全,更可以簡化操作摔癣。(封裝擴(kuò)展閱讀:oc面向?qū)ο笕筇匦灾?<封裝>)繼承是面向?qū)ο髮?shí)現(xiàn)軟件復(fù)用的重要手段奴饮,當(dāng)子類繼承父類后纬向,子類是一種特殊的父類,能直接或間接獲得父類里的成員戴卜。
繼承的缺點(diǎn):1)繼承是一種強(qiáng)耦合關(guān)系逾条,父類變子類也必須變;2)繼承破壞了封裝投剥,對于父類而言师脂,它的實(shí)現(xiàn)細(xì)節(jié)對子類來說都是透明的。多態(tài)簡而言之就是同一個(gè)行為具有多個(gè)不同表現(xiàn)形式或形態(tài)的能力江锨。
比如說危彩,有一杯水,我不知道它是溫的泳桦、冰的還是燙的汤徽,但是我一摸我就知道了,我摸水杯的這個(gè)動作灸撰,對于不同溫度的水谒府,就會得到不同的結(jié)果,這就是多態(tài)浮毯。
多態(tài)的條件:1)繼承完疫;2)重寫;3)向上轉(zhuǎn)型债蓝。
多態(tài)的好處:當(dāng)把不同的子類對象都當(dāng)作父類類型來看壳鹤,可以屏蔽不同子類對象之間的實(shí)現(xiàn)差異,從而寫出通用的代碼達(dá)到通用編程饰迹,以適應(yīng)需求的不斷變化芳誓。(多態(tài)擴(kuò)展閱讀:重新認(rèn)識java(五) ---- 面向?qū)ο笾鄳B(tài)(向上轉(zhuǎn)型與向下轉(zhuǎn)型))抽象是指從特定的角度出發(fā),從已經(jīng)存在的一些事物中抽取我們所關(guān)注的特性啊鸭、行為锹淌,從而形成一個(gè)新的事物的思維過程,是一種從復(fù)雜到簡潔的思維方式赠制。
2)面向?qū)ο蠛兔嫦蜻^程的區(qū)別赂摆?
答:面向過程是一種站在過程的角度思考問題的思想,強(qiáng)調(diào)的是功能行為钟些,功能的執(zhí)行過程烟号,即先干啥,后干啥政恍。
面向過程的設(shè)計(jì):最小的程序單元是函數(shù)汪拥,每個(gè)函數(shù)負(fù)責(zé)完成某一個(gè)功能,用以接受輸入數(shù)據(jù)抚垃,函數(shù)對輸入數(shù)據(jù)進(jìn)行處理喷楣,然后輸出結(jié)果數(shù)據(jù)趟大。整個(gè)軟件系統(tǒng)由一個(gè)個(gè)的函數(shù)組成,其中作為程序入口的函數(shù)稱之為主函數(shù)铣焊,主函數(shù)依次調(diào)用其他函數(shù)逊朽,普通函數(shù)之間可以相互調(diào)用,從而實(shí)現(xiàn)整個(gè)系統(tǒng)功能曲伊。
-
面向過程的缺陷:
向過程的設(shè)計(jì),是采用置頂而下的設(shè)計(jì)方式叽讳,在設(shè)計(jì)階段就需要考慮每一個(gè)模塊應(yīng)該分解成哪些子模塊,每一個(gè)子模塊有細(xì)分為更小的子模塊坟募,如此類推岛蚤,直到將模塊細(xì)化為一個(gè)個(gè)函數(shù)。 - 問題:1)設(shè)計(jì)不夠直觀懈糯,與人類的習(xí)慣思維不一致涤妒;2)系統(tǒng)軟件適應(yīng)性差,可擴(kuò)展性差赚哗,維護(hù)性低她紫。
面向過程最大的問題在于隨著系統(tǒng)的膨脹,面向過程將無法應(yīng)付屿储,最終導(dǎo)致系統(tǒng)的崩潰贿讹。為了解決這一種軟件危機(jī),我們提出面向?qū)ο?/strong>思想够掠。
面向?qū)ο笫且环N基于面向過程的新的編程思想民褂,是一種站在對象的角度思考問題的思想,我們把多個(gè)功能合理的放到不同對象里疯潭,強(qiáng)調(diào)的是具備某些功能的對象赊堪。
- 面向?qū)ο蟾臃衔覀兂R?guī)的思維方式,穩(wěn)定性好袁勺,可重用性強(qiáng)雹食,易于開發(fā)大型軟件產(chǎn)品畜普,有良好的可維護(hù)性期丰。在軟件工程上,面向?qū)ο罂梢允构こ谈幽K化吃挑,實(shí)現(xiàn)更低的耦合和更高的內(nèi)聚钝荡。
- 注意: 不要粗淺的認(rèn)為面向?qū)ο笠欢ň蛢?yōu)于面向過程的設(shè)計(jì)
看到知乎上有一句有意思的話:
你的程序要完成一個(gè)任務(wù)必逆,相當(dāng)于講一個(gè)故事浸遗。
面向過程:編年體;
面向?qū)ο螅杭o(jì)傳體晦攒。而對于復(fù)雜的程序/宏大的故事逛犹,事實(shí)都證明了端辱,面向?qū)ο?紀(jì)傳是更合理的表述方法梁剔。
擴(kuò)展閱讀:面向過程 VS 面向?qū)ο?/a>
3)JDK 和 JRE 的區(qū)別是什么?
解析:這是考察一些基本的概念
答:Java 運(yùn)行時(shí)環(huán)境(JRE-Java Runtime Environment)舞蔽,它包括 Java 虛擬機(jī)荣病、Java 核心類庫和支持文件,但并不包含開發(fā)工具(JDK-Java Development Kit)——編譯器渗柿、調(diào)試器和其他工具个盆。
Java 開發(fā)工具包(JDK)是完整的 Java 軟件開發(fā)包,包含了 JRE朵栖,編譯器和其他的工具(比如 JavaDoc颊亮, Java 調(diào)試器),可以讓開發(fā)者開發(fā)陨溅、編譯终惑、執(zhí)行 Java 應(yīng)用程序。
- 還有其他的一些名詞也可以再看一下:
4)Java 中覆蓋和重載是什么意思门扇?
解析:覆蓋和重載是比較重要的基礎(chǔ)知識點(diǎn)狠鸳,并且容易混淆,所以面試中常見悯嗓。
答:覆蓋(Override)是指子類對父類方法的一種重寫件舵,只能比父類拋出更少的異常,訪問權(quán)限不能比父類的小脯厨,被覆蓋的方法不能是 private 的铅祸,否則只是在子類中重新定義了一個(gè)新方法。
重載(Overload)表示同一個(gè)類中可以有多個(gè)名稱相同的方法合武,但這些方法的參數(shù)列表各不相同临梗。
面試官: 那么構(gòu)成重載的條件有哪些?
答:參數(shù)類型不同稼跳、參數(shù)個(gè)數(shù)不同盟庞、參數(shù)順序不同。
面試官: 函數(shù)的返回值不同可以構(gòu)成重載嗎汤善?為什么什猖?
答:不可以,因?yàn)?Java 中調(diào)用函數(shù)并不需要強(qiáng)制賦值红淡。舉例如下:
如下兩個(gè)方法:
void f(){}
int f(){ return 1; }
只要編譯器可以根據(jù)語境明確判斷出語義不狮,比如在 int x = f();
中,那么的確可以據(jù)此區(qū)分重載方法在旱。不過摇零, 有時(shí)你并不關(guān)心方法的返回值,你想要的是方法調(diào)用的其他效果 (這常被稱為 “為了副作用而調(diào)用” )桶蝎,這時(shí)你可能會調(diào)用方法而忽略其返回值驻仅,所以如果像下面的調(diào)用:
f();
此時(shí) Java 如何才能判斷調(diào)用的是哪一個(gè) f()
呢谅畅?別人如何理解這種代碼呢?所以噪服,根據(jù)方法返回值來區(qū)分重載方法是行不通的铃彰。
5)抽象類和接口的區(qū)別有哪些?
答:
- 抽象類中可以沒有抽象方法芯咧;接口中的方法必須是抽象方法牙捉;
- 抽象類中可以有普通的成員變量;接口中的變量必須是 static final 類型的敬飒,必須被初始化,接口中只有常量邪铲,沒有變量。
- 抽象類只能單繼承无拗,接口可以繼承多個(gè)父接口带到;
- Java 8 中接口中會有 default 方法,即方法可以被實(shí)現(xiàn)英染。
面試官:抽象類和接口如何選擇揽惹?
答:
如果要創(chuàng)建不帶任何方法定義和成員變量的基類,那么就應(yīng)該選擇接口而不是抽象類四康。
如果知道某個(gè)類應(yīng)該是基類搪搏,那么第一個(gè)選擇的應(yīng)該是讓它成為一個(gè)接口,只有在必須要有方法定義和成員變量的時(shí)候闪金,才應(yīng)該選擇抽象類疯溺。因?yàn)槌橄箢愔性试S存在一個(gè)或多個(gè)被具體實(shí)現(xiàn)的方法,只要方法沒有被全部實(shí)現(xiàn)該類就仍是抽象類哎垦。
6)Java 和 C++ 的區(qū)別:
解析:雖然我們不太懂C++囱嫩,但是就是會這么問,尤其是三面(總監(jiān)級別)面試中漏设。
答:
都是面向?qū)ο蟮恼Z言墨闲,都支持封裝、繼承和多態(tài)
指針:Java不提供指針來直接訪問內(nèi)存郑口,程序更加安全
繼承: Java的類是單繼承的鸳碧,C++支持多重繼承;Java通過一個(gè)類實(shí)現(xiàn)多個(gè)接口來實(shí)現(xiàn)C++中的多重繼承潘酗; Java中類不可以多繼承杆兵,但是!W卸帷!接口可以多繼承
內(nèi)存: Java有自動內(nèi)存管理機(jī)制攒砖,不需要程序員手動釋放無用內(nèi)存
7)“static” 關(guān)鍵字是什么意思缸兔?
答:“static” 關(guān)鍵字表明一個(gè)成員變量或者是成員方法可以在沒有所屬的類的實(shí)例變量的情況下被訪問日裙。
面試官:Java中是否可以覆蓋(override)一個(gè) private 或者是 static 的方法?
答:Java 中 static 方法不能被覆蓋惰蜜,因?yàn)榉椒ǜ采w是基于運(yùn)行時(shí)動態(tài)綁定的昂拂,而 static 方法是編譯時(shí)靜態(tài)綁定的。static 方法跟類的任何實(shí)例都不相關(guān)抛猖,所以概念上不適用格侯。
Java 中也不可以覆蓋 private 的方法,因?yàn)?private 修飾的變量和方法只能在當(dāng)前類中使用财著,如果是其他的類繼承當(dāng)前類是不能訪問到 private 變量或方法的联四,當(dāng)然也不能覆蓋。
擴(kuò)展閱讀:重新認(rèn)識java(六) ---- java中的另類:static關(guān)鍵字(附代碼塊知識)
8)Java 是值傳遞還是引用傳遞撑教?
解析:這類題目朝墩,面試官會手寫一個(gè)例子,讓你說出函數(shù)執(zhí)行結(jié)果伟姐。
答:值傳遞是對基本型變量而言的,傳遞的是該變量的一個(gè)副本,改變副本不影響原變量收苏。引用傳遞一般是對于對象型變量而言的,傳遞的是該對象地址的一個(gè)副本, 并不是原對象本身 。
一般認(rèn)為愤兵,Java 內(nèi)的傳遞都是值傳遞.鹿霸,Java 中實(shí)例對象的傳遞是引用傳遞,Java 是值傳遞的秆乳!
- 我們先來看一個(gè)例子:
這是一個(gè)很經(jīng)典的例子杜跷,我們希望在調(diào)用了 swap() 方法之后交換 arg1 和 arg2 的值,但事實(shí)上并沒有矫夷,為什么會這樣葛闷?
這就是因?yàn)?Java 是值傳遞的,也就是說双藕,我們在調(diào)用一個(gè)需要傳遞參數(shù)的函數(shù)時(shí)淑趾,傳遞給函數(shù)的參數(shù)并不是我們傳遞進(jìn)去的參數(shù)本身,而是它的一個(gè)副本忧陪,我們改變了數(shù)據(jù)其實(shí)只是改變了副本的數(shù)據(jù)而已扣泊,并不會對原來的參數(shù)有任何的改變。
- 再來看一個(gè)例子:
我們自己定義了一個(gè)內(nèi)部類 Person 嘶摊,該類只有一個(gè) int 類型的 age 屬性延蟹,然后有 getter/setter ,我們希望通過 changeAge() 函數(shù)來改變 Person 對象的 age 屬性叶堆,為什么這次成功了呢阱飘?
你依然可以理解為,主函數(shù)將 person 復(fù)制了一份到 changeAge 函數(shù)中去,最終還是只改變了 changeAge 中復(fù)制的那一份參數(shù)的值沥匈,而原本的參數(shù)并沒有改變蔗喂,但 changeAge 中的那一份和原本的參數(shù)指向了同一個(gè)內(nèi)存區(qū)域!
9)JDK 中常用的包有哪些高帖?
答:java.lang缰儿、java.util、java.io散址、java.net乖阵、java.sql。
10)JDK预麸,JRE 和 JVM 的聯(lián)系和區(qū)別瞪浸?
答:JDK 是 Java 開發(fā)工具包,是 Java 開發(fā)環(huán)境的核心組件师崎,并提供編譯默终、調(diào)試和運(yùn)行一個(gè) Java 程序所需要的所有工具,可執(zhí)行文件和二進(jìn)制文件犁罩,是一個(gè)平臺特定的軟件齐蔽。
JRE 是 Java 運(yùn)行時(shí)環(huán)境,是 JVM 的實(shí)施實(shí)現(xiàn)床估,提供了運(yùn)行 Java 程序的平臺含滴。JRE 包含了 JVM,但是不包含 Java 編譯器 / 調(diào)試器之類的開發(fā)工具丐巫。
JVM 是 Java 虛擬機(jī)谈况,當(dāng)我們運(yùn)行一個(gè)程序時(shí),JVM 負(fù)責(zé)將字節(jié)碼轉(zhuǎn)換為特定機(jī)器代碼递胧,JVM 提供了內(nèi)存管理 / 垃圾回收和安全機(jī)制等碑韵。
這種獨(dú)立于硬件和操作系統(tǒng),正是 Java 程序可以一次編寫多處執(zhí)行的原因缎脾。
區(qū)別:
??1. JDK 用于開發(fā)祝闻,JRE 用于運(yùn)行 Java 程序;
??2. JDK 和 JRE 中都包含 JVM遗菠;
??3. JVM 是 Java 編程語言的核心并且具有平臺獨(dú)立性联喘。
11)Integer 的緩存機(jī)制
解析:考察的是對源碼的熟悉程度
- 看一個(gè)例子:
第一個(gè)返回true很好理解,就像上面講的辙纬,a和b指向相同的地址豁遭。
第二個(gè)返回false是為什么呢?這是因?yàn)?Integer 有緩存機(jī)制贺拣,在 JVM 啟動初期就緩存了 -128 到 127 這個(gè)區(qū)間內(nèi)的所有數(shù)字蓖谢。
第三個(gè)返回false是因?yàn)橛昧薾ew關(guān)鍵字來開辟了新的空間捂蕴,i和j兩個(gè)對象分別指向堆區(qū)中的兩塊內(nèi)存空間。
我們可以跟蹤一下Integer的源碼蜈抓,看看到底怎么回事启绰。在IDEA中昂儒,你只需要按住Ctrl然后點(diǎn)擊Integer沟使,就會自動進(jìn)入jar包中對應(yīng)的類文件。
跟蹤到文件的700多行渊跋,你會看到這么一段腊嗡,感興趣可以仔細(xì)讀一下,不用去讀也沒有關(guān)系拾酝,因?yàn)槟阒恍枰肋@是 Java 的一個(gè)緩存機(jī)制燕少。Integer 類的內(nèi)部類緩存了 -128 到 127 的所有數(shù)字。(事實(shí)上蒿囤,Integer類的緩存上限是可以通過修改系統(tǒng)來更改的客们,了解就行了,不必去深究材诽。)
12)下述兩種方法分別創(chuàng)建了幾個(gè) Sring 對象底挫?
// 第一種:直接賦一個(gè)字面量
String str1 = "ABCD";
// 第二種:通過構(gòu)造器創(chuàng)建
String str2 = new String("ABCD");
解析:考察的是對 String 對象和 JVM 內(nèi)存劃分的知識。
答:String str1 = "ABCD";
最多創(chuàng)建一個(gè)String對象,最少不創(chuàng)建String對象.如果常量池中,存在”ABCD”,那么str1直接引用,此時(shí)不創(chuàng)建String對象.否則,先在常量池先創(chuàng)建”ABCD”內(nèi)存空間,再引用.
String str2 = new String("ABCD");
最多創(chuàng)建兩個(gè)String對象脸侥,至少創(chuàng)建一個(gè)String對象建邓。new關(guān)鍵字絕對會在堆空間創(chuàng)建一塊新的內(nèi)存區(qū)域,所以至少創(chuàng)建一個(gè)String對象睁枕。
我們來看圖理解一下:
- 當(dāng)執(zhí)行第一句話的時(shí)候官边,會在常量池中添加一個(gè)新的ABCD字符,str1指向常量池的ABCD
- 當(dāng)執(zhí)行第二句話的時(shí)候外遇,因?yàn)橛衝ew操作符注簿,所以會在堆空間新開辟一塊空間用來存儲新的String對象,因?yàn)榇藭r(shí)常量池中已經(jīng)有了ABCD字符跳仿,所以堆中的String對象指向常量池中的ABCD诡渴,而str2則指向堆空間中的String對象。
String 對象是一個(gè)特殊的存在塔嬉,需要注意的知識點(diǎn)也比較多玩徊,這里給一個(gè)之前寫的 String 詳解的文章鏈接:傳送門 其中包含的問題大概有:1)“+” 怎么連接字符串;2)字符串的比較谨究;3)StringBuilder/StringBuffer/String 的區(qū)別恩袱;
13)i++ 與 ++i 到底有什么不同?
解析:對于這兩個(gè)的區(qū)別胶哲,熟悉的表述是:前置++是先將變量的值加 1畔塔,然后使用加 1 后的值參與運(yùn)算,而后置++則是先使用該值參與運(yùn)算,然后再將該值加 1 .但事實(shí)上澈吨,前置++和后置++一樣把敢,在參與運(yùn)算之前都會將變量的值加 1
答:實(shí)際上,不管是前置 ++谅辣,還是后置 ++修赞,都是先將變量的值加 1,然后才繼續(xù)計(jì)算的桑阶。二者之間真正的區(qū)別是:前置 ++ 是將變量的值加 1 后柏副,使用增值后的變量進(jìn)行運(yùn)算的,而后置 ++ 是首先將變量賦值給一個(gè)臨時(shí)變量蚣录,接下來對變量的值加 1割择,然后使用那個(gè)臨時(shí)變量進(jìn)行運(yùn)算。
14)交換變量的三種方式
答:
- 第一種:通過第三個(gè)變量
public class Test{
public static void main(String[] args) {
int x = 5;
int y = 10;
swap(x,y);
System.out.println(x);
System.out.println(y);
Value v = new Value(5,10);
swap(v);
System.out.println(v.x);
System.out.println(v.y);
}
// 無效的交換:形參的改變無法反作用于實(shí)參
public static void swap(int x,int y) {
int temp = x;
x = y;
y = temp;
}
// 有效的交換:通過引用(變量指向一個(gè)對象)來修改成員變量
public static void swap(Value value) {
int temp = value.x;
value.x = value.y;
value.y = temp;
}
}
class Value{
int x;
int y;
public Value(int x,int y) {
this.x = x;
this.y = y;
}
}
輸出的結(jié)果:
5
10
10
5
這有點(diǎn)類似于C/C++語言中的指針萎河,不過相對來說更加安全荔泳。
事實(shí)上,其實(shí)如果把基礎(chǔ)類型int改成對應(yīng)的包裝類的話其實(shí)可以更加簡單的完成這個(gè)操作虐杯,不過需要付出更多的內(nèi)存代價(jià)玛歌。
第二種:通過通過相加的方式(相同的 Value 類不再重復(fù)展示)
public class Test{
public static void main(String[] args) {
Value v1 = new Value(5,10);
swap(v1);
System.out.println("v1交換之后的結(jié)果為:");
System.out.println(v1.x);
System.out.println(v1.y);
}
public static void swap(Value v) {
v.x = v.x + v.y;
v.y = v.x - v.y;
v.x = v.x - v.y;
}
}
輸出的結(jié)果:
v1的交換結(jié)果:
10
5
核心的算法就是swap方法:
v.x = v.x + v.y; // 把v.x與v.y的和存儲在v.x中
v.y = v.x - v.y; // v.x減掉v.y本來的值即為v.x
v.x = v.x - v.y; // v.x減掉v.y的值也就是以前x.y的值
這樣就可以不通過臨時(shí)變量,來達(dá)到交換兩個(gè)變量的目的厦幅,如果覺得上面的方法不太容易理解沾鳄,我們也可以用另一個(gè)參數(shù)z來表示上述過程:
int z = v.x + v.y; // 把v.x與v.y的和存儲在z中
v.y = z - v.y; // z減掉以前的v.y就等于v.x
v.x = z - v.y; // z減掉現(xiàn)在的v.y即以前的v.x,即為v.y
但并不推薦這種做法确憨,原因在于當(dāng)數(shù)值很大的時(shí)候译荞,16進(jìn)制的求和運(yùn)算可能造成數(shù)據(jù)的溢出,雖然最后的結(jié)果依然會是我們所期望的那樣休弃,但仍然不是十分可取吞歼。
- 第三種:通過異或的方式:
位異或運(yùn)算符(^)有這樣的一個(gè)性質(zhì),就是兩個(gè)整型的數(shù)據(jù)x與y塔猾,有:
(x ^ y ^ y) == x
這說明篙骡,如果一個(gè)變量x異或另外一個(gè)變量y兩次,結(jié)果為x丈甸。通過這一點(diǎn)糯俗,可以實(shí)現(xiàn)交換兩個(gè)變量的值:
public class Test{
public static void main(String[] args) {
Value v1 = new Value(5,10);
swap(v1);
System.out.println("v1交換之后的結(jié)果為:");
System.out.println(v1.x);
System.out.println(v1.y);
}
public static void swap(Value v) {
v.x = v.x ^ v.y;
v.y = v.x ^ v.y;
v.x = v.x ^ v.y;
}
}
輸出的結(jié)果:
v1交換之后的結(jié)果為:
10
5
跟上面相加的方式過程幾乎類似,只不過運(yùn)算的方式不同而已睦擂。異或的方法比相加更加可取的地方在于得湘,異或不存在數(shù)據(jù)溢出。
15)Java 對象初始化順序顿仇?
答:不考慮靜態(tài)成員的初始化淘正,調(diào)用一個(gè)對象的構(gòu)造函數(shù)時(shí)摆马,程序先調(diào)用父類的構(gòu)造函數(shù)(可以通過super關(guān)鍵字指定父類的構(gòu)造函數(shù),否則默認(rèn)調(diào)用無參的構(gòu)造函數(shù)鸿吆,并且需要在子類的構(gòu)造函數(shù)的第一行調(diào)用)囤采,之后靜態(tài)成員變量的初始化函數(shù)和靜態(tài)初始化塊則按照在代碼當(dāng)中的順序執(zhí)行,成員變量如果沒有指定值的話則賦予默認(rèn)值惩淳,即基本數(shù)據(jù)類型為0或false等蕉毯,對象則為null;最后調(diào)用自身構(gòu)造函數(shù)黎泣。
- 我們可以寫一段程序來對初始化順序進(jìn)行一個(gè)簡單的驗(yàn)證:
public class Derive extends Base
{
private Member m1 = new Member("Member 1");
{
System.out.println("Initial Block()");
}
public Derive() {
System.out.println("Derive()");
}
private Member m2 = new Member("Member 2");
private int i = getInt();
private int getInt()
{
System.out.println("getInt()");
return 2;
}
public static void main(String[] args)
{
new Derive();
}
}
class Base
{
public Base()
{
System.out.println("Base()");
}
}
class Member
{
public Member(String m)
{
System.out.println("Member() "+m);
}
}
程序的輸出結(jié)果是:
Base()
Member() Member 1
Initial Block()
Member() Member 2
getInt()
Derive()
16)true恕刘、false 與 null 是關(guān)鍵字嗎缤谎?
答:不是抒倚。true、false 是布爾類型的字面常量坷澡,null 是引用類型的字面常量托呕。
面試官:那 goto 與 const 呢?
答:是频敛。goto 與 const 均是 Java 語言保留的關(guān)鍵字项郊,即沒有任何語法應(yīng)用。
17)exception 和 error 有什么區(qū)別斟赚?
答:exception 和 error都是 Throwable 的子類着降。exception 用于用戶程序可以捕獲的異常情況;error 定義了不期望被用戶程序捕獲的異常拗军。
exception 表示一種設(shè)計(jì)或設(shè)計(jì)的問題任洞,也就是說只要程序正常運(yùn)行,從不會發(fā)生的情況发侵;而 error 表示回復(fù)不是不可能但是很困難的情況下的一種嚴(yán)重問題交掏,比如內(nèi)存溢出,不可能指望程序處理這樣的情況刃鳄。
18)throw 和 throws 有什么區(qū)別盅弛?
答:throw 關(guān)鍵字用來在程序中明確的拋出異常,相反叔锐,throws 語句用來表明方法不能處理的異常挪鹏。每一個(gè)方法都必須要指定哪些異常不能處理,所以方法的調(diào)用者才能夠確保處理可能發(fā)生的異常愉烙,多個(gè)異常是用逗號分隔的讨盒。
小結(jié):本節(jié)主要闡述了 Java 基礎(chǔ)知識,并沒有涉及到一些高級的特性齿梁,這些問題一般難度不大催植,適當(dāng)復(fù)習(xí)下肮蛹,應(yīng)該沒問題。
(二)Java 中常見集合
集合這方面的考察相當(dāng)多创南,這部分是面試中必考的知識點(diǎn)伦忠。
1)說說常見的集合有哪些吧?
答:Map接口和Collection接口是所有集合框架的父接口:
- Collection接口的子接口包括:Set接口和List接口
- Map接口的實(shí)現(xiàn)類主要有:HashMap稿辙、TreeMap昆码、Hashtable、ConcurrentHashMap以及Properties等
- Set接口的實(shí)現(xiàn)類主要有:HashSet邻储、TreeSet赋咽、LinkedHashSet等
- List接口的實(shí)現(xiàn)類主要有:ArrayList、LinkedList吨娜、Stack以及Vector等
2)HashMap和Hashtable的區(qū)別有哪些脓匿?(必問)
答:
HashMap沒有考慮同步,是線程不安全的宦赠;Hashtable使用了synchronized關(guān)鍵字陪毡,是線程安全的;
前者允許null作為Key勾扭;后者不允許null作為Key
3)HashMap的底層實(shí)現(xiàn)你知道嗎毡琉?
答:在Java8之前,其底層實(shí)現(xiàn)是數(shù)組+鏈表實(shí)現(xiàn)妙色,Java8使用了數(shù)組+鏈表+紅黑樹實(shí)現(xiàn)桅滋。此時(shí)你可以簡單的在紙上畫圖分析:
4)ConcurrentHashMap 和 Hashtable 的區(qū)別?(必問)
答:ConcurrentHashMap 結(jié)合了 HashMap 和 HashTable 二者的優(yōu)勢身辨。HashMap 沒有考慮同步丐谋,HashTable 考慮了同步的問題。但是 HashTable 在每次同步執(zhí)行時(shí)都要鎖住整個(gè)結(jié)構(gòu)栅表。 ConcurrentHashMap 鎖的方式是稍微細(xì)粒度的笋鄙。 ConcurrentHashMap 將 hash 表分為 16 個(gè)桶(默認(rèn)值),諸如get,put,remove 等常用操作只鎖當(dāng)前需要用到的桶怪瓶。
面試官:ConcurrentHashMap的具體實(shí)現(xiàn)知道嗎萧落?
答:
1. 該類包含兩個(gè)靜態(tài)內(nèi)部類 HashEntry 和 Segment ;前者用來封裝映射表的鍵值對洗贰,后者用來充當(dāng)鎖的角色找岖;
2. Segment 是一種可重入的鎖 ReentrantLock,每個(gè) Segment 守護(hù)一個(gè)HashEntry 數(shù)組里得元素敛滋,當(dāng)對 HashEntry 數(shù)組的數(shù)據(jù)進(jìn)行修改時(shí)许布,必須首先獲得對應(yīng)的 Segment 鎖。
5)HashMap 的長度為什么是2的冪次方绎晃?
答:
1. 通過將 Key 的 hash 值與 length - 1 進(jìn)行 & 運(yùn)算蜜唾,實(shí)現(xiàn)了當(dāng)前 Key 的定位杂曲,2 的冪次方可以減少沖突(碰撞)的次數(shù),提高 HashMap 查詢效率
2. 如果 length 為 2 的次冪 則 length-1 轉(zhuǎn)化為二進(jìn)制必定是 11111……的形式袁余,在于 h 的二進(jìn)制與操作效率會非常的快擎勘,而且空間不浪費(fèi);如果 length 不是 2 的次冪颖榜,比如 length 為 15棚饵,則 length - 1 為 14,對應(yīng)的二進(jìn)制為 1110掩完,在于 h 與操作噪漾,最后一位都為 0 ,而 0001且蓬,0011欣硼,0101,1001缅疟,1011分别,0111,1101 這幾個(gè)位置永遠(yuǎn)都不能存放元素了存淫,空間浪費(fèi)相當(dāng)大,更糟的是這種情況中沼填,數(shù)組可以使用的位置比數(shù)組長度小了很多桅咆,這意味著進(jìn)一步增加了碰撞的幾率,減慢了查詢的效率坞笙!這樣就會造成空間的浪費(fèi)岩饼。
6)List和Set的區(qū)別是啥?
答:List元素是有序的薛夜,可以重復(fù)籍茧;Set元素是無序的,不可以重復(fù)梯澜。
7)List寞冯、Set和Map的初始容量和加載因子:
答:
1. List
ArrayList的初始容量是10;加載因子為0.5晚伙; 擴(kuò)容增量:原容量的 0.5倍+1吮龄;一次擴(kuò)容后長度為15。
Vector初始容量為10咆疗,加載因子是1漓帚。擴(kuò)容增量:原容量的 1倍,如 Vector的容量為10午磁,一次擴(kuò)容后是容量為20尝抖。
2. Set
HashSet毡们,初始容量為16,加載因子為0.75昧辽; 擴(kuò)容增量:原容量的 1 倍漏隐; 如 HashSet的容量為16,一次擴(kuò)容后容量為32
3. Map
HashMap奴迅,初始容量16青责,加載因子為0.75; 擴(kuò)容增量:原容量的 1 倍取具; 如 HashMap的容量為16脖隶,一次擴(kuò)容后容量為32
8)Comparable接口和Comparator接口有什么區(qū)別?
答:
1. 前者簡單暇检,但是如果需要重新定義比較類型時(shí)产阱,需要修改源代碼。
2. 后者不需要修改源代碼块仆,自定義一個(gè)比較器构蹬,實(shí)現(xiàn)自定義的比較方法。 具體解析參考博客:Java集合框架—Set
9)Java集合的快速失敗機(jī)制 “fail-fast”
答:
是java集合的一種錯(cuò)誤檢測機(jī)制悔据,當(dāng)多個(gè)線程對集合進(jìn)行結(jié)構(gòu)上的改變的操作時(shí)庄敛,有可能會產(chǎn)生 fail-fast 機(jī)制。
例如:假設(shè)存在兩個(gè)線程(線程1科汗、線程2)藻烤,線程1通過Iterator在遍歷集合A中的元素,在某個(gè)時(shí)候線程2修改了集合A的結(jié)構(gòu)(是結(jié)構(gòu)上面的修改头滔,而不是簡單的修改集合元素的內(nèi)容)怖亭,那么這個(gè)時(shí)候程序就會拋出 ConcurrentModificationException 異常,從而產(chǎn)生fail-fast機(jī)制坤检。
原因:迭代器在遍歷時(shí)直接訪問集合中的內(nèi)容兴猩,并且在遍歷過程中使用一個(gè) modCount 變量。集合在被遍歷期間如果內(nèi)容發(fā)生變化早歇,就會改變modCount的值倾芝。每當(dāng)?shù)魇褂胔ashNext()/next()遍歷下一個(gè)元素之前,都會檢測modCount變量是否為expectedmodCount值缺前,是的話就返回遍歷蛀醉;否則拋出異常,終止遍歷衅码。
解決辦法:
1. 在遍歷過程中拯刁,所有涉及到改變modCount值得地方全部加上synchronized。
2. 使用CopyOnWriteArrayList來替換ArrayList
10)ArrayList 和 Vector 的區(qū)別
答:
這兩個(gè)類都實(shí)現(xiàn)了 List 接口(List 接口繼承了 Collection 接口)逝段,他們都是有序集合垛玻,即存儲在這兩個(gè)集合中的元素位置都是有順序的割捅,相當(dāng)于一種動態(tài)的數(shù)組,我們以后可以按位置索引來取出某個(gè)元素帚桩,并且其中的數(shù)據(jù)是允許重復(fù)的亿驾,這是與 HashSet 之類的集合的最大不同處,HashSet 之類的集合不可以按索引號去檢索其中的元素账嚎,也不允許有重復(fù)的元素莫瞬。
ArrayList 與 Vector 的區(qū)別主要包括兩個(gè)方面:
同步性:
Vector 是線程安全的,也就是說它的方法之間是線程同步(加了synchronized 關(guān)鍵字)的郭蕉,而 ArrayList 是線程不安全的疼邀,它的方法之間是線程不同步的。如果只有一個(gè)線程會訪問到集合召锈,那最好是使用 ArrayList旁振,因?yàn)樗豢紤]線程安全的問題,所以效率會高一些涨岁;如果有多個(gè)線程會訪問到集合拐袜,那最好是使用 Vector,因?yàn)椴恍枰覀冏约涸偃タ紤]和編寫線程安全的代碼梢薪。數(shù)據(jù)增長:
ArrayList 與 Vector 都有一個(gè)初始的容量大小蹬铺,當(dāng)存儲進(jìn)它們里面的元素的個(gè)人超過了容量時(shí),就需要增加 ArrayList 和 Vector 的存儲空間沮尿,每次要增加存儲空間時(shí)丛塌,不是只增加一個(gè)存儲單元,而是增加多個(gè)存儲單元畜疾,每次增加的存儲單元的個(gè)數(shù)在內(nèi)存空間利用與程序效率之間要去的一定的平衡。Vector 在數(shù)據(jù)滿時(shí)(加載因子1)增長為原來的兩倍(擴(kuò)容增量:原容量的 1 倍)印衔,而 ArrayList 在數(shù)據(jù)量達(dá)到容量的一半時(shí)(加載因子 0.5)增長為原容量的 0.5 倍 + 1 個(gè)空間啡捶。
面試官:那 ArrayList 和 LinkedList 的區(qū)別呢?
答:
- LinkedList 實(shí)現(xiàn)了 List 和 Deque 接口奸焙,一般稱為雙向鏈表瞎暑;
- LinkedList 在插入和刪除數(shù)據(jù)時(shí)效率更高,ArrayList 在查找某個(gè) index 的數(shù)據(jù)時(shí)效率更高与帆;
- LinkedList 比 ArrayList 需要更多的內(nèi)存了赌;
面試官:Array 和 ArrayList 有什么區(qū)別?什么時(shí)候該應(yīng) Array 而不是 ArrayList 呢玄糟?
答:它們的區(qū)別是:
- Array 可以包含基本類型和對象類型勿她,ArrayList 只能包含對象類型。
- Array 大小是固定的阵翎,ArrayList 的大小是動態(tài)變化的逢并。
- ArrayList 提供了更多的方法和特性之剧,比如:addAll(),removeAll()砍聊,iterator() 等等背稼。
對于基本類型數(shù)據(jù),集合使用自動裝箱來減少編碼工作量玻蝌。但是蟹肘,當(dāng)處理固定大小的基本數(shù)據(jù)類型的時(shí)候,這種方式相對比較慢俯树。
11)如何去掉一個(gè) Vector 集合中重復(fù)的元素帘腹?
答:
Vector newVector = new Vector();
for (int i = 0; i < vector.size(); i++) {
Object obj = vector.get(i);
if (!newVector.contains(obj)) {
newVector.add(obj);
}
}
還有一種簡單的方式,利用了 Set 不允許重復(fù)元素的特性:
HashSet set = new HashSet(vector);
小結(jié):本小節(jié)是 Java 中關(guān)于集合的考察聘萨,是 Java 崗位面試中必考的知識點(diǎn)竹椒,除了應(yīng)該掌握以上的問題,包括各個(gè)集合的底層實(shí)現(xiàn)也建議各位同學(xué)閱讀米辐,加深理解胸完。
12)如何權(quán)衡是使用無序的數(shù)組還是有序的數(shù)組?
答:有序數(shù)組最大的好處在于查找的時(shí)間復(fù)雜度是O(log n)翘贮,而無序數(shù)組是O(n)赊窥。有序數(shù)組的缺點(diǎn)是插入操作的時(shí)間復(fù)雜度是O(n),因?yàn)橹荡蟮脑匦枰笠苿觼斫o新元素騰位置狸页。相反锨能,無序數(shù)組的插入時(shí)間復(fù)雜度是常量O(1)。
總結(jié)
oh.....復(fù)習(xí)下來還真是酸爽....前路漫漫啊....
歡迎轉(zhuǎn)載芍耘,轉(zhuǎn)載請注明出處址遇!
簡書ID:@我沒有三顆心臟
github:wmyskxz
歡迎關(guān)注公眾微信號:wmyskxz_javaweb
分享自己的Java Web學(xué)習(xí)之路以及各種Java學(xué)習(xí)資料