Android程序員面試分類模擬2_第1頁
Android程序員面試分類模擬2_第2頁
Android程序員面試分類模擬2_第3頁
Android程序員面試分類模擬2_第4頁
Android程序員面試分類模擬2_第5頁
已閱讀5頁,還剩21頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)

文檔簡介

Android程序員面試分類模擬2論述題1.

Java語言有哪些優(yōu)點?正確答案:SUN公司對Java語言的描述如下:“Javaisasimple,object-oriented,dist(江南博哥)ributed,interpreted,robust,secure,architectureneutral,portable,high-performance,multithreaded,anddynamiclanguage”。具體而言,Java語言具有以下幾個方面的優(yōu)點:

1)Java為純面向?qū)ο蟮恼Z言(《Java編程思想》提到Java語言是一種“Everythingisobject”的語言),它能夠直接反映現(xiàn)實生活中的對象,例如火車、動物等,因此通過它,開發(fā)人員更容易編寫程序。

2)平臺無關(guān)性。Java語言可以一次編譯,到處運行。無論是在Windows平臺還是在Linux、macOS等其他平臺上對Java程序進行編譯,編譯后的程序在其他平臺上都可以運行。由于Java是解釋型語言,編譯器會把Java代碼變成“中間代碼”,然后在JVM(JavaVirtualMachine,Java虛擬機)上被解釋執(zhí)行。由于中間代碼與平臺無關(guān),所以,Java語言可以很好地跨平臺執(zhí)行,具有很好的可移植性。

3)Java提供了很多內(nèi)置的類庫,通過這些類庫,簡化了開發(fā)人員的編程工作,同時縮短了項目的開發(fā)時間。例如:提供了對多線程支持,提供了對網(wǎng)絡(luò)通信的支持,最重要的一點是提供了垃圾回收器,把開發(fā)人員從對內(nèi)存的管理中解脫出來。

4)提供了對Web應(yīng)用開發(fā)的支持,例如Applet、Servlet和JSP可以用來開發(fā)Web應(yīng)用程序。Socket、RMI可以用來開發(fā)分布式應(yīng)用程序的類庫。

5)具有較好的安全性和健壯性。Java語言經(jīng)常被用在網(wǎng)絡(luò)環(huán)境中,為了增強程序的安全性,Java語言提供了一個防止惡意代碼攻擊的安全機制(數(shù)組邊界檢測和bytecode校驗等)。Java的強類型機制、垃圾回收器、異常處理和安全檢查機制使得使用Java語言編寫的程序有很好的健壯性。

6)去除了C++語言中難以理解、容易混淆的特性,例如頭文件、指針、結(jié)構(gòu)、單元、運算符重載、虛擬基礎(chǔ)類、多重繼承等,使得程序更加嚴謹、簡潔。

2.

instanceof有什么作用?正確答案:instanceof是Java語言中的一個二元運算符,它的作用是判斷一個引用類型的變量所指向的對象是否為一個類(或接口、抽象類、父類)的實例,即它左邊的對象是否是它右邊的類的實例,返回boolean類型的數(shù)據(jù)。

常見的用法為:result=objectinstanceofclass,如果object是class的一個實例,則instanceof運算符返回true。如果object不是指定類的一個實例,或者object是null,則返回false。

以如下程序為例:

publicclassTest

{

publicstaticvoidmain(Stringargs[])

{

Strings="Hello";

int[]a={1,2};

if(sinstanceofString)

System.out.println("true");

if(sinstanceofObject)

System.out.println("true");

if(ainstanceofint[])

System.out.println("true");

}

}

程序運行結(jié)果為:

true

true

true

3.

一個Java文件中是否可以定義多個類?正確答案:一個Java文件可以定義多個類,但是最多只能有一個類被public修飾,并且這個類的類名與文件名必須相同,若這個文件中沒有public的類,則文件名隨便是一個類的名字即可。需要注意的是,當(dāng)用javac指令編譯這個java文件的時候,它會給每一個類生成一個對應(yīng)的.class文件。如下例定義Derived.java為:

classBase

{

publicvoidprint()

{

System.out.println("Base");

}

}

publicclassDerivedextendsBase

{

publicstaticvoidmain(String[]a)

{

Basec=newDerived();

c.print();

}

}

使用javacDerived.java指令編譯上述代碼,會生成兩個字節(jié)碼文件:Base.class與Derived.class,然后使用javaDerived指令執(zhí)行,會輸出:Base。

4.

變量命名有哪些規(guī)則?正確答案:在Java語言中,變量名、函數(shù)名、數(shù)組名統(tǒng)稱為標(biāo)識符,Java語言規(guī)定標(biāo)識符只能由字母(a~z,A~Z)、數(shù)字(0~9)、下劃線(_)和$組成,并且標(biāo)識符的第一個字符必須是字母、下劃線或$。此外,標(biāo)識符也不能包含空白字符(換行符、空格和制表符)。

以下標(biāo)識符都是非法的:

1)char:char是Java語言的一個數(shù)據(jù)類型,是保留字,不能作為標(biāo)識符,其他的如int、float等類似。

2)numberofbook:標(biāo)識符中不能有空格。

3)3com:不能以數(shù)字開頭。

4)a*B:*不能作為標(biāo)識符的字符。

值得注意的是,在Java語言中,變量名是區(qū)分大小寫的,例如Count與count被認為是兩個不同的標(biāo)識符。

5.

