大家都應(yīng)該知道Java是目前最火的計(jì)算機(jī)語(yǔ)言之一,連續(xù)幾年蟬聯(lián)最受程序員歡迎的計(jì)算機(jī)語(yǔ)言榜首,因此每年新入職Java程序員也數(shù)不勝數(shù)。究竟這些新入職的Java程序員是入坑還是入行呢?那就要看他們對(duì)于Java這門(mén)語(yǔ)言的看法了。不管如何,在入職之前,問(wèn)題會(huì)要經(jīng)過(guò)面試,那么Java面試題是怎么出的呢?下面羅列了20道常見(jiàn)初級(jí)Java面試題,簡(jiǎn)直是入職者必備!?
遂川網(wǎng)站制作公司哪家好,找成都創(chuàng)新互聯(lián)!從網(wǎng)頁(yè)設(shè)計(jì)、網(wǎng)站建設(shè)、微信開(kāi)發(fā)、APP開(kāi)發(fā)、成都響應(yīng)式網(wǎng)站建設(shè)等網(wǎng)站項(xiàng)目制作,到程序開(kāi)發(fā),運(yùn)營(yíng)維護(hù)。成都創(chuàng)新互聯(lián)從2013年成立到現(xiàn)在10年的時(shí)間,我們擁有了豐富的建站經(jīng)驗(yàn)和運(yùn)維經(jīng)驗(yàn),來(lái)保證我們的工作的順利進(jìn)行。專(zhuān)注于網(wǎng)站建設(shè)就選成都創(chuàng)新互聯(lián)。
?
1、面向?qū)ο蟮奶卣饔心男┓矫??
答:面向?qū)ο蟮奶卣髦饕幸韵聨讉€(gè)方面:?
- 抽象:抽象是將一類(lèi)對(duì)象的共同特征總結(jié)出來(lái)構(gòu)造類(lèi)的過(guò)程,包括數(shù)據(jù)抽象和行為抽象兩方面。抽象只關(guān)注對(duì)象有哪些屬性和行為,并不關(guān)注這些行為的細(xì)節(jié)是什么。?
- 繼承:繼承是從已有類(lèi)得到繼承信息創(chuàng)建新類(lèi)的過(guò)程。提供繼承信息的類(lèi)被稱(chēng)為父類(lèi)(超類(lèi)、基類(lèi));得到繼承信息的類(lèi)被稱(chēng)為子類(lèi)(派生類(lèi))。繼承讓變化中的軟件系統(tǒng)有了一定的延續(xù)性,同時(shí)繼承也是封裝程序中可變因素的重要手段(如果不能理解請(qǐng)閱讀閻宏博士的《Java與模式》或《設(shè)計(jì)模式精解》中關(guān)于橋梁模式的部分)。?
- 封裝:通常認(rèn)為封裝是把數(shù)據(jù)和操作數(shù)據(jù)的方法綁定起來(lái),對(duì)數(shù)據(jù)的訪問(wèn)只能通過(guò)已定義的接口。面向?qū)ο蟮谋举|(zhì)就是將現(xiàn)實(shí)世界描繪成一系列完全自治、封閉的對(duì)象。我們?cè)陬?lèi)中編寫(xiě)的方法就是對(duì)實(shí)現(xiàn)細(xì)節(jié)的一種封裝;我們編寫(xiě)一個(gè)類(lèi)就是對(duì)數(shù)據(jù)和數(shù)據(jù)操作的封裝。可以說(shuō),封裝就是隱藏一切可隱藏的東西,只向外界提供最簡(jiǎn)單的編程接口(可以想想普通洗衣機(jī)和全自動(dòng)洗衣機(jī)的差別,明顯全自動(dòng)洗衣機(jī)封裝更好因此操作起來(lái)更簡(jiǎn)單;我們現(xiàn)在使用的智能手機(jī)也是封裝得足夠好的,因?yàn)閹讉€(gè)按鍵就搞定了所有的事情)。?
- 多態(tài)性:多態(tài)性是指允許不同子類(lèi)型的對(duì)象對(duì)同一消息作出不同的響應(yīng)。簡(jiǎn)單的說(shuō)就是用同樣的對(duì)象引用調(diào)用同樣的方法但是做了不同的事情。多態(tài)性分為編譯時(shí)的多態(tài)性和運(yùn)行時(shí)的多態(tài)性。如果將對(duì)象的方法視為對(duì)象向外界提供的服務(wù),那么運(yùn)行時(shí)的多態(tài)性可以解釋為:當(dāng)A系統(tǒng)訪問(wèn)B系統(tǒng)提供的服務(wù)時(shí),B系統(tǒng)有多種提供服務(wù)的方式,但一切對(duì)A系統(tǒng)來(lái)說(shuō)都是透明的(就像電動(dòng)剃須刀是A系統(tǒng),它的供電系統(tǒng)是B系統(tǒng),B系統(tǒng)可以使用電池供電或者用交流電,甚至還有可能是太陽(yáng)能,A系統(tǒng)只會(huì)通過(guò)B類(lèi)對(duì)象調(diào)用供電的方法,但并不知道供電系統(tǒng)的底層實(shí)現(xiàn)是什么,究竟通過(guò)何種方式獲得了動(dòng)力)。方法重載(overload)實(shí)現(xiàn)的是編譯時(shí)的多態(tài)性(也稱(chēng)為前綁定),而方法重寫(xiě)(override)實(shí)現(xiàn)的是運(yùn)行時(shí)的多態(tài)性(也稱(chēng)為后綁定)。運(yùn)行時(shí)的多態(tài)是面向?qū)ο笞罹璧臇|西,要實(shí)現(xiàn)多態(tài)需要做兩件事:1). 方法重寫(xiě)(子類(lèi)繼承父類(lèi)并重寫(xiě)父類(lèi)中已有的或抽象的方法);2). 對(duì)象造型(用父類(lèi)型引用引用子類(lèi)型對(duì)象,這樣同樣的引用調(diào)用同樣的方法就會(huì)根據(jù)子類(lèi)對(duì)象的不同而表現(xiàn)出不同的行為)。?
2、訪問(wèn)修飾符public,private,protected,以及不寫(xiě)(默認(rèn))時(shí)的區(qū)別??
答:
類(lèi)的成員不寫(xiě)訪問(wèn)修飾時(shí)默認(rèn)為default。默認(rèn)對(duì)于同一個(gè)包中的其他類(lèi)相當(dāng)于公開(kāi)(public),對(duì)于不是同一個(gè)包中的其他類(lèi)相當(dāng)于私有(private)。受保護(hù)(protected)對(duì)子類(lèi)相當(dāng)于公開(kāi),對(duì)不是同一包中的沒(méi)有父子關(guān)系的類(lèi)相當(dāng)于私有。Java中,外部類(lèi)的修飾符只能是public或默認(rèn),類(lèi)的成員(包括內(nèi)部類(lèi))的修飾符可以是以上四種。?
3、String 是最基本的數(shù)據(jù)類(lèi)型嗎??
答:不是。Java中的基本數(shù)據(jù)類(lèi)型只有8個(gè):byte、short、int、long、float、double、char、boolean;除了基本類(lèi)型(primitive type)和枚舉類(lèi)型(enumeration type),剩下的都是引用類(lèi)型(reference type)。?
4、float f=3.4;是否正確??
答:不正確。3.4是雙精度數(shù),將雙精度型(double)賦值給浮點(diǎn)型(float)屬于下轉(zhuǎn)型(down-casting,也稱(chēng)為窄化)會(huì)造成精度損失,因此需要強(qiáng)制類(lèi)型轉(zhuǎn)換float f =(float)3.4; 或者寫(xiě)成float f =3.4F;。?
5、short s1 = 1; s1 = s1 + 1;有錯(cuò)嗎?short s1 = 1; s1 += 1;有錯(cuò)嗎??
答:對(duì)于short s1 = 1; s1 = s1 + 1;由于1是int類(lèi)型,因此s1+1運(yùn)算結(jié)果也是int 型,需要強(qiáng)制轉(zhuǎn)換類(lèi)型才能賦值給short型。而short s1 = 1; s1 += 1;可以正確編譯,因?yàn)閟1+= 1;相當(dāng)于s1 = (short)(s1 + 1);其中有隱含的強(qiáng)制類(lèi)型轉(zhuǎn)換。?
?
6、Java有沒(méi)有g(shù)oto??
答:goto 是Java中的保留字,在目前版本的Java中沒(méi)有使用。(根據(jù)James Gosling(Java之父)編寫(xiě)的《The Java Programming Language》一書(shū)的附錄中給出了一個(gè)Java關(guān)鍵字列表,其中有g(shù)oto和const,但是這兩個(gè)是目前無(wú)法使用的關(guān)鍵字,因此有些地方將其稱(chēng)之為保留字,其實(shí)保留字這個(gè)詞應(yīng)該有更廣泛的意義,因?yàn)槭煜語(yǔ)言的程序員都知道,在系統(tǒng)類(lèi)庫(kù)中使用過(guò)的有特殊意義的單詞或單詞的組合都被視為保留字)?
7、int和Integer有什么區(qū)別??
答:Java是一個(gè)近乎純潔的面向?qū)ο缶幊陶Z(yǔ)言,但是為了編程的方便還是引入了基本數(shù)據(jù)類(lèi)型,但是為了能夠?qū)⑦@些基本數(shù)據(jù)類(lèi)型當(dāng)成對(duì)象操作,Java為每一個(gè)基本數(shù)據(jù)類(lèi)型都引入了對(duì)應(yīng)的包裝類(lèi)型(wrapper class),int的包裝類(lèi)就是Integer,從Java 5開(kāi)始引入了自動(dòng)裝箱/拆箱機(jī)制,使得二者可以相互轉(zhuǎn)換。?
Java 為每個(gè)原始類(lèi)型提供了包裝類(lèi)型:?
- 原始類(lèi)型: boolean,char,byte,short,int,long,float,double?
- 包裝類(lèi)型:Boolean,Character,Byte,Short,Integer,Long,F(xiàn)loat,Double?
class AutoUnboxingTest {
public static void main(String[] args) {
Integer a = new Integer(3);
Integer b = 3; // 將3自動(dòng)裝箱成Integer類(lèi)型
int c = 3;
System.out.println(a == b); // false 兩個(gè)引用沒(méi)有引用同一對(duì)象
System.out.println(a == c); // true a自動(dòng)拆箱成int類(lèi)型再和c比較
}
}
最近還遇到一個(gè)面試題,也是和自動(dòng)裝箱和拆箱有點(diǎn)關(guān)系的,代碼如下所示:?
public class Test03 {
public static void main(String[] args) {
Integer f1 = 100, f2 = 100, f3 = 150, f4 = 150;
System.out.println(f1 == f2);
System.out.println(f3 == f4);
}
}
如果不明就里很容易認(rèn)為兩個(gè)輸出要么都是true要么都是false。首先需要注意的是f1、f2、f3、f4四個(gè)變量都是Integer對(duì)象引用,所以下面的==運(yùn)算比較的不是值而是引用。裝箱的本質(zhì)是什么呢?當(dāng)我們給一個(gè)Integer對(duì)象賦一個(gè)int值的時(shí)候,會(huì)調(diào)用Integer類(lèi)的靜態(tài)方法valueOf,如果看看valueOf的源代碼就知道發(fā)生了什么。?
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
IntegerCache是Integer的內(nèi)部類(lèi),其代碼如下所示:?
/**
* Cache to support the object identity semantics of autoboxing for values between
* -128 and 127 (inclusive) as required by JLS.
*
* The cache is initialized on first usage. The size of the cache
* may be controlled by the {@code -XX:AutoBoxCacheMax=} option.
* During VM initialization, java.lang.Integer.IntegerCache.high property
* may be set and saved in the private system properties in the
* sun.misc.VM class.
*/
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
簡(jiǎn)單的說(shuō),如果整型字面量的值在-128到127之間,那么不會(huì)new新的Integer對(duì)象,而是直接引用常量池中的Integer對(duì)象,所以上面的面試題中f1==f2的結(jié)果是true,而f3==f4的結(jié)果是false。?
提醒:越是貌似簡(jiǎn)單的面試題其中的玄機(jī)就越多,需要面試者有相當(dāng)深厚的功力。?
8、解釋內(nèi)存中的棧(stack)、堆(heap)和靜態(tài)區(qū)(static area)的用法。?
答:通常我們定義一個(gè)基本數(shù)據(jù)類(lèi)型的變量,一個(gè)對(duì)象的引用,還有就是函數(shù)調(diào)用的現(xiàn)場(chǎng)保存都使用內(nèi)存中的棧空間;而通過(guò)new關(guān)鍵字和構(gòu)造器創(chuàng)建的對(duì)象放在堆空間;程序中的字面量(literal)如直接書(shū)寫(xiě)的100、”hello”和常量都是放在靜態(tài)區(qū)中。??臻g操作起來(lái)最快但是棧很小,通常大量的對(duì)象都是放在堆空間,理論上整個(gè)內(nèi)存沒(méi)有被其他進(jìn)程使用的空間甚至硬盤(pán)上的虛擬內(nèi)存都可以被當(dāng)成堆空間來(lái)使用。?
String str = new String("hello");
上面的語(yǔ)句中變量str放在棧上,用new創(chuàng)建出來(lái)的字符串對(duì)象放在堆上,而”hello”這個(gè)字面量放在靜態(tài)區(qū)。?
9、當(dāng)一個(gè)對(duì)象被當(dāng)作參數(shù)傳遞到一個(gè)方法后,此方法可改變這個(gè)對(duì)象的屬性,并可返回變化后的結(jié)果,那么這里到底是值傳遞還是引用傳遞??
答:是值傳遞。Java語(yǔ)言的方法調(diào)用只支持參數(shù)的值傳遞。當(dāng)一個(gè)對(duì)象實(shí)例作為一個(gè)參數(shù)被傳遞到方法中時(shí),參數(shù)的值就是對(duì)該對(duì)象的引用。對(duì)象的屬性可以在被調(diào)用過(guò)程中被改變,但對(duì)對(duì)象引用的改變是不會(huì)影響到調(diào)用者的。C++和C#中可以通過(guò)傳引用或傳輸出參數(shù)來(lái)改變傳入的參數(shù)的值。在C#中可以編寫(xiě)如下所示的代碼,但是在Java中卻做不到。?
using System;
namespace CS01 {
class Program {
public static void swap(ref int x, ref int y) {
int temp = x;
x = y;
y = temp;
}
public static void Main (string[] args) {
int a = 5, b = 10;
swap (ref a, ref b);
// a = 10, b = 5;
Console.WriteLine ("a = {0}, b = {1}", a, b);
}
}
}
說(shuō)明:Java中沒(méi)有傳引用實(shí)在是非常的不方便,這一點(diǎn)在Java 8中仍然沒(méi)有得到改進(jìn),正是如此在Java編寫(xiě)的代碼中才會(huì)出現(xiàn)大量的Wrapper類(lèi)(將需要通過(guò)方法調(diào)用修改的引用置于一個(gè)Wrapper類(lèi)中,再將Wrapper對(duì)象傳入方法),這樣的做法只會(huì)讓代碼變得臃腫,尤其是讓從C和C++轉(zhuǎn)型為Java程序員的開(kāi)發(fā)者無(wú)法容忍。?
10、重載(Overload)和重寫(xiě)(Override)的區(qū)別。重載的方法能否根據(jù)返回類(lèi)型進(jìn)行區(qū)分??
答:方法的重載和重寫(xiě)都是實(shí)現(xiàn)多態(tài)的方式,區(qū)別在于前者實(shí)現(xiàn)的是編譯時(shí)的多態(tài)性,而后者實(shí)現(xiàn)的是運(yùn)行時(shí)的多態(tài)性。重載發(fā)生在一個(gè)類(lèi)中,同名的方法如果有不同的參數(shù)列表(參數(shù)類(lèi)型不同、參數(shù)個(gè)數(shù)不同或者二者都不同)則視為重載;重寫(xiě)發(fā)生在子類(lèi)與父類(lèi)之間,重寫(xiě)要求子類(lèi)被重寫(xiě)方法與父類(lèi)被重寫(xiě)方法有相同的返回類(lèi)型,比父類(lèi)被重寫(xiě)方法更好訪問(wèn),不能比父類(lèi)被重寫(xiě)方法聲明更多的異常(里氏代換原則)。重載對(duì)返回類(lèi)型沒(méi)有特殊的要求。?
面試題:華為的面試題中曾經(jīng)問(wèn)過(guò)這樣一個(gè)問(wèn)題 - “為什么不能根據(jù)返回類(lèi)型來(lái)區(qū)分重載”,快說(shuō)出你的答案吧!?
11、描述一下JVM加載class文件的原理機(jī)制??
答:JVM中類(lèi)的裝載是由類(lèi)加載器(ClassLoader)和它的子類(lèi)來(lái)實(shí)現(xiàn)的,Java中的類(lèi)加載器是一個(gè)重要的Java運(yùn)行時(shí)系統(tǒng)組件,它負(fù)責(zé)在運(yùn)行時(shí)查找和裝入類(lèi)文件中的類(lèi)。?
由于Java的跨平臺(tái)性,經(jīng)過(guò)編譯的Java源程序并不是一個(gè)可執(zhí)行程序,而是一個(gè)或多個(gè)類(lèi)文件。當(dāng)Java程序需要使用某個(gè)類(lèi)時(shí),JVM會(huì)確保這個(gè)類(lèi)已經(jīng)被加載、連接(驗(yàn)證、準(zhǔn)備和解析)和初始化。類(lèi)的加載是指把類(lèi)的.class文件中的數(shù)據(jù)讀入到內(nèi)存中,通常是創(chuàng)建一個(gè)字節(jié)數(shù)組讀入.class文件,然后產(chǎn)生與所加載類(lèi)對(duì)應(yīng)的Class對(duì)象。加載完成后,Class對(duì)象還不完整,所以此時(shí)的類(lèi)還不可用。當(dāng)類(lèi)被加載后就進(jìn)入連接階段,這一階段包括驗(yàn)證、準(zhǔn)備(為靜態(tài)變量分配內(nèi)存并設(shè)置默認(rèn)的初始值)和解析(將符號(hào)引用替換為直接引用)三個(gè)步驟。最后JVM對(duì)類(lèi)進(jìn)行初始化,包括:1)如果類(lèi)存在直接的父類(lèi)并且這個(gè)類(lèi)還沒(méi)有被初始化,那么就先初始化父類(lèi);2)如果類(lèi)中存在初始化語(yǔ)句,就依次執(zhí)行這些初始化語(yǔ)句。?
類(lèi)的加載是由類(lèi)加載器完成的,類(lèi)加載器包括:根加載器(BootStrap)、擴(kuò)展加載器(Extension)、系統(tǒng)加載器(System)和用戶(hù)自定義類(lèi)加載器(java.lang.ClassLoader的子類(lèi))。從Java 2(JDK 1.2)開(kāi)始,類(lèi)加載過(guò)程采取了父親委托機(jī)制(PDM)。PDM更好的保證了Java平臺(tái)的安全性,在該機(jī)制中,JVM自帶的Bootstrap是根加載器,其他的加載器都有且僅有一個(gè)父類(lèi)加載器。類(lèi)的加載首先請(qǐng)求父類(lèi)加載器加載,父類(lèi)加載器無(wú)能為力時(shí)才由其子類(lèi)加載器自行加載。JVM不會(huì)向Java程序提供對(duì)Bootstrap的引用。下面是關(guān)于幾個(gè)類(lèi)加載器的說(shuō)明:?
Bootstrap:一般用本地代碼實(shí)現(xiàn),負(fù)責(zé)加載JVM基礎(chǔ)核心類(lèi)庫(kù)(rt.jar);?
Extension:從java.ext.dirs系統(tǒng)屬性所指定的目錄中加載類(lèi)庫(kù),它的父加載器是Bootstrap;?
System:又叫應(yīng)用類(lèi)加載器,其父類(lèi)是Extension。它是應(yīng)用最廣泛的類(lèi)加載器。它從環(huán)境變量classpath或者系統(tǒng)屬性java.class.path所指定的目錄中記載類(lèi),是用戶(hù)自定義加載器的默認(rèn)父加載器。?
12、抽象類(lèi)(abstract class)和接口(interface)有什么異同??
答:抽象類(lèi)和接口都不能夠?qū)嵗?,但可以定義抽象類(lèi)和接口類(lèi)型的引用。一個(gè)類(lèi)如果繼承了某個(gè)抽象類(lèi)或者實(shí)現(xiàn)了某個(gè)接口都需要對(duì)其中的抽象方法全部進(jìn)行實(shí)現(xiàn),否則該類(lèi)仍然需要被聲明為抽象類(lèi)。接口比抽象類(lèi)更加抽象,因?yàn)槌橄箢?lèi)中可以定義構(gòu)造器,可以有抽象方法和具體方法,而接口中不能定義構(gòu)造器而且其中的方法全部都是抽象方法。抽象類(lèi)中的成員可以是private、默認(rèn)、protected、public的,而接口中的成員全都是public的。抽象類(lèi)中可以定義成員變量,而接口中定義的成員變量實(shí)際上都是常量。有抽象方法的類(lèi)必須被聲明為抽象類(lèi),而抽象類(lèi)未必要有抽象方法。?
13、靜態(tài)嵌套類(lèi)(Static Nested Class)和內(nèi)部類(lèi)(Inner Class)的不同??
答:Static Nested Class是被聲明為靜態(tài)(static)的內(nèi)部類(lèi),它可以不依賴(lài)于外部類(lèi)實(shí)例被實(shí)例化。而通常的內(nèi)部類(lèi)需要在外部類(lèi)實(shí)例化后才能實(shí)例化,其語(yǔ)法看起來(lái)挺詭異的,如下所示。?
/**
* 撲克類(lèi)(一副撲克)
*
*/
public class Poker {
private static String[] suites = {"黑桃", "紅桃", "草花", "方塊"};
private static int[] faces = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
private Card[] cards;
/**
* 構(gòu)造器
*
*/
public Poker() {
cards = new Card[52];
for(int i = 0; i < suites.length; i++) {
for(int j = 0; j < faces.length; j++) {
cards[i * 13 + j] = new Card(suites[i], faces[j]);
}
}
}
/**
* 洗牌 (隨機(jī)亂序)
*
*/
public void shuffle() {
for(int i = 0, len = cards.length; i < len; i++) {
int index = (int) (Math.random() * len);
Card temp = cards[index];
cards[index] = cards[i];
cards[i] = temp;
}
}
/**
* 發(fā)牌
* @param index 發(fā)牌的位置
*
*/
public Card deal(int index) {
return cards[index];
}
/**
* 卡片類(lèi)(一張撲克)
* [內(nèi)部類(lèi)]
*/
public class Card {
private String suite; // 花色
private int face; // 點(diǎn)數(shù)
public Card(String suite, int face) {
this.suite = suite;
this.face = face;
}
@Override
public String toString() {
String faceStr = "";
switch(face) {
case 1: faceStr = "A"; break;
case 11: faceStr = "J"; break;
case 12: faceStr = "Q"; break;
case 13: faceStr = "K"; break;
default: faceStr = String.valueOf(face);
}
return suite + faceStr;
}
}
}
測(cè)試代碼:
class PokerTest {
public static void main(String[] args) {
Poker poker = new Poker();
poker.shuffle(); // 洗牌
Poker.Card c1 = poker.deal(0); // 發(fā)第一張牌
// 對(duì)于非靜態(tài)內(nèi)部類(lèi)Card
// 只有通過(guò)其外部類(lèi)Poker對(duì)象才能創(chuàng)建Card對(duì)象
Poker.Card c2 = poker.new Card("紅心", 1); // 自己創(chuàng)建一張牌
System.out.println(c1); // 洗牌后的第一張
System.out.println(c2); // 打印: 紅心A
}
}
面試題 - 下面的代碼哪些地方會(huì)產(chǎn)生編譯錯(cuò)誤??
class Outer {
class Inner {}
public static void foo() { new Inner(); }
public void bar() { new Inner(); }
public static void main(String[] args) {
new Inner();
}
}
注意:Java中非靜態(tài)內(nèi)部類(lèi)對(duì)象的創(chuàng)建要依賴(lài)其外部類(lèi)對(duì)象,上面的面試題中foo和main方法都是靜態(tài)方法,靜態(tài)方法中沒(méi)有this,也就是說(shuō)沒(méi)有所謂的外部類(lèi)對(duì)象,因此無(wú)法創(chuàng)建內(nèi)部類(lèi)對(duì)象,如果要在靜態(tài)方法中創(chuàng)建內(nèi)部類(lèi)對(duì)象,可以這樣做:?
new Outer().new Inner();
14、Java 中會(huì)存在內(nèi)存泄漏嗎,請(qǐng)簡(jiǎn)單描述。?
答:理論上Java因?yàn)橛欣厥諜C(jī)制(GC)不會(huì)存在內(nèi)存泄露問(wèn)題(這也是Java被廣泛使用于服務(wù)器端編程的一個(gè)重要原因);然而在實(shí)際開(kāi)發(fā)中,可能會(huì)存在無(wú)用但可達(dá)的對(duì)象,這些對(duì)象不能被GC回收,因此也會(huì)導(dǎo)致內(nèi)存泄露的發(fā)生。例如Hibernate的Session(一級(jí)緩存)中的對(duì)象屬于持久態(tài),垃圾回收器是不會(huì)回收這些對(duì)象的,然而這些對(duì)象中可能存在無(wú)用的垃圾對(duì)象,如果不及時(shí)關(guān)閉(close)或清空(flush)一級(jí)緩存就可能導(dǎo)致內(nèi)存泄露。下面例子中的代碼也會(huì)導(dǎo)致內(nèi)存泄露。?
import java.util.Arrays;
import java.util.EmptyStackException;
public class MyStack {
private T[] elements;
private int size = 0;
private static final int INIT_CAPACITY = 16;
public MyStack() {
elements = (T[]) new Object[INIT_CAPACITY];
}
public void push(T elem) {
ensureCapacity();
elements[size++] = elem;
}
public T pop() {
if(size == 0)
throw new EmptyStackException();
return elements[--size];
}
private void ensureCapacity() {
if(elements.length == size) {
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
}
上面的代碼實(shí)現(xiàn)了一個(gè)棧(先進(jìn)后出(FILO))結(jié)構(gòu),乍看之下似乎沒(méi)有什么明顯的問(wèn)題,它甚至可以通過(guò)你編寫(xiě)的各種單元測(cè)試。然而其中的pop方法卻存在內(nèi)存泄露的問(wèn)題,當(dāng)我們用pop方法彈出棧中的對(duì)象時(shí),該對(duì)象不會(huì)被當(dāng)作垃圾回收,即使使用棧的程序不再引用這些對(duì)象,因?yàn)闂?nèi)部維護(hù)著對(duì)這些對(duì)象的過(guò)期引用(obsolete reference)。在支持垃圾回收的語(yǔ)言中,內(nèi)存泄露是很隱蔽的,這種內(nèi)存泄露其實(shí)就是無(wú)意識(shí)的對(duì)象保持。如果一個(gè)對(duì)象引用被無(wú)意識(shí)的保留起來(lái)了,那么垃圾回收器不會(huì)處理這個(gè)對(duì)象,也不會(huì)處理該對(duì)象引用的其他對(duì)象,即使這樣的對(duì)象只有少數(shù)幾個(gè),也可能會(huì)導(dǎo)致很多的對(duì)象被排除在垃圾回收之外,從而對(duì)性能造成重大影響,極端情況下會(huì)引發(fā)Disk Paging(物理內(nèi)存與硬盤(pán)的虛擬內(nèi)存交換數(shù)據(jù)),甚至造成OutOfMemoryError。?
15、如何實(shí)現(xiàn)對(duì)象克隆??
答:有兩種方式:?
1). 實(shí)現(xiàn)Cloneable接口并重寫(xiě)Object類(lèi)中的clone()方法;?
2). 實(shí)現(xiàn)Serializable接口,通過(guò)對(duì)象的序列化和反序列化實(shí)現(xiàn)克隆,可以實(shí)現(xiàn)真正的深度克隆,代碼如下。?
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class MyUtil {
private MyUtil() {
throw new Asserti();
}
public static 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();
// 說(shuō)明:調(diào)用ByteArrayInputStream或ByteArrayOutputStream對(duì)象的close方法沒(méi)有任何意義
// 這兩個(gè)基于內(nèi)存的流只要垃圾回收器清理對(duì)象就能夠釋放資源,這一點(diǎn)不同于對(duì)外部資源(如文件流)的釋放
}
}
下面是測(cè)試代碼:?
import java.io.Serializable;
/**
* 人類(lèi)
*/
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 + "]";
}
}1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
/**
* 小汽車(chē)類(lèi)
*/
class Car implements Serializable {
private static final long serialVersionUID = -5713945027627603702L;
private String brand; // 品牌
private int maxSpeed; // 最高時(shí)速
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對(duì)象p2關(guān)聯(lián)的汽車(chē)對(duì)象的品牌屬性
// 原來(lái)的Person對(duì)象p1關(guān)聯(lián)的汽車(chē)不會(huì)受到任何影響
// 因?yàn)樵诳寺erson對(duì)象時(shí)其關(guān)聯(lián)的汽車(chē)對(duì)象也被克隆了
System.out.println(p1);
} catch (Exception e) {
e.printStackTrace();
}
}
}
注意:基于序列化和反序列化實(shí)現(xiàn)的克隆不僅僅是深度克隆,更重要的是通過(guò)泛型限定,可以檢查出要克隆的對(duì)象是否支持序列化,這項(xiàng)檢查是編譯器完成的,不是在運(yùn)行時(shí)拋出異常,這種是方案明顯優(yōu)于使用Object類(lèi)的clone方法克隆對(duì)象。讓問(wèn)題在編譯的時(shí)候暴露出來(lái)總是優(yōu)于把問(wèn)題留到運(yùn)行時(shí)。?
16、GC是什么?為什么要有GC??
答:GC是垃圾收集的意思,內(nèi)存處理是編程人員容易出現(xiàn)問(wèn)題的地方,忘記或者錯(cuò)誤的內(nèi)存回收會(huì)導(dǎo)致程序或系統(tǒng)的不穩(wěn)定甚至崩潰,Java提供的GC功能可以自動(dòng)監(jiān)測(cè)對(duì)象是否超過(guò)作用域從而達(dá)到自動(dòng)回收內(nèi)存的目的,Java語(yǔ)言沒(méi)有提供釋放已分配內(nèi)存的顯示操作方法。Java程序員不用擔(dān)心內(nèi)存管理,因?yàn)槔占鲿?huì)自動(dòng)進(jìn)行管理。要請(qǐng)求垃圾收集,可以調(diào)用下面的方法之一:System.gc() 或Runtime.getRuntime().gc() ,但JVM可以屏蔽掉顯示的垃圾回收調(diào)用。?
垃圾回收可以有效的防止內(nèi)存泄露,有效的使用可以使用的內(nèi)存。垃圾回收器通常是作為一個(gè)單獨(dú)的低優(yōu)先級(jí)的線程運(yùn)行,不可預(yù)知的情況下對(duì)內(nèi)存堆中已經(jīng)死亡的或者長(zhǎng)時(shí)間沒(méi)有使用的對(duì)象進(jìn)行清除和回收,程序員不能實(shí)時(shí)的調(diào)用垃圾回收器對(duì)某個(gè)對(duì)象或所有對(duì)象進(jìn)行垃圾回收。在Java誕生初期,垃圾回收是Java最大的亮點(diǎn)之一,因?yàn)榉?wù)器端的編程需要有效的防止內(nèi)存泄露問(wèn)題,然而時(shí)過(guò)境遷,如今Java的垃圾回收機(jī)制已經(jīng)成為被詬病的東西。移動(dòng)智能終端用戶(hù)通常覺(jué)得iOS的系統(tǒng)比Android系統(tǒng)有更好的用戶(hù)體驗(yàn),其中一個(gè)深層次的原因就在于Android系統(tǒng)中垃圾回收的不可預(yù)知性。?
補(bǔ)充:垃圾回收機(jī)制有很多種,包括:分代復(fù)制垃圾回收、標(biāo)記垃圾回收、增量垃圾回收等方式。標(biāo)準(zhǔn)的Java進(jìn)程既有棧又有堆。棧保存了原始型局部變量,堆保存了要?jiǎng)?chuàng)建的對(duì)象。Java平臺(tái)對(duì)堆內(nèi)存回收和再利用的基本算法被稱(chēng)為標(biāo)記和清除,但是Java對(duì)其進(jìn)行了改進(jìn),采用“分代式垃圾收集”。這種方法會(huì)跟Java對(duì)象的生命周期將堆內(nèi)存劃分為不同的區(qū)域,在垃圾收集過(guò)程中,可能會(huì)將對(duì)象移動(dòng)到不同區(qū)域:?
- 伊甸園(Eden):這是對(duì)象最初誕生的區(qū)域,并且對(duì)大多數(shù)對(duì)象來(lái)說(shuō),這里是它們唯一存在過(guò)的區(qū)域。?
- 幸存者樂(lè)園(Survivor):從伊甸園幸存下來(lái)的對(duì)象會(huì)被挪到這里。?
- 終身頤養(yǎng)園(Tenured):這是足夠老的幸存對(duì)象的歸宿。年輕代收集(Minor-GC)過(guò)程是不會(huì)觸及這個(gè)地方的。當(dāng)年輕代收集不能把對(duì)象放進(jìn)終身頤養(yǎng)園時(shí),就會(huì)觸發(fā)一次完全收集(Major-GC),這里可能還會(huì)牽扯到壓縮,以便為大對(duì)象騰出足夠的空間。?
與垃圾回收相關(guān)的JVM參數(shù):?
-Xms / -Xmx — 堆的初始大小 / 堆的最大大小?
-Xmn — 堆中年輕代的大小?
-XX:-DisableExplicitGC — 讓System.gc()不產(chǎn)生任何作用?
-XX:+PrintGCDetails — 打印GC的細(xì)節(jié)?
-XX:+PrintGCDateStamps — 打印GC操作的時(shí)間戳?
-XX:NewSize / XX:MaxNewSize — 設(shè)置新生代大小/新生代最大大小?
-XX:NewRatio — 可以設(shè)置老生代和新生代的比例?
-XX:PrintTenuringDistribution — 設(shè)置每次新生代GC后輸出幸存者樂(lè)園中對(duì)象年齡的分布?
-XX:InitialTenuringThreshold / -XX:MaxTenuringThreshold:設(shè)置老年代閥值的初始值和最大值?
-XX:TargetSurvivorRatio:設(shè)置幸存區(qū)的目標(biāo)使用率?
17、日期和時(shí)間:?
- 如何取得年月日、小時(shí)分鐘秒??
- 如何取得從1970年1月1日0時(shí)0分0秒到現(xiàn)在的毫秒數(shù)??
- 如何取得某月的最后一天??
- 如何格式化日期??
答:?
問(wèn)題1:創(chuàng)建java.util.Calendar 實(shí)例,調(diào)用其get()方法傳入不同的參數(shù)即可獲得參數(shù)所對(duì)應(yīng)的值。Java 8中可以使用java.time.LocalDateTimel來(lái)獲取,代碼如下所示。?
public class DateTimeTest {
public static void main(String[] args) {
Calendar cal = Calendar.getInstance();
System.out.println(cal.get(Calendar.YEAR));
System.out.println(cal.get(Calendar.MONTH)); // 0 - 11
System.out.println(cal.get(Calendar.DATE));
System.out.println(cal.get(Calendar.HOUR_OF_DAY));
System.out.println(cal.get(Calendar.MINUTE));
System.out.println(cal.get(Calendar.SECOND));
// Java 8
LocalDateTime dt = LocalDateTime.now();
System.out.println(dt.getYear());
System.out.println(dt.getMonthValue()); // 1 - 12
System.out.println(dt.getDayOfMonth());
System.out.println(dt.getHour());
System.out.println(dt.getMinute());
System.out.println(dt.getSecond());
}
}
問(wèn)題2:以下方法均可獲得該毫秒數(shù)。?
? ?Calendar.getInstance().getTimeInMillis();
System.currentTimeMillis();
Clock.systemDefaultZone().millis(); // Java 8
問(wèn)題3:代碼如下所示。?
? ?Calendar time = Calendar.getInstance();
time.getActualMaximum(Calendar.DAY_OF_MONTH);12
問(wèn)題4:利用java.text.DataFormat 的子類(lèi)(如SimpleDateFormat類(lèi))中的format(Date)方法可將日期格式化。Java 8中可以用java.time.format.DateTimeFormatter來(lái)格式化時(shí)間日期,代碼如下所示。?
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Date;
class DateFormatTest {
public static void main(String[] args) {
SimpleDateFormat oldFormatter = new SimpleDateFormat("yyyy/MM/dd");
Date date1 = new Date();
System.out.println(oldFormatter.format(date1));
// Java 8
DateTimeFormatter newFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
LocalDate date2 = LocalDate.now();
System.out.println(date2.format(newFormatter));
}
}
補(bǔ)充:Java的時(shí)間日期API一直以來(lái)都是被詬病的東西,為了解決這一問(wèn)題,Java 8中引入了新的時(shí)間日期API,其中包括LocalDate、LocalTime、LocalDateTime、Clock、Instant等類(lèi),這些的類(lèi)的設(shè)計(jì)都使用了不變模式,因此是線程安全的設(shè)計(jì)。如果不理解這些內(nèi)容,可以參考我的另一篇文章《關(guān)于Java并發(fā)編程的總結(jié)和思考》。?
18、比較一下Java和JavaSciprt。?
答:Java 與Java是兩個(gè)公司開(kāi)發(fā)的不同的兩個(gè)產(chǎn)品。Java 是原Sun Microsystems公司推出的面向?qū)ο蟮某绦蛟O(shè)計(jì)語(yǔ)言,特別適合于互聯(lián)網(wǎng)應(yīng)用程序開(kāi)發(fā);而Java是Netscape公司的產(chǎn)品,為了擴(kuò)展Netscape瀏覽器的功能而開(kāi)發(fā)的一種可以嵌入Web頁(yè)面中運(yùn)行的基于對(duì)象和事件驅(qū)動(dòng)的解釋性語(yǔ)言。Java的前身是Live;而Java的前身是Oak語(yǔ)言。?
下面對(duì)兩種語(yǔ)言間的異同作如下比較:?
- 基于對(duì)象和面向?qū)ο螅篔ava是一種真正的面向?qū)ο蟮恼Z(yǔ)言,即使是開(kāi)發(fā)簡(jiǎn)單的程序,必須設(shè)計(jì)對(duì)象;Java是種腳本語(yǔ)言,它可以用來(lái)制作與網(wǎng)絡(luò)無(wú)關(guān)的,與用戶(hù)交互作用的復(fù)雜軟件。它是一種基于對(duì)象(Object-Based)和事件驅(qū)動(dòng)(Event-Driven)的編程語(yǔ)言,因而它本身提供了非常豐富的內(nèi)部對(duì)象供設(shè)計(jì)人員使用。?
- 解釋和編譯:Java的源代碼在執(zhí)行之前,必須經(jīng)過(guò)編譯。Java是一種解釋性編程語(yǔ)言,其源代碼不需經(jīng)過(guò)編譯,由瀏覽器解釋執(zhí)行。(目前的瀏覽器幾乎都使用了JIT(即時(shí)編譯)技術(shù)來(lái)提升Java的運(yùn)行效率)?
- 強(qiáng)類(lèi)型變量和類(lèi)型弱變量:Java采用強(qiáng)類(lèi)型變量檢查,即所有變量在編譯之前必須作聲明;Java中變量是弱類(lèi)型的,甚至在使用變量前可以不作聲明,Java的解釋器在運(yùn)行時(shí)檢查推斷其數(shù)據(jù)類(lèi)型。?
- 代碼格式不一樣。?
19、Java堆的結(jié)構(gòu)是什么樣子的?什么是堆中的永久代(Perm Gen space)??
JVM的堆是運(yùn)行時(shí)數(shù)據(jù)區(qū),所有類(lèi)的實(shí)例和數(shù)組都是在堆上分配內(nèi)存。它在JVM啟動(dòng)的時(shí)候被創(chuàng)建。對(duì)象所占的堆內(nèi)存是由自動(dòng)內(nèi)存管理系統(tǒng)也就是垃圾收集器回收。?
堆內(nèi)存是由存活和死亡的對(duì)象組成的。存活的對(duì)象是應(yīng)用可以訪問(wèn)的,不會(huì)被垃圾回收。死亡的對(duì)象是應(yīng)用不可訪問(wèn)尚且還沒(méi)有被垃圾收集器回收掉的對(duì)象。一直到垃圾收集器把這些對(duì)象回收掉之前,他們會(huì)一直占據(jù)堆內(nèi)存空間。?
20、闡述ArrayList、Vector、LinkedList的存儲(chǔ)性能和特性。?
答:ArrayList 和Vector都是使用數(shù)組方式存儲(chǔ)數(shù)據(jù),此數(shù)組元素?cái)?shù)大于實(shí)際存儲(chǔ)的數(shù)據(jù)以便增加和插入元素,它們都允許直接按序號(hào)索引元素,但是插入元素要涉及數(shù)組元素移動(dòng)等內(nèi)存操作,所以索引數(shù)據(jù)快而插入數(shù)據(jù)慢,Vector中的方法由于添加了synchronized修飾,因此Vector是線程安全的容器,但性能上較ArrayList差,因此已經(jīng)是Java中的遺留容器。LinkedList使用雙向鏈表實(shí)現(xiàn)存儲(chǔ)(將內(nèi)存中零散的內(nèi)存單元通過(guò)附加的引用關(guān)聯(lián)起來(lái),形成一個(gè)可以按序號(hào)索引的線性結(jié)構(gòu),這種鏈?zhǔn)酱鎯?chǔ)方式與數(shù)組的連續(xù)存儲(chǔ)方式相比,內(nèi)存的利用率更高),按序號(hào)索引數(shù)據(jù)需要進(jìn)行前向或后向遍歷,但是插入數(shù)據(jù)時(shí)只需要記錄本項(xiàng)的前后項(xiàng)即可,所以插入速度較快。Vector屬于遺留容器(Java早期的版本中提供的容器,除此之外,Hashtable、Dictionary、BitSet、Stack、Properties都是遺留容器),已經(jīng)不推薦使用,但是由于ArrayList和LinkedListed都是非線程安全的,如果遇到多個(gè)線程操作同一個(gè)容器的場(chǎng)景,則可以通過(guò)工具類(lèi)Collections中的synchronizedList方法將其轉(zhuǎn)換成線程安全的容器后再使用(這是對(duì)裝潢模式的應(yīng)用,將已有對(duì)象傳入另一個(gè)類(lèi)的構(gòu)造器中創(chuàng)建新的對(duì)象來(lái)增強(qiáng)實(shí)現(xiàn))。?
補(bǔ)充:遺留容器中的Properties類(lèi)和Stack類(lèi)在設(shè)計(jì)上有嚴(yán)重的問(wèn)題,Properties是一個(gè)鍵和值都是字符串的特殊的鍵值對(duì)映射,在設(shè)計(jì)上應(yīng)該是關(guān)聯(lián)一個(gè)Hashtable并將其兩個(gè)泛型參數(shù)設(shè)置為String類(lèi)型,但是Java API中的Properties直接繼承了Hashtable,這很明顯是對(duì)繼承的濫用。這里復(fù)用代碼的方式應(yīng)該是Has-A關(guān)系而不是Is-A關(guān)系,另一方面容器都屬于工具類(lèi),繼承工具類(lèi)本身就是一個(gè)錯(cuò)誤的做法,使用工具類(lèi)最好的方式是Has-A關(guān)系(關(guān)聯(lián))或Use-A關(guān)系(依賴(lài))。同理,Stack類(lèi)繼承Vector也是不正確的。Sun公司的工程師們也會(huì)犯這種低級(jí)錯(cuò)誤,讓人唏噓不已。
歡迎工作一到五年的Java工程師朋友們加入Java技術(shù)交流群:659270626
群內(nèi)提供免費(fèi)的Java架構(gòu)學(xué)習(xí)資料(里面有高可用、高并發(fā)、高性能及分布式、Jvm性能調(diào)優(yōu)、Spring源碼,MyBatis,Netty,redis,Kafka,MySQL,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個(gè)知識(shí)點(diǎn)的架構(gòu)資料)合理利用自己每一分每一秒的時(shí)間來(lái)學(xué)習(xí)提升自己,不要再用"沒(méi)有時(shí)間“來(lái)掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來(lái)的自己一個(gè)交代!
?
新聞名稱(chēng):Java面試必看二十問(wèn)題
文章鏈接:http://jinyejixie.com/article18/jjhdgp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供品牌網(wǎng)站設(shè)計(jì)、軟件開(kāi)發(fā)、網(wǎng)站維護(hù)、用戶(hù)體驗(yàn)、動(dòng)態(tài)網(wǎng)站、ChatGPT
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)