注:都是在百度搜索整理的答案,如有侵權(quán)和錯(cuò)誤,希告知更改。
一、java中==和equals和hashCode的區(qū)別
1)“==”運(yùn)算符用來比較兩個(gè)變量的值是否相等。也就是說,該運(yùn)算符用于比較變量對(duì)應(yīng)的內(nèi)存中所存儲(chǔ)的數(shù)值是否相同,要比較兩個(gè)基本類型的數(shù)據(jù)或兩個(gè)引用變量是否相等,只能使用“==”運(yùn)算符。
具體而言,如果兩個(gè)變量是基本數(shù)據(jù)類型,可以直接使用“==”運(yùn)算符來比較其對(duì)應(yīng)的值是否相等。如果一個(gè)變量指向的數(shù)據(jù)是對(duì)象(引用類型),那么,此時(shí)涉及了兩塊內(nèi)存,對(duì)象本身占用了一塊內(nèi)存(堆內(nèi)存),變量也占用一塊內(nèi)存,例如,對(duì)于賦值語句:
String s = new String();
變量s占用一塊存儲(chǔ)空間,而new String()則存儲(chǔ)在另外一塊存儲(chǔ)空間里,此時(shí),變量s所對(duì)應(yīng)內(nèi)存中存儲(chǔ)的數(shù)值就是對(duì)象占用的那塊內(nèi)存的首地址。對(duì)于指向?qū)ο箢愋偷淖兞?,如果要比較兩個(gè)變量是否指向同一個(gè)對(duì)象,即要看這兩個(gè)變量所對(duì)應(yīng)的內(nèi)存中的數(shù)值是否相等(這兩個(gè)對(duì)象是否指向同一塊存儲(chǔ)空間),這時(shí)候就可以用“==”運(yùn)算符進(jìn)行比較。但是,如果要比較這兩個(gè)對(duì)象的內(nèi)容是否相等,那么用“==”運(yùn)算符就無法實(shí)現(xiàn)了。
2)equals是Object類提供的方法之一。每一個(gè)Java類都集成自O(shè)bject類,所以每一個(gè)對(duì)象都具有equals這個(gè)方法。Object類中定義的equals(Object)方法的情況下,equals(Object)與“==”運(yùn)算符一樣,比較的是引用。
相比“==”運(yùn)算符,equals(Object)方法的特殊之處就在于它可以被覆蓋,所以可以通過覆蓋的方法讓它不是比較引用而是比較數(shù)據(jù)內(nèi)容,例如String類的equals方法是用于比較兩個(gè)獨(dú)立對(duì)象的內(nèi)容是否相同,即堆中的內(nèi)容是否相同,以下面的代碼為例:
String s1 = new String("Hello");
String s2 =new String("Hello");
兩條new語句創(chuàng)建了兩個(gè)對(duì)象,然后用s1、s2這兩個(gè)變量分別指向一個(gè)對(duì)象,這是兩個(gè)不同的對(duì)象,它們的首地址是不同的,即s1和s2中存儲(chǔ)的數(shù)值是不相同的,所以,表達(dá)式s1==s2將返回false,而這兩個(gè)對(duì)象中的內(nèi)容是相同的,所以,表達(dá)式s1.equals(s2)將返回true。
如果一個(gè)類沒有自己定義equals()方法,那么它將繼承Object類的equals()方法,Object類的equals()方法的實(shí)現(xiàn)代碼如下:
boolean equals(Object object){
return this==object;
}
通過以上例子可以說明,如果一個(gè)類沒有自己定義equals()方法,它默認(rèn)的equals()方法(從Object類繼承的)就是使用“==”運(yùn)算符,也是在比較兩個(gè)變量指向的對(duì)象是否是同一對(duì)象,此時(shí)使用equals()方法和使用“==”運(yùn)算符會(huì)得到同樣的結(jié)果。若比較的是兩個(gè)獨(dú)立的對(duì)象,則總返回false。如果編寫的類希望能夠比較該類創(chuàng)建的兩個(gè)實(shí)例對(duì)象的內(nèi)容是否相同,那么必須覆蓋equals()方法,由開發(fā)人員自己編寫代碼來決定在什么情況下即可認(rèn)為兩個(gè)對(duì)象的內(nèi)容是相同的。
3)hashCode()方法是從Object類中繼承過來的,它也用來鑒定兩個(gè)對(duì)象是否相等。Object類中的hashCode()方法返回對(duì)象在內(nèi)存中地址轉(zhuǎn)換成的一個(gè)int值,所以如果沒有重寫hashCode()方法,任何對(duì)象的hashCode()方法都是不相等的。
雖然equals()方法也是用來判斷兩個(gè)對(duì)象是否相等的,但是它與hashCode()方法是有區(qū)別的。一般來講,equals()方法是給用戶調(diào)用的,如果需要判斷兩個(gè)對(duì)象是否相等的,可以重寫equals()方法,然后在代碼中調(diào)用,這樣就可以判斷它們是否相等了。對(duì)于hashCode()方法,用戶一般不會(huì)去調(diào)用它,例如在hashmap中,由于key是不可以重復(fù)的,它在判斷key是否重復(fù)時(shí)就判斷了hashCode()方法,而且也用到了equals()方法。此處“不可以重復(fù)”指的是equals()和hashCode()只要有一個(gè)不等就可以了。所以,hashCode()方法相當(dāng)于是一個(gè)對(duì)象的編碼,就好像文件中的md5,它與equals()方法的不同之處就在于它返回的是int型,比較起來不直觀。
一般在覆蓋equals()方法的同時(shí)也要覆蓋hashCode()方法,否則,就會(huì)違反Object.hashCode的通用約定,從而導(dǎo)致該類無法與所有基于散列值(hash)集合類(HashMap、HashSet和Hashtable)結(jié)合在一起正常運(yùn)行。
hashCode()方法的返回值和equals()方法的關(guān)系如下:
x.equals(y)返回true,即兩個(gè)對(duì)象根據(jù)equals()方法比較是相等的,那么調(diào)用這兩個(gè)對(duì)象中任意一個(gè)對(duì)象的hashCode()方法都必須產(chǎn)生同樣的整數(shù)結(jié)果。如果x.equals(y)返回false,即兩個(gè)對(duì)象根據(jù)equals()方法比較是不相等的,那么x和y的hashCode()方法的返回值有可能相等,也有可能不相等。反之,hashCode()方法的返回值不相等,一定能推出equals()方法的返回值也不相等,而hashCode()方法的返回值相等,equals()方法的返回值則可能相等,也可能不相等。
二、int、char、long各占多少字節(jié)數(shù)
Java基本類型占用的字節(jié)數(shù):
1字節(jié): byte , boolean
2字節(jié): short , char
4字節(jié): int , float
8字節(jié): long , double
注:1字節(jié)(byte)=8位(bits)
三、int與integer的區(qū)別
- Integer是int的包裝類;int則是java的一種基本數(shù)據(jù)類型
- Integer變量必須實(shí)例化后才能使用;而int變量不需要
- Integer實(shí)際是對(duì)象的引用,當(dāng)new一個(gè)Integer時(shí),實(shí)際上是生成一個(gè)指針指向此對(duì)象;而int則是直接存儲(chǔ)數(shù)據(jù)值
- Integer的默認(rèn)值是null;int的默認(rèn)值是0
四、談?wù)剬?duì)java多態(tài)的理解
?所謂多態(tài)就是指程序中定義的引用變量所指向的具體類型和通過該引用變量發(fā)出的方法調(diào)用在編程時(shí)并不確定,而是在程序運(yùn)行期間才確定,即一個(gè)引用變量倒底會(huì)指向哪個(gè)類的實(shí)例對(duì)象,該引用變量發(fā)出的方法調(diào)用到底是哪個(gè)類中實(shí)現(xiàn)的方法,必須在由程序運(yùn)行期間才能決定。因?yàn)樵诔绦蜻\(yùn)行時(shí)才確定具體的類,這樣,不用修改源程序代碼,就可以讓引用變量綁定到各種不同的類實(shí)現(xiàn)上,從而導(dǎo)致該引用調(diào)用的具體方法隨之改變,即不修改程序代碼就可以改變程序運(yùn)行時(shí)所綁定的具體代碼,讓程序可以選擇多個(gè)運(yùn)行狀態(tài),這就是多態(tài)性。
比如你是一個(gè)酒神,對(duì)酒情有獨(dú)鐘。某日回家發(fā)現(xiàn)桌上有幾個(gè)杯子里面都裝了白酒,從外面看我們是不可能知道這是些什么酒,只有喝了之后才能夠猜出來是何種酒。你一喝,這是劍南春、再喝這是五糧液、再喝這是酒鬼酒….在這里我們可以描述成如下:
酒 a = 劍南春
酒 b = 五糧液
酒 c = 酒鬼酒
?這里所表現(xiàn)的的就是多態(tài)。劍南春、五糧液、酒鬼酒都是酒的子類,我們只是通過酒這一個(gè)父類就能夠引用不同的子類,這就是多態(tài)——我們只有在運(yùn)行的時(shí)候才會(huì)知道引用變量所指向的具體實(shí)例對(duì)象。
?誠然,要理解多態(tài)我們就必須要明白什么是“向上轉(zhuǎn)型”。在繼承中我們簡(jiǎn)單介紹了向上轉(zhuǎn)型,這里就在啰嗦下:在上面的喝酒例子中,酒(Win)是父類,劍南春(JNC)、五糧液(WLY)、酒鬼酒(JGJ)是子類。我們定義如下代碼:
JNC a = new JNC();
對(duì)于這個(gè)代碼我們非常容易理解無非就是實(shí)例化了一個(gè)劍南春的對(duì)象嘛!但是這樣呢?
Wine a = new JNC();
?在這里我們這樣理解,這里定義了一個(gè)Wine 類型的a,它指向JNC對(duì)象實(shí)例。由于JNC是繼承與Wine,所以JNC可以自動(dòng)向上轉(zhuǎn)型為Wine,所以a是可以指向JNC實(shí)例對(duì)象的。這樣做存在一個(gè)非常大的好處,在繼承中我們知道子類是父類的擴(kuò)展,它可以提供比父類更加強(qiáng)大的功能,如果我們定義了一個(gè)指向子類的父類引用類型,那么它除了能夠引用父類的共性外,還可以使用子類強(qiáng)大的功能。
?但是向上轉(zhuǎn)型存在一些缺憾,那就是它必定會(huì)導(dǎo)致一些方法和屬性的丟失,而導(dǎo)致我們不能夠獲取它們。所以父類類型的引用可以調(diào)用父類中定義的所有屬性和方法,對(duì)于只存在與子類中的方法和屬性它就望塵莫及了。
public class Wine {
public void fun1(){
System.out.println("Wine 的Fun.....");
fun2();
}
public void fun2(){
System.out.println("Wine 的Fun2...");
}
}
public class JNC extends Wine{
/**
* @desc 子類重載父類方法
* 父類中不存在該方法,向上轉(zhuǎn)型后,父類是不能引用該方法的
* @param a
* @return void
*/
public void fun1(String a){
System.out.println("JNC 的 Fun1...");
fun2();
}
/**
* 子類重寫父類方法
* 指向子類的父類引用調(diào)用fun2時(shí),必定是調(diào)用該方法
*/
public void fun2(){
System.out.println("JNC 的Fun2...");
}
}
public class Test {
public static void main(String[] args) {
Wine a = new JNC();
a.fun1();
}
}
Output:
Wine 的Fun.....
JNC 的Fun2...
?從程序的運(yùn)行結(jié)果中我們發(fā)現(xiàn),a.fun1()首先是運(yùn)行父類Wine中的fun1().然后再運(yùn)行子類JNC中的fun2()。
?分析:在這個(gè)程序中子類JNC重載了父類Wine的方法fun1(),重寫fun2(),而且重載后的fun1(String a)與 fun1()不是同一個(gè)方法,由于父類中沒有該方法,向上轉(zhuǎn)型后會(huì)丟失該方法,所以執(zhí)行JNC的Wine類型引用是不能引用fun1(String a)方法。而子類JNC重寫了fun2() ,那么指向JNC的Wine引用會(huì)調(diào)用JNC中fun2()方法。
所以對(duì)于多態(tài)我們可以總結(jié)如下:
?指向子類的父類引用由于向上轉(zhuǎn)型了,它只能訪問父類中擁有的方法和屬性,而對(duì)于子類中存在而父類中不存在的方法,該引用是不能使用的,盡管是重載該方法。若子類重寫了父類中的某些方法,在調(diào)用該些方法的時(shí)候,必定是使用子類中定義的這些方法(動(dòng)態(tài)連接、動(dòng)態(tài)調(diào)用)。
?對(duì)于面向?qū)ο蠖眩鄳B(tài)分為編譯時(shí)多態(tài)和運(yùn)行時(shí)多態(tài)。其中編輯時(shí)多態(tài)是靜態(tài)的,主要是指方法的重載,它是根據(jù)參數(shù)列表的不同來區(qū)分不同的函數(shù),通過編輯之后會(huì)變成兩個(gè)不同的函數(shù),在運(yùn)行過程中談不上多態(tài)。而運(yùn)行時(shí)多態(tài)是動(dòng)態(tài)的,它是通過動(dòng)態(tài)綁定來實(shí)現(xiàn)的,也就是我們所說的多態(tài)性。
五、String、StringBuffer、StringBuilder區(qū)別
1、首先說運(yùn)行速度,或者說是執(zhí)行速度,在這方面運(yùn)行速度快慢為:StringBuilder > StringBuffer > String
String最慢的原因:
?String為字符串常量,而StringBuilder和StringBuffer均為字符串變量,即String對(duì)象一旦創(chuàng)建之后該對(duì)象是不可更改的,但后兩者的對(duì)象是變量,是可以更改的。以下面一段代碼為例:
1 String str="abc";
2 System.out.println(str);
3 str=str+"de";
4 System.out.println(str);
?如果運(yùn)行這段代碼會(huì)發(fā)現(xiàn)先輸出“abc”,然后又輸出“abcde”,好像是str這個(gè)對(duì)象被更改了,其實(shí),這只是一種假象罷了,JVM對(duì)于這幾行代碼是這樣處理的,首先創(chuàng)建一個(gè)String對(duì)象str,并把“abc”賦值給str,然后在第三行中,其實(shí)JVM又創(chuàng)建了一個(gè)新的對(duì)象也名為str,然后再把原來的str的值和“de”加起來再賦值給新的str,而原來的str就會(huì)被JVM的垃圾回收機(jī)制(GC)給回收掉了,所以,str實(shí)際上并沒有被更改,也就是前面說的String對(duì)象一旦創(chuàng)建之后就不可更改了。所以,Java中對(duì)String對(duì)象進(jìn)行的操作實(shí)際上是一個(gè)不斷創(chuàng)建新的對(duì)象并且將舊的對(duì)象回收的一個(gè)過程,所以執(zhí)行速度很慢。
?而StringBuilder和StringBuffer的對(duì)象是變量,對(duì)變量進(jìn)行操作就是直接對(duì)該對(duì)象進(jìn)行更改,而不進(jìn)行創(chuàng)建和回收的操作,所以速度要比String快很多。
?另外,有時(shí)候我們會(huì)這樣對(duì)字符串進(jìn)行賦值
1 String str="abc"+"de";
2 StringBuilder stringBuilder=new StringBuilder().append("abc").append("de");
3 System.out.println(str);
4 System.out.println(stringBuilder.toString());
?這樣輸出結(jié)果也是“abcde”和“abcde”,但是String的速度卻比StringBuilder的反應(yīng)速度要快很多,這是因?yàn)榈?行中的操作和String str="abcde";
?是完全一樣的,所以會(huì)很快,而如果寫成下面這種形式
1 String str1="abc";
2 String str2="de";
3 String str=str1+str2;
?那么JVM就會(huì)像上面說的那樣,不斷的創(chuàng)建、回收對(duì)象來進(jìn)行這個(gè)操作了。速度就會(huì)很慢。
2、再來說線程安全
?在線程安全上,StringBuilder是線程不安全的,而StringBuffer是線程安全的
如果一個(gè)StringBuffer對(duì)象在字符串緩沖區(qū)被多個(gè)線程使用時(shí),StringBuffer中很多方法可以帶有synchronized關(guān)鍵字,所以可以保證線程是安全的,但StringBuilder的方法則沒有該關(guān)鍵字,所以不能保證線程安全,有可能會(huì)出現(xiàn)一些錯(cuò)誤的操作。所以如果要進(jìn)行的操作是多線程的,那么就要使用StringBuffer,但是在單線程的情況下,還是建議使用速度比較快的StringBuilder。
3、總結(jié)一下
String:適用于少量的字符串操作的情況
StringBuilder:適用于單線程下在字符緩沖區(qū)進(jìn)行大量操作的情況
StringBuffer:適用多線程下在字符緩沖區(qū)進(jìn)行大量操作的情況
六、什么是內(nèi)部類??jī)?nèi)部類的作用
內(nèi)部類( Inner Class )就定義在另一個(gè)類里面或者一個(gè)方法里面的類。與之對(duì)應(yīng),包含內(nèi)部類的類被稱為外部類。
主要作用如下:
- 內(nèi)部類提供了更好的封裝,可以把內(nèi)部類隱藏在外部類之內(nèi),不允許同一個(gè)包中的其他類訪問該類
- 內(nèi)部類的方法可以直接訪問外部類的所有數(shù)據(jù),包括私有的數(shù)據(jù)
- 內(nèi)部類所實(shí)現(xiàn)的功能使用外部類同樣可以實(shí)現(xiàn),只是有時(shí)使用內(nèi)部類更方便
內(nèi)部類種類:
1、成員內(nèi)部類(它的定義為位于另一個(gè)類的內(nèi)部)
class Circle {
private double radius = 0;
public static int count =1;
public Circle(double radius) {
this.radius = radius;
}
class Draw { //內(nèi)部類
public void drawSahpe() {
System.out.println(radius); //外部類的private成員
System.out.println(count); //外部類的靜態(tài)成員
}
}
}
2、靜態(tài)內(nèi)部類(定義在另一個(gè)類里面的類,只不過在類的前面多了一個(gè)關(guān)鍵字static,但是它不能使用外部類的非static成員變量或者方法)
public class Test {
public static void main(String[] args) {
Outter.Inner inner = new Outter.Inner();
}
}
class Outter {
public Outter() {
}
static class Inner {
public Inner() {
}
}
}
3、方法內(nèi)部類(又名局部?jī)?nèi)部類,它的定義是在一個(gè)方法或者一個(gè)作用域里面的類,它和成員內(nèi)部類的區(qū)別在于局部?jī)?nèi)部類的訪問僅限于方法內(nèi)或者該作用域內(nèi),而且聲明的類不能用public或者private修飾 )
class People{
public People() {
}
}
class Man{
public Man(){
}
public People getWoman(){
class Woman extends People{ //局部?jī)?nèi)部類
int age =0;
}
return new Woman();
}
}
4、匿名內(nèi)部類
scan_bt.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
}
});
七、抽象類和接口區(qū)別
1.語法層面上的區(qū)別
1)抽象類可以提供成員方法的實(shí)現(xiàn)細(xì)節(jié),而接口中只能存在public abstract 方法;
2)抽象類中的成員變量可以是各種類型的,而接口中的成員變量只能是public static final類型的;
3)接口中不能含有靜態(tài)代碼塊以及靜態(tài)方法,而抽象類可以有靜態(tài)代碼塊和靜態(tài)方法;
4)一個(gè)類只能繼承一個(gè)抽象類,而一個(gè)類卻可以實(shí)現(xiàn)多個(gè)接口。
2.設(shè)計(jì)層面上的區(qū)別
1)抽象類是對(duì)一種事物的抽象,即對(duì)類抽象,而接口是對(duì)行為的抽象。抽象類是對(duì)整個(gè)類整體進(jìn)行抽象,包括屬性、行為,但是接口卻是對(duì)類局部(行為)進(jìn)行抽象。舉個(gè)簡(jiǎn)單的例子,飛機(jī)和鳥是不同類的事物,但是它們都有一個(gè)共性,就是都會(huì)飛。那么在設(shè)計(jì)的時(shí)候,可以將飛機(jī)設(shè)計(jì)為一個(gè)類Airplane,將鳥設(shè)計(jì)為一個(gè)類Bird,但是不能將 飛行 這個(gè)特性也設(shè)計(jì)為類,因此它只是一個(gè)行為特性,并不是對(duì)一類事物的抽象描述。此時(shí)可以將 飛行 設(shè)計(jì)為一個(gè)接口Fly,包含方法fly( ),然后Airplane和Bird分別根據(jù)自己的需要實(shí)現(xiàn)Fly這個(gè)接口。然后至于有不同種類的飛機(jī),比如戰(zhàn)斗機(jī)、民用飛機(jī)等直接繼承Airplane即可,對(duì)于鳥也是類似的,不同種類的鳥直接繼承Bird類即可。從這里可以看出,繼承是一個(gè) "是不是"的關(guān)系,而 接口 實(shí)現(xiàn)則是 "有沒有"的關(guān)系。如果一個(gè)類繼承了某個(gè)抽象類,則子類必定是抽象類的種類,而接口實(shí)現(xiàn)則是有沒有、具備不具備的關(guān)系,比如鳥是否能飛(或者是否具備飛行這個(gè)特點(diǎn)),能飛行則可以實(shí)現(xiàn)這個(gè)接口,不能飛行就不實(shí)現(xiàn)這個(gè)接口。
2)設(shè)計(jì)層面不同,抽象類作為很多子類的父類,它是一種模板式設(shè)計(jì)。而接口是一種行為規(guī)范,它是一種輻射式設(shè)計(jì)。什么是模板式設(shè)計(jì)?最簡(jiǎn)單例子,大家都用過ppt里面的模板,如果用模板A設(shè)計(jì)了ppt B和ppt C,ppt B和ppt C公共的部分就是模板A了,如果它們的公共部分需要改動(dòng),則只需要改動(dòng)模板A就可以了,不需要重新對(duì)ppt B和ppt C進(jìn)行改動(dòng)。而輻射式設(shè)計(jì),比如某個(gè)電梯都裝了某種報(bào)警器,一旦要更新報(bào)警器,就必須全部更新。也就是說對(duì)于抽象類,如果需要添加新的方法,可以直接在抽象類中添加具體的實(shí)現(xiàn),子類可以不進(jìn)行變更;而對(duì)于接口則不行,如果接口進(jìn)行了變更,則所有實(shí)現(xiàn)這個(gè)接口的類都必須進(jìn)行相應(yīng)的改動(dòng)。
八、抽象類的意義
抽象類的由來:
我們?cè)诟叨瘸橄笠活愂挛锏臅r(shí)候,由于是提取的此類事物公共行為,但具體到某個(gè)具體的事物的時(shí)候,同一行為又會(huì)有不同的表現(xiàn)。所以我們不應(yīng)該在抽象階段就給出具體的實(shí)現(xiàn),而只給出方法的聲明,也就是不給出方法體,在java中,沒有方法體的方法,我們稱之為抽象方法,而類中有抽象方法,類就必須聲明為抽象類,抽象類由此而來,而且有其存在的必要性。
抽象類的特點(diǎn):
包含抽象方法的類稱為抽象類,但并不意味著抽象類中只能有抽象方法,它和普通類一樣,同樣可以擁有成員變量和普通的成員方法。注意,抽象類和普通類的主要有三點(diǎn)區(qū)別:
- 抽象方法必須為public或者protected(因?yàn)槿绻麨閜rivate,則不能被子類繼承,子類便無法實(shí)現(xiàn)該方法),缺省情況下默認(rèn)為public。
- 抽象類不能用來創(chuàng)建對(duì)象;
- 如果一個(gè)類繼承于一個(gè)抽象類,則子類必須實(shí)現(xiàn)父類的抽象方法。如果子類沒有實(shí)現(xiàn)父類的抽象方法,則必須將子類也定義為為abstract類。
九、抽象類和接口的使用場(chǎng)景介紹
語義上的區(qū)別
首先類描述的是 這個(gè)東西是什么(強(qiáng)調(diào)所屬)?包含了靜態(tài)屬性,靜態(tài)行為 ,屬性和行為。
而接口描述的它能做什么事兒(強(qiáng)調(diào)行為)? 只是 靜態(tài)常量屬性 和 行為
Interface的應(yīng)用場(chǎng)合
A. 類與類之前需要特定的接口進(jìn)行協(xié)調(diào),而不在乎其如何實(shí)現(xiàn)。
B. 作為能夠?qū)崿F(xiàn)特定功能的標(biāo)識(shí)存在,也可以是什么接口方法都沒有的純粹標(biāo)識(shí)。
C. 需要將一組類視為單一的類,而調(diào)用者只通過接口來與這組類發(fā)生聯(lián)系。
D. 需要實(shí)現(xiàn)特定的多項(xiàng)功能,而這些功能之間可能完全沒有任何聯(lián)系。
Abstract class的應(yīng)用場(chǎng)合
一句話,在既需要統(tǒng)一的接口,又需要實(shí)例變量或缺省的方法的情況下,就可以使用它。最常見的有:
A. 定義了一組接口,但又不想強(qiáng)迫每個(gè)實(shí)現(xiàn)類都必須實(shí)現(xiàn)所有的接口。可以用abstract class定義一組方法體,甚至可以是空方法體,然后由子類選擇自己所感興趣的方法來覆蓋。
B. 某些場(chǎng)合下,只靠純粹的接口不能滿足類與類之間的協(xié)調(diào),還必需類中表示狀態(tài)的變量來區(qū)別不同的關(guān)系。abstract的中介作用可以很好地滿足這一點(diǎn)。
C. 規(guī)范了一組相互協(xié)調(diào)的方法,其中一些方法是共同的,與狀態(tài)無關(guān)的,可以共享的,無需子類分別實(shí)現(xiàn);而另一些方法卻需要各個(gè)子類根據(jù)自己特定的狀態(tài)來實(shí)現(xiàn)特定的功能
十、抽象類是否可以沒有方法和屬性?
可以。例如 :
abbstract class Test{
}
十一、接口的意義
1、重要性:在Java語言中, abstract class 和interface 是支持抽象類定義的兩種機(jī)制。正是由于這兩種機(jī)制的存在,才賦予了Java強(qiáng)大的 面向?qū)ο竽芰Α?/p>
2、簡(jiǎn)單、規(guī)范性:如果一個(gè)項(xiàng)目比較龐大,那么就需要一個(gè)能理清所有業(yè)務(wù)的架構(gòu)師來定義一些主要的接口,這些接口不僅告訴開發(fā)人員你需要實(shí)現(xiàn)那些業(yè)務(wù),而且也將命名規(guī)范限制住了(防止一些開發(fā)人員隨便命名導(dǎo)致別的程序員無法看明白)。
3、維護(hù)、拓展性:比如你要做一個(gè)畫板程序,其中里面有一個(gè)面板類,主要負(fù)責(zé)繪畫功能,然后你就這樣定義了這個(gè)類。
?可是在不久將來,你突然發(fā)現(xiàn)這個(gè)類滿足不了你了,然后你又要重新設(shè)計(jì)這個(gè)類,更糟糕是你可能要放棄這個(gè)類,那么其他地方可能有引用他,這樣修改起來很麻煩。
?如果你一開始定義一個(gè)接口,把繪制功能放在接口里,然后定義類時(shí)實(shí)現(xiàn)這個(gè)接口,然后你只要用這個(gè)接口去引用實(shí)現(xiàn)它的類就行了,以后要換的話只不過是引用另一個(gè)類而已,這樣就達(dá)到維護(hù)、拓展的方便性。
4、安全、嚴(yán)密性:接口是實(shí)現(xiàn)軟件松耦合的重要手段,它描敘了系統(tǒng)對(duì)外的所有服務(wù),而不涉及任何具體的實(shí)現(xiàn)細(xì)節(jié)。這樣就比較安全、嚴(yán)密一些(一般軟件服務(wù)商考慮的比較多)。
十二、泛型中extends和super的區(qū)別
1、<? extends T>限定參數(shù)類型的上界:參數(shù)類型必須是T或T的子類型
2、<? super T> 限定參數(shù)類型的下界:參數(shù)類型必須是T或T的超類型
通俗的意思:
- List<? extends T> 是說 這個(gè)list放的是T或者T的子類型的對(duì)象,但是不能確定具體是什么類型,所以可以get(),不能add()(可以add null值)
- List<? super T> 是說這個(gè)list放的是至少是T類型的對(duì)象,所以我可以add T或者T的子類型,但是get得到的類型不確定,所以不能get
extends 示例
static class Food{}
static class Fruit extends Food{}
static class Apple extends Fruit{}
static class RedApple extends Apple{}
List<? extends Fruit> flist = new ArrayList<Apple>();
// complie error:
// flist.add(new Apple());
// flist.add(new Fruit());
// flist.add(new Object());
flist.add(null); // only work for null
List<? extends Frut> 表示 “具有任何從Fruit繼承類型的列表”,編譯器無法確定List所持有的類型,所以無法安全的向其中添加對(duì)象??梢蕴砑觧ull,因?yàn)閚ull 可以表示任何類型。所以List 的add 方法不能添加任何有意義的元素,但是可以接受現(xiàn)有的子類型List<Apple> 賦值。
Fruit fruit = flist.get(0);
Apple apple = (Apple)flist.get(0);
由于,其中放置是從Fruit中繼承的類型,所以可以安全地取出Fruit類型。
flist.contains(new Fruit());
flist.contains(new Apple());
在使用Collection中的contains 方法時(shí),接受Object 參數(shù)類型,可以不涉及任何通配符,編譯器也允許這么調(diào)用。
super 示例
List<? super Fruit> flist = new ArrayList<Fruit>();
flist.add(new Fruit());
flist.add(new Apple());
flist.add(new RedApple());
// compile error:
List<? super Fruit> flist = new ArrayList<Apple>();
List<? super Fruit> 表示“具有任何Fruit超類型的列表”,列表的類型至少是一個(gè) Fruit 類型,因此可以安全的向其中添加Fruit 及其子類型。由于List<? super Fruit>中的類型可能是任何Fruit 的超類型,無法賦值為Fruit的子類型Apple的List<Apple>.
// compile error:
Fruit item = flist.get(0);
因?yàn)椋琇ist<? super Fruit>中的類型可能是任何Fruit 的超類型,所以編譯器無法確定get返回的對(duì)象類型是Fruit,還是Fruit的父類Food 或 Object.
總結(jié)
- <? extends T> 只能用于方法返回,告訴編譯器此返參的類型的最小繼承邊界為T,T和T的父類都能接收,但是入?yún)㈩愋蜔o法確定,只能接受null的傳入(可用于的返回類型限定,不能用于參數(shù)類型限定。)
- <? super T>只能用于限定方法入?yún)?,告訴編譯器入?yún)⒅荒苁荰或其子類型,而返參只能用Object類接收(可用于參數(shù)類型限定,不能用于返回類型限定。)
十三、父類的靜態(tài)方法能否被子類重寫
父類的靜態(tài)方法可以被子類繼承,但是不能重寫。
例:
public class Fu {
public static void show() {
System.out.println("父類的靜態(tài)方法");
}
public void method() {
System.out.println("父類的一般方法");
}
}
public class Zi extends Fu {
public static void main(String[] args) {
Fu fu = new Zi();
fu.show();
fu.method();
}
public static void show() {
System.out.println("子類的靜態(tài)");
}
public void method() {
System.out.println("子類的一般方法");
}
}
輸出
父類的靜態(tài)方法
子類的一般方法
十四、進(jìn)程和線程的區(qū)別
進(jìn)程:指在系統(tǒng)中能獨(dú)立運(yùn)行并作為資源分配的基本單位,它是由一組機(jī)器指令、數(shù)據(jù)和堆棧等組成的,是一個(gè)能獨(dú)立運(yùn)行的活動(dòng)實(shí)體。(資源(CPU時(shí)間、內(nèi)存等)分配的最小單位)
線程:線程是進(jìn)程中的一個(gè)實(shí)體,作為系統(tǒng)調(diào)度和分派的基本單位。Linux下的線程看作輕量級(jí)進(jìn)程。(程序執(zhí)行的最小單位)
區(qū)別:
1、進(jìn)程是資源分配的最小單位,線程是程序執(zhí)行的最小單位。
2、進(jìn)程有自己的獨(dú)立地址空間,每啟動(dòng)一個(gè)進(jìn)程,系統(tǒng)就會(huì)為它分配地址空間,建立數(shù)據(jù)表來維護(hù)代碼段、堆棧段和數(shù)據(jù)段,這種操作非常昂貴。而線程是共享進(jìn)程中的數(shù)據(jù)的,使用相同的地址空間,因此CPU切換一個(gè)線程的花費(fèi)遠(yuǎn)比進(jìn)程要小很多,同時(shí)創(chuàng)建一個(gè)線程的開銷也比進(jìn)程要小很多。
3、 線程之間的通信更方便,同一進(jìn)程下的線程共享全局變量、靜態(tài)變量等數(shù)據(jù),而進(jìn)程之間的通信需要以通信的方式(IPC)進(jìn)行。不過如何處理好同步與互斥是編寫多線程程序的難點(diǎn)。
4、但是多進(jìn)程程序更健壯,多線程程序只要有一個(gè)線程死掉,整個(gè)進(jìn)程也死掉了,而一個(gè)進(jìn)程死掉并不會(huì)對(duì)另外一個(gè)進(jìn)程造成影響,因?yàn)檫M(jìn)程有自己獨(dú)立的地址空間。
十五、final,finally,finalize的區(qū)別
final
final用于修飾類、成員變量和成員方法:
- final修飾的類,不能被繼承(String、StringBuilder、StringBuffer、Math,不可變類),其中所有的方法都不能被重寫,所以不能同時(shí)用abstract和final修飾類(abstract修飾的類是抽象類,抽象類是用于被子類繼承的,和final起相反的作用);
- final修飾的方法不能被重寫,但是子類可以用父類中final修飾的方法;
- final修飾的成員變量是不可變的,如果成員變量是基本數(shù)據(jù)類型,初始化之后成員變量的值不能被改變,如果成員變量是引用類型,那么它只能指向初始化時(shí)指向的那個(gè)對(duì)象,不能再指向別的對(duì)象,但是對(duì)象當(dāng)中的內(nèi)容是允許改變的。
finally
finally通常和try catch搭配使用,保證不管有沒有發(fā)生異常,資源都能夠被釋放(釋放連接、關(guān)閉IO流)。
finalize
finalize是object類中的一個(gè)方法,子類可以重寫finalize()方法實(shí)現(xiàn)對(duì)資源的回收。垃圾回收只負(fù)責(zé)回收內(nèi)存,并不負(fù)責(zé)資源的回收,資源回收要由程序員完成,Java虛擬機(jī)在垃圾回收之前會(huì)先調(diào)用垃圾對(duì)象的finalize方法用于使對(duì)象釋放資源(如關(guān)閉連接、關(guān)閉文件),之后才進(jìn)行垃圾回收,這個(gè)方法一般不會(huì)顯示的調(diào)用,在垃圾回收時(shí)垃圾回收器會(huì)主動(dòng)調(diào)用。
十六、序列化的方式
十七、Serializable 和Parcelable 的區(qū)別
Serializable的使用
import java.io.Serializable;
public class TestSerializable implements Serializable {
public int old;
public String name ;
}
Parcelable的使用
public class TestParcelable implements android.os.Parcelable {
private String name ;
private int old;
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(android.os.Parcel dest, int flags) {
dest.writeString(this.name);
dest.writeInt(this.old);
}
public TestParcelable() {
}
protected TestParcelable(android.os.Parcel in) {
this.name = in.readString();
this.old = in.readInt();
}
public static final android.os.Parcelable.Creator<TestParcelable> CREATOR = new android.os.Parcelable.Creator<TestParcelable>() {
@Override
public TestParcelable createFromParcel(android.os.Parcel source) {
return new TestParcelable(source);
}
@Override
public TestParcelable[] newArray(int size) {
return new TestParcelable[size];
}
};
}
1、在使用內(nèi)存的時(shí)候,Parcelable比Serializable性能高,所以推薦使用Parcelable。
2、Serializable在序列化的時(shí)候會(huì)產(chǎn)生大量的臨時(shí)變量,從而引起頻繁的GC。
3、Parcelable不能使用在要將數(shù)據(jù)存儲(chǔ)在磁盤上的情況,因?yàn)镻arcelable不能很好的保證數(shù)據(jù)的持續(xù)性在外界有變化的情況下。盡管Serializable效率低點(diǎn), 也不提倡用,但在這種情況下,還是建議你用Serializable 。
十八、靜態(tài)屬性和靜態(tài)方法是否可以被繼承?是否可以被重寫?以及原因?
靜態(tài)屬性、靜態(tài)方法性都可以被繼承和隱藏而不能被重寫,因此不能實(shí)現(xiàn)多態(tài),不能實(shí)現(xiàn)父類的引用可以指向不同子類的對(duì)象。非靜態(tài)方法可以被繼承和重寫,因此可以實(shí)現(xiàn)多態(tài)。
靜態(tài)方法和屬性是屬于類的,調(diào)用的時(shí)候直接通過類名.方法名完成對(duì),不需要繼承機(jī)制及可以調(diào)用。如果子類里面定義了靜態(tài)方法和屬性,那么這時(shí)候父類的靜態(tài)方法或?qū)傩苑Q之為"隱藏"。如果你想要調(diào)用父類的靜態(tài)方法和屬性,直接通過父類名.方法或變量名完成,至于是否繼承一說,子類是有繼承靜態(tài)方法和屬性,但是跟實(shí)例方法和屬性不太一樣,存在"隱藏"的這種情況。
十九、靜態(tài)內(nèi)部類的設(shè)計(jì)意圖
?靜態(tài)內(nèi)部類與非靜態(tài)內(nèi)部類之間存在一個(gè)最大的區(qū)別:非靜態(tài)內(nèi)部類在編譯完成之后會(huì)隱含地保存著一個(gè)引用,該引用是指向創(chuàng)建它的外圍內(nèi),但是靜態(tài)內(nèi)部類卻沒有。
沒有這個(gè)引用就意味著:
- 它的創(chuàng)建是不需要依賴于外圍類的。
- 它不能使用任何外圍類的非static成員變量和方法。
二十、成員內(nèi)部類、靜態(tài)內(nèi)部類、局部?jī)?nèi)部類和匿名內(nèi)部類的理解,以及項(xiàng)目中的應(yīng)用
對(duì)內(nèi)部類的理解與項(xiàng)目中的應(yīng)用可以回看問題---六、什么是內(nèi)部類??jī)?nèi)部類的作用
二十一、談?wù)剬?duì)kotlin的理解
優(yōu)點(diǎn):
提高團(tuán)隊(duì)的生產(chǎn)力。基于Java的Kotlin消除了其前身的過時(shí)和繁瑣。語言緊湊、清晰、高效,語法簡(jiǎn)潔直觀。因此,Kotlin可以顯著提高團(tuán)隊(duì)效率:編寫和部署新代碼所需的時(shí)間較少,這也有助于提高代碼可維護(hù)性。
與現(xiàn)有代碼兼容。Kotlin的優(yōu)點(diǎn)之一是其100%的Java互操作性。該語言適用于Java本身,及所有相關(guān)工具和框架,提供了豐富的生態(tài)系統(tǒng)。這也使得可以選擇逐漸遷移到Kotlin,或者在同一個(gè)項(xiàng)目中同時(shí)使用這兩種語言。
可維護(hù)性。由JetBrains構(gòu)建并支持,Kotlin為許多IDE(包括 Android Studio)提供了良好的支持。
更少的錯(cuò)誤。通過更緊湊和更清晰的代碼庫,Kotlin允許在生產(chǎn)中減少錯(cuò)誤,生成更穩(wěn)定的代碼。編譯器在編譯時(shí)檢測(cè)所有可能的錯(cuò)誤,而不是運(yùn)行時(shí)。這使得Kotlin成為比Java更安全的替代品。
可靠。不同于Swift,Kotlin是一種更成熟的語言。在2011年推出后,在最終的1.0版本發(fā)布之前,經(jīng)歷了多個(gè)Alfa和Beta階段,最新版本也與以前的版本反向兼容。
缺點(diǎn):
Kotlin雖然非常接近Java,但在許多方面依然不同。因此,想要切換語言的開發(fā)者仍然面臨著一定的學(xué)習(xí)曲線。因此,如果你決定將Android開發(fā)團(tuán)隊(duì)遷移到Kotlin,則需要對(duì)培訓(xùn)和時(shí)間進(jìn)行額外的投資。一些使用過Kotlin的開發(fā)者反映編譯速度較慢,這是Kotlin的主要缺點(diǎn)之一。
不過,在不同的測(cè)試環(huán)境下結(jié)果有所不同:在某些情況下,Kotlin以編譯速度擊敗Java,但有時(shí)它又比Java慢得多。雖然Kotlin正在迅速成長(zhǎng)為Android開發(fā)的首要語言,但目前仍然只有一個(gè)小型的開發(fā)者社區(qū)。這意味著用于學(xué)習(xí)該語言的資源有限,并且找出在應(yīng)用開發(fā)過程中可能出現(xiàn)的問題的答案會(huì)比較困難。
二十二、閉包和局部?jī)?nèi)部類的區(qū)別
閉包
- 可以包含自由(未綁定到特定對(duì)象)變量的代碼塊;這些變量不是在這個(gè)代碼塊內(nèi)或者任何全局上下文中定義的,而是在定義代碼塊的環(huán)境中定義(局部變量)
- 是引用了自由變量的函數(shù)。這個(gè)函數(shù)通常被定義在另一個(gè)外部函數(shù)中,并且引用了外部函數(shù)中的變量。
- 是一個(gè)可調(diào)用的對(duì)象,它記錄了一些信息,這些信息來自于創(chuàng)建它的作用域。
簡(jiǎn)單理解:
閉包能夠?qū)⒁粋€(gè)方法作為一個(gè)變量去存儲(chǔ),這個(gè)方法有能力去訪問所在類的自由變量。
閉包是 通過“接口與內(nèi)部類實(shí)現(xiàn)的”
package study.ung.edu.ungstudy.javaDemo.testLambda;
public class TestClosure {
public static void main(String[] args) {
Food food = new Food();
food.getEat().eat();
food.getEat().eat();
//在內(nèi)部類是public修飾時(shí),可以通過以下方式
Food foodPub = new Food();
EatActive eat = foodPub.new EatActive();
eat.eat();
}
}
class Food{
public static final String name = "Food";
private static int num = 20;
public Food() {
System.out.println("Delicious Food");
}
public Active getEat() {
return new EatActive();
}
private class EatActive implements Active {
@Override
public void eat() {
if (num == 0) {
System.out.println("吃貨,已經(jīng)吃沒了");
}
num --;
System.out.println("吃貨,你吃了一份了");
}
}
public void currentNum() {
System.out.println("還剩:"+num+"份");
}
}
interface Active{
void eat();
}
而局部?jī)?nèi)部類定義在方法中,這時(shí),class EatActive在Food類中是不可見的,修改getEat方法體:
public Active getEat() {
class EatActive implements Active{
@Override
public void eat() {
if (num == 0) {
System.out.println("吃貨,已經(jīng)吃沒了");
}
num --;
System.out.println("吃貨,你吃了一份了");
}
}
return new EatActive();
}
總結(jié):
1、閉包在類中可見,而在方法內(nèi)部類不可見。(對(duì)類而言,類的定義已經(jīng)"隱藏"在方法里面了,只能通過方法才能實(shí)例化,這種情況就是不可見)
二十三、string 轉(zhuǎn)換成 integer的方式及原理
string 轉(zhuǎn)換成 integer的方式:
String mSmg = "123";
int m = Integer.valueOf(mSmg);
原理
public static Integer valueOf(String s) throws NumberFormatException {
return Integer.valueOf(parseInt(s, 10));
}
//該方法的第二個(gè)參數(shù)表示是基數(shù)(最常用的是十進(jìn)制,還有十六機(jī)制,八進(jìn)制等等).
public static int parseInt(String s, int radix)
throws NumberFormatException
{
/*
* WARNING: This method may be invoked early during VM initialization
* before IntegerCache is initialized. Care must be taken to not use
* the valueOf method.
*/
//如果字符串是空指針,直接拋出異常.
//如果基礎(chǔ)小于2或者大于36的話,拋出異常(這種情況一般不會(huì)出現(xiàn),因?yàn)槲覀冇玫淖疃嗑褪鞘M(jìn)制的了).
if (s == null) {
throw new NumberFormatException("null");
}
//Character.MIN_RADIX=2
if (radix < Character.MIN_RADIX) {
throw new NumberFormatException("radix " + radix +
" less than Character.MIN_RADIX");
}
//Character.MAX_RADIX=36
if (radix > Character.MAX_RADIX) {
throw new NumberFormatException("radix " + radix +
" greater than Character.MAX_RADIX");
}
int result = 0;//記錄返回值
boolean negative = false;//符號(hào)標(biāo)志
int i = 0, len = s.length();//i:字符串位置,len:字符串長(zhǎng)度
int limit = -Integer.MAX_VALUE;//界限
int multmin;//也是一個(gè)界限
int digit;//當(dāng)前字符表示的數(shù)字
if (len > 0) {
char firstChar = s.charAt(0);
//用第一個(gè)字符來判斷是否為正數(shù)還是負(fù)數(shù)
if (firstChar < '0') { // Possible leading "+" or "-"
if (firstChar == '-') {
negative = true;
limit = Integer.MIN_VALUE;
} else if (firstChar != '+')
throw NumberFormatException.forInputString(s);
if (len == 1) // Cannot have lone "+" or "-"
throw NumberFormatException.forInputString(s);
i++;
}
multmin = limit / radix;
while (i < len) {
// Accumulating negatively avoids surprises near MAX_VALUE
//這里使用了Character中的靜態(tài)方法digit,這個(gè)方法比較復(fù)雜,這里先說明它的功能:
//對(duì)于給定的基數(shù),如果是合法的字符(可以轉(zhuǎn)化為數(shù)字),返回該數(shù)字值,否則返回-1.比如digit('3',10)返回//3,digit('a',10)返回-1.
digit = Character.digit(s.charAt(i++),radix);
if (digit < 0) {
throw NumberFormatException.forInputString(s);
}
if (result < multmin) {
throw NumberFormatException.forInputString(s);
}
result *= radix;
if (result < limit + digit) {
throw NumberFormatException.forInputString(s);
}
result -= digit;
}
} else {
throw NumberFormatException.forInputString(s);
}
return negative ? result : -result;
}