“==”、equals和hashCode的區(qū)別是什么?正確答案:“==”運算符用來比較兩個變量的值是否相等,也就是用于比較變量所對應(yīng)的內(nèi)存中所存儲的數(shù)值是否相同,要比較兩個基本類型的數(shù)據(jù)或兩個引用變量是否相等,只能用“==”運算符。

具體而言,如果兩個變量是基本數(shù)據(jù)類型,可以直接用“==”來比較其對應(yīng)的值是否相等。如果一個變量指向的數(shù)據(jù)是對象(引用類型),那么,此時涉及了兩塊內(nèi)存,對象本身占用一塊內(nèi)存(堆內(nèi)存),對象的引用也占用一塊內(nèi)存。例如,對于賦值語句Strings=newString(),變量s占用一塊存儲空間(一般在棧中),而newString()則存儲在另外一塊存儲空間里(一般在堆中),此時,變量s所對應(yīng)的內(nèi)存中存儲的數(shù)值就是對象占用的那塊內(nèi)存的首地址。對于指向?qū)ο箢愋偷淖兞?,如果要比較兩個變量是否指向同一個對象,即要看這兩個變量所對應(yīng)的內(nèi)存中的數(shù)值是否相等(這兩個對象是否指向同一塊存儲空間),這時候就可以用“==”運算符進行比較。但是,如果要比較這兩個對象的內(nèi)容是否相等,那么用“==”運算符就無法實現(xiàn)了。

2)equals是Object類提供的方法之一,每一個Java類都繼承自O(shè)bject類,所以每一個對象都具有equals這個方法。Object類中定義的equals(Object)方法是直接使用“==”比較的兩個對象,所以在沒有覆蓋equals(Object)方法的情況下,equals(Object)與“==”一樣,比較的是引用。

相比“==”運算符,equals(Object)方法的特殊之處就在于它可以被覆蓋,所以可以通過覆蓋這個方法讓它不是比較引用而是比較對象的屬性。例如String類的equals方法是用于比較兩個獨立對象的內(nèi)容是否相同,即堆中的內(nèi)容是否相同。例如,對于下面的代碼:

Strings1=newString("Hello");

Strings2=newString("Hello");

兩條new語句在堆中創(chuàng)建了兩個對象,然后用s1、s2這兩個變量分別指向這兩個對象,這是兩個不同的對象,它們的首地址是不同的,即s1和s2中存儲的數(shù)值是不相同的,所以,表達式a==b將返回false,而這兩個對象中的內(nèi)容是相同的,所以,表達式a.equals(b)將返回true。

如果一個類沒有實現(xiàn)equals方法,那么它將繼承Object類的equals方法,Object類的equals方法的實現(xiàn)代碼如下:

booleanequals(Objecto){

returnthis==o;

}

通過以上例子可以說明,如果一個類沒有自己定義equals方法,它默認的equals方法(從Object類繼承的)就是使用“==”運算符,也是在比較兩個變量指向的對象是否為同一對象,此時使用equals方法和使用“==”會得到同樣的結(jié)果,如果比較的是兩個獨立的對象則總返回false。如果編寫的類希望能夠比較該類創(chuàng)建的兩個實例對象的內(nèi)容是否相同,那么必須覆蓋equals方法,由開發(fā)人員自己寫代碼來決定在什么情況即可認為兩個對象的內(nèi)容是相同的。

3)hashCode()方法是從Object類中繼承過來的,它也用來鑒定兩個對象是否相等。Object類中的hashCode()方法返回對象在內(nèi)存中地址轉(zhuǎn)換成的一個int值,所以如果沒有重寫hashCode()方法,任何對象的hashCode()方法都是不相等的。

雖然equals方法也是用來判斷兩個對象是否相等的,但是二者是有區(qū)別的。一般來講,equals方法是給用戶調(diào)用的,如果需要判斷兩個對象是否相等,可以重寫equals方法,然后在代碼中調(diào)用,就可以判斷它們是否相等了。對于hashCode()方法,用戶一般不會去調(diào)用它,例如在HashMap中,由于key是不可以重復(fù)的,它在判斷key是否重復(fù)的時候就判斷了hashCode()這個方法,而且也用到了equals方法。此外“不可以重復(fù)”指的是equals和hashCode()只要有一個不等就可以了。所以,hashCode()相當(dāng)于是一個對象的編碼,就好像文件中的md5,它與equals方法的不同之處就在于它返回的是int型,比較起來不直觀。

一般在覆蓋equals方法的同時也要覆蓋hashCode()方法,否則,就會違反Object.hashCode的通用約定,從而導(dǎo)致該類無法與所有基于散列值(hash)的集合類(HashMap、HashSet和Hashtable)結(jié)合在一起正常運行。

hashCode()的返回值和equals方法的關(guān)系如下:如果x.equals(y)返回true,即兩個對象根據(jù)equals方法比較是相等的,那么調(diào)用這兩個對象中任意一個對象的hashCode()方法都必須產(chǎn)生同樣的整數(shù)結(jié)果。如果x.equals(y)返回false,即兩個對象根據(jù)equals()方法比較是不相等的,那么x和y的hashCode()方法的返回值有可能相等,也有可能不等。反過來,hashCode()方法的返回值不等,一定能推出equals方法的返回值也不等,而hashCode()方法的返回值相等,則equals方法的返回值可能相等,也可能不等。

6.

“<<”運算符與“>>”運算符有何異同?正確答案:“<<”運算符表示左移,左移n位表示原來的值乘2的n次方。經(jīng)常用來代替乘法操作,例如:一個數(shù)m乘以16可以表示為將這個數(shù)左移4位(m<<4),由于CPU直接支持位運算,因此位運算比乘法運算的效率高。

與右移運算不同的是,左移運算沒有有符號與無符號左移,在左移的時候,移除高位的同時再低位補0。以4<<3(4為int型)為例,其運算步驟如下所示:

1)把4轉(zhuǎn)換為二進制數(shù)字00000000000000000000000000000100。

2)把該數(shù)字的高三位移走,同時其他位向左移動3位。

3)在最低位補3個零。最終結(jié)果為00000000000000000000000000100000,對應(yīng)的十進制數(shù)為32。

與右移運算符相同的是,當(dāng)進行左移運算時,如果移動的位數(shù)超過了該類型的最大位數(shù),那么編譯器會對移動的位數(shù)取模。例如對int型移動33位,實際上只移動了33%32=1位。

7.

Java程序初始化的順序是怎樣的?正確答案:在Java語言中,當(dāng)實例化對象時,對象所在類的所有成員變量首先要進行初始化,只有當(dāng)所有類成員完成初始化后,才會調(diào)用對象所在類的構(gòu)造函數(shù)創(chuàng)建對象。

Java程序的初始化一般遵循以下3個原則(優(yōu)先級依次遞減):①靜態(tài)對象(變量)優(yōu)先于非靜態(tài)對象初始化,其中,靜態(tài)對象(變量)只初始化一次,而非靜態(tài)對象(變量)可能會初始化多次。②父類優(yōu)先于子類進行初始化。③按照成員變量定義順序進行初始化。即使變量定義散布于方法定義之中,它們依然在任何方法(包括構(gòu)造方法)被調(diào)用之前先進行初始化。

Java程序的初始化工作可以在許多不同的代碼塊中來完成(例如:靜態(tài)代碼塊、構(gòu)造函數(shù)等),它們執(zhí)行的順序為:父類靜態(tài)變量→父類靜態(tài)代碼塊→子類靜態(tài)變量→子類靜態(tài)代碼→父類非靜態(tài)變量→父類非靜態(tài)代碼塊→父類構(gòu)造函數(shù)→子類非靜態(tài)變量→子類非靜態(tài)代碼塊→子類構(gòu)造函數(shù)。下面給出一個不同模塊初始化時執(zhí)行順序的一個例子。

classBase{

static

{

System.out.println("Basestaticblock");

}

{

System.out.println("Baseblock");

}

publicBase()

{

System.out.println("Baseconstructor");

}

)

publicclassDerivedextendsBase

{

static

{

System.out.println("Derivedstaticblock");

}

{

System.out.println("Derivedblock");

}

publicDerived()

{

System.out.println("Derivedconstructor");

}

publicstaticvoidmain(Stringargs[])

{

newDerived();

}

}

程序運行結(jié)果為:

Basestaticblock

Derivedstaticblock

Baseblock

Baseconstructor

Derivedblock

Derivedconstructor

這里需要注意的是,(靜態(tài))非靜態(tài)成員域在定義時初始化和(靜態(tài))非靜態(tài)塊中初始化的優(yōu)先級是平級的,也就是說按照從上到下初始化,最后一次初始化為最終的值(不包括非靜態(tài)的成員域在構(gòu)造器中初始化)。所以在(靜態(tài))非靜態(tài)塊中初始化的域甚至能在該域聲明的上方,因為分配存儲空間在初始化之前就完成了。

如下例所示:

publicclasstestStatic

{

static{a=2;}

staticinta=1;

staticintb=3;

static{b=4;}

publicstaticvoidmain(String[]args)

{

System.out.println(a);

System.out.println(b);

}

}

程序運行結(jié)果為:

1

4

8.

JDK中哪些類是不能被繼承的?正確答案:不能繼承的類是那些用final關(guān)鍵字修飾的類。一般比較基本的類型為防止擴展類無意間破壞原來方法的實現(xiàn)的類型都應(yīng)該是final的,在JDK中,String、StringBuffer等都是基本類型。所以,String和StringBuffer等是不能繼承的。

9.

運算符優(yōu)先級是什么?正確答案:Java語言中有很多運算符,由于運算符優(yōu)先級的問題經(jīng)常會導(dǎo)致程序出現(xiàn)意想不到的結(jié)果,下表詳細介紹了運算符的優(yōu)先級。

運算符優(yōu)先級優(yōu)先級運算符結(jié)合性1.()[]從左向右2+(正)-(負)++--~!3*/%4+(加)-(減)5<<>>(無符號右移)>>>(有符號右移)6<<=

>>=instanceof7==!=8&9|10^11&&12||13?:14=+=-=*=/=%=&=|=^=~=<<=>>=>>>=在實際使用的時候,如果不確定運算符的優(yōu)先級,最好通過括號運算符來控制運算順序。

10.

數(shù)組的初始化方式有哪幾種?正確答案:在Java語言中,一維數(shù)組的聲明方式為:

typearrayName[]或type[]arrayName

其中type既可以是基本的數(shù)據(jù)類型,也可以是類,arrayName表示數(shù)組的名字,[]用來表示這個變量的類型為一維數(shù)組。與C/C++語言不同的是,在Java語言中,數(shù)組被創(chuàng)建后會根據(jù)數(shù)組存儲的數(shù)據(jù)類型初始化成對應(yīng)的初始值(例如,int類型會初始化為0,對象會初始化為null)。另外一個不同之處是Java數(shù)組在定義的時候,并不會給數(shù)組元素分配空間,因此[]中不需要指定數(shù)組的長度,對于使用上面方式定義的數(shù)組在使用的時候還必須為之分配空間,分配方法為:

arrayName=newtype[arraySize];//arraySize表示數(shù)組的長度

在完成數(shù)組的聲明后,需要對其進行初始化,下面介紹兩種初始化方法:

1)int[]a=newint[5];//動態(tài)創(chuàng)建5個整型,默認初始化為0

2)int[]a={1,2,3,4,5};//聲明一個數(shù)組類型變量并初始化。

當(dāng)然,在使用的時候也可以把數(shù)組的聲明和初始化分開來寫,例如:

1)int[]a;//聲明一個數(shù)組類型的對象a

a=newint[5];//給數(shù)組a申請可以存儲5個int類型大小的空間,默認值為0

2)int[]a;//聲明一個數(shù)組類型的對象a

a=newint[]{1,2,3,4,5};//給數(shù)組申請存儲空間,并初始化為默認值

以上主要介紹了一維數(shù)組的聲明與初始化的方式,下面介紹二維數(shù)組的聲明與初始化的方式,二維數(shù)組有3種聲明的方法:

1)typearrayName[][];

2)type[][]arrayName;

3)type[]arrayName[];

其中[]必須為空。

二維數(shù)組也可以用初始化列表的方式來進行初始化,它的一般形式為:

typearrayName[][]={{c11,c12,c13...},{c21,c22,c23...},{c31,c32,c33...}...};

也可以通過new關(guān)鍵字來給數(shù)組申請存儲空間:

typearrayname[][]=newtype[行數(shù)][列數(shù)]

與C/C++語言不同的是,在Java語言中,二維數(shù)組的第二維的長度可以不同。假如要定義一個二維數(shù)組有兩行,第一行有兩列,第二行有三列,定義方法如下:

1)int[][]arr={{12},{345}};

2)int[][]a=newint[2][];

a[0]=newint[]{1,2};

a[1]=newint[]{3,4,5);

對二維數(shù)組的訪問也是通過下標(biāo)來完成的,一般形式為arryName[行號][列號],下例介紹二維數(shù)組的遍歷方法。

publicclassTest

{

publicstaticvoidmain(String[]args)

{

inta[][]=newint[2][];

a[0]=newint[]{1,2};

a[1]=newint[]{3,4,5};

for(inti=0;i<a.length;i++)

{

for(intj=0;j<a[i].length;j++)

System.out.print(a[i][j]+"");

}

}

}

程序運行結(jié)果為:

12345

11.

Java如何實現(xiàn)類似于C語言中函數(shù)指針的功能?正確答案:在C語言中,有一個非常重要的概念:函數(shù)指針,其最重要的功能是實現(xiàn)回調(diào)函數(shù)。什么是回調(diào)函數(shù)呢?所謂回調(diào)函數(shù),就是指函數(shù)先在某處注冊,而它將在稍后某個需要的時候被調(diào)用。在Windows系統(tǒng)中,開發(fā)人員想讓系統(tǒng)DLL(DynamicLinkLibrary,動態(tài)鏈接庫)調(diào)用自己編寫的一個方法,于是利用DLL當(dāng)中回調(diào)函數(shù)的接口來編寫程序,通過傳遞一個函數(shù)的指針來被調(diào)用,這個過程就稱為回調(diào)。回調(diào)函數(shù)一般用于截獲消息、獲取系統(tǒng)信息或處理異步事件。舉一個簡單例子,程序員A寫了一段程序a,其中預(yù)留有回調(diào)函數(shù)接口,并封裝好了該程序。程序員B要讓a調(diào)用自己的程序b中的一個方法,于是,他通過a中的接口回調(diào)屬于自己的程序b中的方法。

函數(shù)指針一般作為函數(shù)的參數(shù)來使用,開發(fā)人員在使用的時候可以根據(jù)自己的需求傳遞自定義的函數(shù)來實現(xiàn)指定的功能。例如:在實現(xiàn)排序算法的時候,可以通過傳遞一個函數(shù)指針來決定兩個數(shù)的先后順序,從而最終決定該算法是按升序還是降序排列。

由于在Java語言中沒有指針的概念,那么如何才能實現(xiàn)類似于函數(shù)指針的功能呢?可以利用接口與類來實現(xiàn)同樣的效果。具體而言,首先定義一個接口,然后在接口中聲明要調(diào)用的方法,接著實現(xiàn)這個接口,最后把這個實現(xiàn)類的一個對象作為參數(shù)傳遞給調(diào)用程序,調(diào)用程序通過這個參數(shù)來調(diào)用指定的方法,從而實現(xiàn)回調(diào)函數(shù)的功能。如下例所示:

//接口中定義了一個用來比較大小的方法

interfaceIntCompare

{

publicintcmp(inta,intb);

}

classCmp1implementsIntCompare

{

publicintcmp(inta,intb){

if(a>b)

return1;

elseif(a<b)

return-1;

else

return0;

}

}

classCmp2implementsIntCompare

{

publicintcmp(inta,intb)

{

if(a>b)

return-1;

elseif(a<b)

return1;

else

return0;

}

}

publicclassTest

{

publicstaticvoidinsertSort(int[]a,IntComparecmp)

{

if(a!=null)

{

for(inti=1;i<a.length;i++)

{

inttemp=a[i],j=i;

if(cmp.cmp(a[j-1],temp)==1)

{

while(j>=1&&cmp.cmp(a[j-1],temp)==1)

{

a[j]=a[j-1];

j--;

}

}

a[j]=temp;

}

}

}

publicstaticvoidmain(String[]args)

{

int[]array1={7,3,19,40,4,7,1};

insertSort(array1,newCmp1());

System.out.print("升序排列:");

for(inti=0;i<array1.length;i++)

System.out.print(array1[i]+"");

System.out.println();

int[]array2={7,3,19,40,4,7,1};

insertSort(array2,newCmp2());

System.out.print("降序排列:");

for(inti=0;i<array2.length;i++)

System.out.print(array2[i]+"");

}

}

程序運行結(jié)果為:

升序排列:134771940

降序排列:401977431

在上例中,定義了一個用來比較大小的接口IntCompare,這個接口實際上充當(dāng)了C語言中函數(shù)指針的功能,在使用的時候,開發(fā)人員可以根據(jù)實際需求傳入自定義的類。在上例中分別有兩個類Cmp1和Cmp2都實現(xiàn)了這個接口,分別用來在實現(xiàn)升序排序和降序排序的時候使用。其實這也是策略設(shè)計模式所用到的思想。

12.

如何實現(xiàn)無符號數(shù)右移操作?正確答案:Java提供了兩種右移運算符:“>>”和“>>>”。其中,“>>”被稱為有符號右移運算符,“>>>”被稱為無符號右移運算符,它們的功能是將參與運算的對象對應(yīng)的二進制數(shù)右移指定的位數(shù)。不同點在于“>>”在執(zhí)行右移操作的時候,如果參與運算的數(shù)字為正數(shù)時,則在高位補0,若為負數(shù)則在高位補1。而“>>>”則不同,無論參與運算的值為正或為負,都會在高位補0。

此外,需要特別注意的是,對于char、byte、short等類型的數(shù)進行移位操作前,都會自動將數(shù)值轉(zhuǎn)化為int型,然后才進行移位操作,由于int型變量只占4個字節(jié)(32位),因此當(dāng)右移的位數(shù)超過32時,移位運算沒有任何意義。所以,在Java語言中,為了保證移動位數(shù)的有效性,使得右移的位數(shù)不超過32,采用了取余的操作,即a>>n等價于a>>(n%32)。如下例所示:

publicclassTest

{

publicstaticvoidmain(String[]a)

{

inti=-4;

System.out.println("----int>>:"+i);

System.out.println("移位前二進制:"+Integer.toBinaryString(i));

i>>=1;

System.out.println("移位后二進制:"+Integer.toBinaryString(i));

System.out.println("----int>>:"+i);

i=-4;

System.out.println("----int>>>:"+i);

System.out.println("移位前二進制:"+Integer.toBinaryString(i));

i>>>=1;

System.out.println("移位后二進制:"+Integer.toBinaryString(i));

System.out.println("----int>>>:"+i);

shortj=-4;

System.out.println("----short>>>:"+j);

System.out.println("移位前二進制:"+Integer.toBinaryString(j));

j>>>=1;

System.out.println("移位后二進制:"+Integer.toBinaryString(j));

System.out.println("----short>>>:"+j);

i=5;

System.out.println("----int>>:"+i);

System.out.println("移位前二進制:"+Integer.toBinaryString(i));

i>>=32;

System.out.println("移位后二進制:"+Integer.toBinaryString(i));

System.out.println("----int>>:"+i);

}

}

程序運行結(jié)果為:

----int>>:-4

移位前二進制:11111111111111111111111111111100

移位后二進制:11111111111111111111111111111110

----int>>:-2

----int>>>:-4

移位前二進制:11111111111111111111111111111100

移位后二進制:1111111111111111111111111111110

----int>>>:2147483646

----short>>>:-4

移位前二進制:11111111111111111111111111111100

移位后二進制:11111111111111111111111111111110

----short>>>:-2

----int>>:5

移位前二進制:101

移位后二進制:101

----int>>:5

需要特別說明的是,對于short類型來說,由于short只占兩字節(jié),在移位操作的時候會先轉(zhuǎn)換為int類型,雖然在進行無符號右移的時候會在高位補1,當(dāng)把運算結(jié)果再賦值給short類型的時候,只會取其中低位的兩個字節(jié),因此,高位無論補0還是補1對運算結(jié)果無影響。在上例中,-4的二進制表示為1111111111111100(負數(shù)以補碼格式存儲),在轉(zhuǎn)換為二進制的時候會以4字節(jié)的方式輸出,高位會補1,因此輸出為11111111111111111111111111111100,在執(zhí)行無符號數(shù)右移后其二進制變?yōu)?1111111111111111111111111111110,當(dāng)把運算結(jié)果再復(fù)制給i的時候只會取低位的兩個字節(jié),因此,運算結(jié)果的二進制表示為1111111111111110,對應(yīng)的十進制值為-2,當(dāng)把-2以二進制形式輸出的時候,同理會以4字節(jié)的方式輸出,高位會補1,因此輸出為11111111111111111111111111111110。

13.

多態(tài)的實現(xiàn)機制是什么?正確答案:多態(tài)是面向?qū)ο蟪绦蛟O(shè)計中代碼重用的一個重要機制,它表示當(dāng)同一個操作作用在不同的對象的時候,會有不同的語義,從而會產(chǎn)生不同的結(jié)果。例如:同樣是“+”操作,3+4用來實現(xiàn)整數(shù)相加,而“3”+“4”卻實現(xiàn)了字符串的連接。在Java語言中,多態(tài)主要有以下兩種表現(xiàn)方式。

1)重載(Overload)

重載是指同一個類中有多個同名的方法,但這些方法有著不同的參數(shù),因此可以在編譯的時候就可以確定到底調(diào)用哪個方法,它是一種編譯時多態(tài)。重載可以被看作一個類中的方法多態(tài)性。

2)重寫(Override)

子類可以重寫父類的方法,因此同樣的方法會在父類與子類中有著不同的表現(xiàn)形式。在Java語言中,基類的引用變量不僅可以指向基類的實例對象,也可以指向其子類的實例對象。同樣,接口的引用變量也可以指向其實現(xiàn)類的實例對象。而程序調(diào)用的方法在運行期才動態(tài)綁定(綁定指的是將一個方法調(diào)用和一個方法主體連接到一起),就是引用變量所指向的具體實例對象的方法,也就是內(nèi)存里正在運行的那個對象的方法,而不是引用變量的類型中定義的方法。通過這種動態(tài)綁定的方法實現(xiàn)了多態(tài)。由于只有在運行時才能確定調(diào)用哪個方法,因此通過方法重寫實現(xiàn)的多態(tài)也可以被稱為運行時多態(tài)。如下例所示:

classBase

{

publicBase()

{

g();

}

publicvoidf()

{

System.out.println("Basef()");

}

publicvoidg()

{

System.out.println("Baseg()");

}

}

classDerivedextendsBase

{

publicvoidf()

{

System.out.println("Derivedf()");

}

publicvoidg()

{

System.out.println("Derivedg()");

}

}

publicclassTest

{

publicstaticvoidmain(String[]args)

{

Baseb=newDerived();

b.f();

b.g();

}

}

程序的輸出結(jié)果為:

Derivedg()

Derivedf()

Derivedg()

上例中,由于子類Derived的f()方法和g()方法與父類Base的方法同名,因此Derived的方法會覆蓋Base的方法。在執(zhí)行Baseb=newDerived()語句的時候,會調(diào)用Base類的構(gòu)造函數(shù),而在Base的構(gòu)造函數(shù)中,執(zhí)行了g()方法,由于Java語言的多態(tài)特性,此時會調(diào)用子類Derived的g()方法,而非父類Base的g()方法,因此會輸出Derivedg()。由于實際創(chuàng)建的是Derived類的對象,后面的方法調(diào)用都會調(diào)用子類Derived的方法。

此外,只有類中的方法才有多態(tài)的概念,類中成員變量沒有多態(tài)的概念。如下例所示:

classBase

{

publicinti=1;

}

classDerivedextendsBase

{

publicinti=2;

}

publicclassTest

{

publicstaticvoidmain(String[]args)

{

Baseb=newDerived();

System.out.println(b.i);

}

}

程序輸出結(jié)果為:

1

由此可見,成員變量是無法實現(xiàn)多態(tài)的,成員變量的值取父類還是子類并不取決于創(chuàng)建對象的類型,而是取決于定義的變量的類型。這是在編譯期間確定的。在上例中,由于b所屬的類型為Base,b.i指的是Base類中定義的i,所以程序輸出結(jié)果為1。

14.

什么是構(gòu)造方法?正確答案:構(gòu)造方法是一種特殊的方法,用來在對象實例化時初始化對象的成員變量。在Java語言中,構(gòu)造方法具有以下特點。

1)構(gòu)造方法必須與類的名字相同,并且不能有返回值(返回值也不能為void)。

2)每個類可以有多個構(gòu)造方法。當(dāng)開發(fā)人員沒有提供構(gòu)造方法的時候,編譯器在把源代碼編譯成字節(jié)碼的過程中會提供一個沒有參數(shù)默認的構(gòu)造方法,但該構(gòu)造方法不會執(zhí)行任何代碼。如果開發(fā)人員提供了構(gòu)造方法,那么編譯器就不會再創(chuàng)建默認的構(gòu)方法數(shù)了。

3)構(gòu)造方法可以有0個、1個或1個以上的參數(shù)。

4)構(gòu)造方法總是伴隨著new操作一起調(diào)用,不能由程序的編寫者直接調(diào)用,必須要由系統(tǒng)調(diào)用。構(gòu)造方法在對象實例化的時候會被自動調(diào)用,且只運行一次,而普通的方法是在程序執(zhí)行到它的時候被調(diào)用的,可以被該對象調(diào)用多次。

5)構(gòu)造方法的主要作用是完成對象的初始化工作。

6)構(gòu)造方法不能被繼承,因此就不能被重寫(Override),但是構(gòu)造方法能夠被重載,可以使用不同的參數(shù)個數(shù)或參數(shù)類型來定義多個構(gòu)造方法。

7)子類可以通過super關(guān)鍵字來顯式地調(diào)用父類的構(gòu)造方法,當(dāng)父類沒有提供無參數(shù)的構(gòu)造方法時,子類的構(gòu)造方法中必須顯示地調(diào)用父類的構(gòu)造方法,如果父類中提供了無參數(shù)的構(gòu)造方法,此時子類的構(gòu)造方法就可以不顯式地調(diào)用父類的構(gòu)造方法,在這種情況下編譯器會默認調(diào)用父類的無參數(shù)的構(gòu)造方法。當(dāng)有父類時,在實例化對象時會首先執(zhí)行父類的構(gòu)造方法,然后才執(zhí)行子類的構(gòu)造方法。

8)當(dāng)父類和子類都沒有定義構(gòu)造方法的時候,編譯器會為父類生成一個默認的無參數(shù)的構(gòu)造方法,給子類也生成一個默認的無參數(shù)的構(gòu)造方法。此外,默認構(gòu)造器的修飾符只跟當(dāng)前類的修飾符有關(guān)(例如:如果一個類被定義為public,那么它的構(gòu)造方法也是public)。

15.

static與final結(jié)合使用表示什么意思?正確答案:static常與final關(guān)鍵字結(jié)合使用,用來修飾成員變量與成員方法,有點類似于“全局常量”。對于變量,如果使用staticfinal修飾,則表示一旦賦值,就不可修改,并且通過類名可以訪問。對于方法,如果使用staticfinal修飾,則表示方法不可覆蓋,并且可以通過類名直接訪問。

16.

Java語言中是否存在goto關(guān)鍵字?正確答案:雖然goto作為Java的保留字,但目前沒有在Java中使用。在C/C++中,goto常被用作跳出多重循環(huán),在Java語言中,可以使用break和continue來達到同樣的效果。那么既然goto沒有在Java語言中使用,為什么還要作為保留字呢?其中一個可能的原因就是這個關(guān)鍵字有可能會在將來被使用。如果現(xiàn)在不把goto作為保留字,開發(fā)人員就有可能用goto作為變量名來使用。一旦有一天Java支持goto關(guān)鍵字了,這會導(dǎo)致以前的程序無法正常運行。因此把goto作為保留字是非常有必要的。

這里需要注意的是,在Java語言中,雖然沒有g(shù)oto語句,但是卻能使用標(biāo)識符加冒號(:)的形式定義標(biāo)簽,如“mylabel:”,其目的主要是為了在多重循環(huán)中方便使用break和continue而設(shè)計的。

17.

volatile有什么作用?正確答案:volatile的使用是為了線程安全,但volatile不保證線程安全。線程安全有3個要素:可見性、有序性、原子性。線程安全是指在多線程情況下,對共享內(nèi)存的使用,不會因為不同線程的訪問和修改而發(fā)生不期望的情況。

volatile有3個作用:

1)volatile用于解決多核CPU高速緩存導(dǎo)致的變量不同步。

本質(zhì)上這是個硬件問題,其根源在于:CPU的高速緩存的讀取速度遠遠快于主存(物理內(nèi)存)。所以,CPU在讀取一個變量的時候,會把數(shù)據(jù)先讀取到緩存,這樣下次再訪問同一個數(shù)據(jù)的時候就可以直接從緩存讀取了,顯著提高了讀取的性能。而多核CPU有多個這樣的緩存。這就帶來了問題,當(dāng)某個CPU(例如CPU1)修改了這個變量(例如把a的值從1修改為2),但是其他的CPU(例如CPU2)在修改前已經(jīng)把a=1讀取到自己的緩存了,當(dāng)CPU2再次讀取數(shù)據(jù)的時候,它仍然會去自己的緩存區(qū)中去讀取,此時讀取到的值仍然是1,但是實際上這個值已經(jīng)變成2了。這里,就涉及線程安全的要素:可見性。

可見性是指當(dāng)多個線程在訪問同一個變量時,如果其中一個線程修改了變量的值,那么其他線程應(yīng)該能立即看到修改后的值。

volatile的實現(xiàn)原理是內(nèi)存屏障(MemoryBarrier),其原理為:當(dāng)CPU寫數(shù)據(jù)時,如果發(fā)現(xiàn)一個變量在其他CPU中存有副本,那么會發(fā)出信號量通知其他CPU將該副本對應(yīng)的緩存行置為無效狀態(tài),當(dāng)其他CPU讀取到變量副本的時候,會發(fā)現(xiàn)該緩存行是無效的,然后,它會從主存重新讀取。

2)volatile還可以解決指令重排序的問題。

在一般情況下,程序是按照順序執(zhí)行的,例如下面的代碼:

1、inti=0;

2、i++;

3、booleanf=false;

4、f=true;

如果i++發(fā)生在inti=0之前,那么會不可避免的出錯,CPU在執(zhí)行代碼對應(yīng)指令的時候,會認為1、2兩行是具備依賴性的,因此,CPU一定會安排行1早于行2執(zhí)行。

那么,inti=0一定會早于booleanf=false嗎?

并不一定,CPU在運行期間會對指令進行優(yōu)化,沒有依賴關(guān)系的指令,它們的順序可能會被重排。在單線程執(zhí)行下,發(fā)生重排是沒有問題的,CPU保證了順序不一定一致,但結(jié)果一定一致。

但在多線程環(huán)境下,重排序則會引起很大的問題,這又涉及線程安全的要素:有序性。

有序性是指程序執(zhí)行的順序應(yīng)當(dāng)按照代碼的先后順序執(zhí)行。

為了更好地理解有序性,下面通過一個例子來分析:

//成員變量i

inti=0;

//線程一的執(zhí)行代碼

Thread.sleep(10);

i++;

f=true;

//線程二的執(zhí)行代碼

while(!f)

{

System.out.println(i);

}

理想的結(jié)果應(yīng)該是線程二不停地打印0,最后打印一個1,終止。

在線程一里,f和i沒有依賴性,如果發(fā)生了指令重排,那么f=true發(fā)生在i++之前,就有可能導(dǎo)致線程二在終止循環(huán)前輸出的全部是0。

需要注意的是,這種情況并不常見,再次運行并不一定能重現(xiàn),正因為如此,很可能會導(dǎo)致一些莫名的問題,需要特別注意。如果修改上方代碼中i的定義為使用volatile關(guān)鍵字來修飾,那么就可以保證最后的輸出結(jié)果符合預(yù)期。這是因為被volatile修飾的變量,CPU不會對它做重排序優(yōu)化,所以也就保證了有序性。

3)volatile不保證操作的原子性。

原子性:一個或多個操作,要么全部連續(xù)執(zhí)行且不會被任何因素中斷,要么就都不執(zhí)行。這個概念和數(shù)據(jù)庫概念里的事務(wù)(Transaction)很類似,沒錯,事務(wù)就是一種原子性操作。

原子性、可見性和有序性,是線程安全的三要素。

需要特別注意的是,volatile保證線程安全的可見性和有序性,但是不保證操作的原子性,下面的代碼將會證明這一點:

staticvolatileintintVal=0;

publicstaticvoidmain(String[]args)

{

//創(chuàng)建10個線程,執(zhí)行簡單的自加操作

for(inti=0;i<10;i++)

{

newThread(()->

{

for(intj=0;j<1000;j++)

intVal++;

}).stan();

}

//保證之前啟動的全部線程執(zhí)行完畢

while(Thread.activeCount()>1)

Thread.yield();

System.out.println(intVal);

}

在之前的內(nèi)容有提及,volatile能保證修改后的數(shù)據(jù)對所有線程可見,那么,這一段對intVal自增的代碼,最終執(zhí)行完畢的時候,intVal應(yīng)該為10000。

但事實上,結(jié)果是不確定的,大部分情況下會小于10000。這是因為,無論是volatile還是自增操作,都不具備原子性。

假設(shè)intVal初始值為100,自增操作的指令執(zhí)行順序如下所示:

1)獲取intVal值,此時主存內(nèi)intVal值為100;

2)intVal執(zhí)行+1,得到101,此時主存內(nèi)intVal值仍然為100;

3)將101寫回給intVal,此時主存內(nèi)intVal值從100變化為101。

具體執(zhí)行流程如下圖所示。

自增操作的實現(xiàn)原理

這個過程很容易理解,如果這段指令發(fā)生在多線程環(huán)境下呢?以下面這段會發(fā)生錯誤的指令順序為例:

1)線程一獲得了intVal值為100;

2)線程一執(zhí)行+1,得到101,此時值沒有寫回給主存;

3)線程二在主存內(nèi)獲得了intVal值為100;

4)線程二執(zhí)行+1,得到101;

5)線程一寫回101;

6)線程二寫回101。

于是,最終主存內(nèi)的intVal值,還是101。具體執(zhí)行流程如下圖所示。

多線程執(zhí)行自增操作的結(jié)果

為什么volatile的可見性保證在這里沒有生效?

根據(jù)volatile保證可見性的原理(內(nèi)存屏障),當(dāng)一個線程執(zhí)行寫的時候,才會改變“數(shù)據(jù)修改”的標(biāo)量,在上述過程中,線程A在執(zhí)行加法操作發(fā)生后,寫回操作發(fā)生前,CPU開始處理線程B的時間片,執(zhí)行了另外一次讀取intVal,此時intVal值為100,且由于寫回操作尚未發(fā)生,這一次讀取是成功的。

因此,出現(xiàn)了最后計算結(jié)果不符合預(yù)期的情況。

synchoronized關(guān)鍵字確實可以解決多線程的原子操作問題,可以修改上面的代碼為:

for(inti=0;i<10;i++)

{

newThread(()->{

synchronized(lock){

for(intj=0;j<1000;j++)

intVal++;

}

}).start();

}

但是,這種方式明顯效率不高(后面會介紹如何通過CAS來保證原子性),10個線程都在爭搶同一個代碼塊的使用權(quán)。

由此可見,volatile只能提供線程安全的兩個必要條件:可見性和有序性。

18.

JavaCollections框架是什么?正確答案:容器在Java語言開發(fā)中有著非常重要的作用,Java提供了多種類型的容器來滿足開發(fā)的需要,容器不僅在面試,在筆試中也是非常重要的一個知識點,在實際開發(fā)的過程中也是經(jīng)常會用到。因此,對容器的掌握是非常有必要也是非常重要的。Java中的容器可以被分為以下兩類。

(1)Collection

用來存儲獨立的元素,其中包括List、Set和Queue。其中List是按照插入的順序保存元素,Set中不能有重復(fù)的元素,而Queue按照排隊規(guī)則來處理容器中的元素。它們之間的關(guān)系如圖1所示。

圖1

Collection類圖

(2)Map

用來存儲鍵值對,這個容器允許通過鍵來查找值。Map也有多種實現(xiàn)類,如圖2所示。

圖2

Map類圖

JavaCollections框架中包含了大量集合接口以及這些接口的實現(xiàn)類和操作它們的算法(例如排序、查找、反轉(zhuǎn)、替換、復(fù)制、取最小元素、取最大元素等),具體而言,主要提供了List(列表)、Queue(隊列)、Set(集合)、Stack(棧)和

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論