Java基礎知識點(上)
Java基礎部分
基礎部分的順序:
- 基本語法
- 類相關的語法
- 內部類的語法
- 繼承相關的語法
- 異常的語法
- 執行緒的語法
- 集合的語法
- io的語法
- 集合的語法
- 虛擬機器方面的語法
1 、一個”.java” 原始檔中是否可以包括多個類(不是內部類)?有什麼限制?
可以有多個類,但只能有一個 public 的類,並且 public 的類名必須與檔名相一致。
2 、java有沒有goto語句?
java 中的保留字,現在沒有在 java 中使用。
3 、& 和&& 的區別。
相同點:
&和&&都可以用作邏輯與的運算子,表示邏輯與(and) ,當運算子兩邊的表示式的結果都為 true 時,整個運算結果才為 true,否則,只要有一方為 false,則結果為 false。不同點1:
&&還具有短路的功能,即如果第一個表示式為 false,則不再計算第二個表示式,例如,對於 if(str != null&& !str.equals(“”))表示式,當 str 為 null 時,後面的表示式不會執行,所以不會出現 NullPointerException 如果將&&改為&,則會丟擲NullPointerException 異常。不同點2:
If(x==33 &++y>0) y 會增長,If(x==33 && ++y>0)不會增長不同點3:
&還可以用作位運算子,當&操作符兩邊的表示式不是 boolean 型別時,&表示按位與操作,我們通常使用0x0f 來與一個整數進行&運算,來獲取該整數的最低4個 bit 位,例如,0x31 &0x0f 的結果為0x01。備註:這道題先說兩者的共同點,再說出&&和&的特殊之處,並列舉一些經典的例子來表明自己理解透徹深入、實際經驗豐富。
4 、在 JAVA 中如何跳出當前的多重巢狀迴圈?
第一種方法:
在 Java 中,要想跳出多重迴圈,可以在外面的迴圈語句前定義一個標號,然後在裡層迴圈體的程式碼中使用帶有標號的 break 語句,即可跳出外層迴圈。例如,
ok:
for(int i= 0;i < 10;i++) {
for(int j = 0;j < 10;j++) {
System.out.println(“i=” + i + “,j=” + j);
if (j == 5) break ok;
}
}
第二種方法:
另外, 我個人通常並不使用標號這種方式, 而是讓外層的迴圈條件表示式的結果可以受到裡層迴圈體程式碼的控制,例如,要在二維陣列中查詢到某個數字。
int arr[][] ={{1,2,3},{4,5,6,7},{9}};
boolean found = false;
for(int i=0;i<arr.length&& !found;i++) {
for(int j=0;j<arr[i].length;j++){
System.out.println(“i=” + i + “,j=” + j);
if(arr[i][j] ==5) {
found = true;
break;
}
}
}
5 、switch 語句能否作用在 byte 上,能否作用在 long 上,能否作用在 String上?
jdk1.6之前的版本, switch(expr1)的 expr1只能是一個整數表示式或者列舉常量(更大字型) ,整數表示式可以是 int 基本型別或 Integer 包裝型別,由於,byte,short,char 都可以隱含轉換為 int,所以,這些型別以及這些型別的包裝型別也是可以的。顯然,long 和 String 型別都不符合switch 的語法規定, 並且不能被隱式轉換成 int 型別, 所以, 它們不能作用於 swtich 語句中。
jdk1.7釋出版本中,可以在switch語句的表示式中使用String物件。如下:
public String getTypeOfDayWithSwitchStatement(String dayOfWeekArg) {
String typeOfDay;
switch (dayOfWeekArg) {
case "Monday":
typeOfDay = "Start of work week";
break;
case "Tuesday":
case "Wednesday":
case "Thursday":
typeOfDay = "Midweek";
break;
case "Friday":
typeOfDay = "End of work week";
break;
case "Saturday":
case "Sunday":
typeOfDay = "Weekend";
break;
default:
throw new IllegalArgumentException("Invalid day of the week: " + dayOfWeekArg);
}
return typeOfDay;
}
switch語句比較與每個case標籤關聯就好像使用String.equals方法表達的表示式的字串物件;因此,在switch語句的字串物件的比較是區分大小寫的。java編譯器生成更有效的位元組碼從switch語句中使用字串物件比鏈式if-then-else語句。
Oracle官網文件請點選傳送門進入.
6 、short s1 = 1; s1 = s1 + 1; 有什麼錯? short s1 = 1; s1 += 1;?
對於 short s1 = 1; s1 = s1 + 1;由於 s1+1運算時會自動提升表示式的型別,所以結果是 int型,再賦值給 short 型別 s1時,編譯器將報告需要強制轉換型別的錯誤。
對於 short s1 = 1; s1 += 1;由於 +=是 java 語言規定的運算子,java 編譯器會對它進行特殊處理,因此可以正確編譯。
7 、char 型變數中能不能存貯一箇中文漢字? 為什麼?
char 型變數是用來儲存 Unicode 編碼的字元的,unicode 編碼字符集中包含了漢字,所以,char 型變數中當然可以儲存漢字啦。不過,如果某個特殊的漢字沒有被包含在 unicode 編碼字符集中,那麼,這個 char 型變數中就不能儲存這個特殊漢字。補充說明:unicode 編碼佔用兩個位元組,所以,char 型別的變數也是佔用兩個位元組。
備註:後面一部分回答雖然不是在正面回答題目,但是,為了展現自己的學識和表現自己對問題理解的透徹深入,可以回答一些相關的知識,做到知無不言,言無不盡。
8 、用最有效率的方法算出 2 乘以 8 等於 等於幾?
2 << 3,
因為將一個數左移 n 位,就相當於乘以了2的 n 次方,那麼,一個數乘以8只要將其左移3位即可, 而位運算 cpu 直接支援的, 效率最高, 所以, 2乘以8等於幾的最效率的方法是2 << 3。
9 、請設計一個一百億的計算器
首先要明白這道題目的考查點是什麼, 一是大家首先要對計算機原理的底層細節要清楚、 要知道加減法的位運算原理和知道計算機中的算術運算會發生越界的情況, 二是要具備一定的面向物件的設計思想。
首先, 計算機中用固定數量的幾個位元組來儲存的數值, 所以計算機中能夠表示的數值是有一定的範圍的, 為了便於講解和理解, 我們先以 byte 型別的整數為例, 它用1個位元組進行儲存,表示的最大數值範圍為-128到+127。 -1在記憶體中對應的二進位制資料為11111111, 如果兩個-1相加,不考慮 Java 運算時的型別提升,運算後會產生進位,二進位制結果為1,11111110,由於進位後超過了 byte 型別的儲存空間,所以進位部分被捨棄,即最終的結果為11111110,也就是-2,這正好利用溢位的方式實現了負數的運算。-128在記憶體中對應的二進位制資料為10000000,如果兩個-128相加,不考慮 Java 運算時的型別提升,運算後會產生進位,二進位制結果為1,00000000,由於進位後超過了 byte 型別的儲存空間,所以進位部分被捨棄,即最終的結果為00000000,也就是0,這樣的結果顯然不是我們期望的,這說明 計算機中的算術運算是會發生越界情況的,兩個數值的運算結果不能超過計算機中的該型別的數值範圍。 。 由於 Java 中涉及表示式運算時的型別自動提升, 我們無法用 byte 型別來做演示這種問題和現象的實驗,大家可以用下面一個使用整數做實驗的例子程式體驗一下:
int a = Integer.MAX_VALUE;
int b = Integer.MAX_VALUE;
int sum = a + b;
System.out.println(“a=”+a+”,b=”+b+”,sum=”+sum);
先不考慮 long 型別,由於 int 的正數範圍為2的31次方,表示的最大數值約等於2*1000*1000*1000,也就是20億的大小,所以,要實現一個一百億的計算器,我們得自己設計一個類可以用於表示很大的整數, 並且提供了與另外一個整數進行加減乘除的功能, 大概功能如下:
(1)這個類內部有兩個成員變數,一個表示符號,另一個用位元組陣列表示數值的二進位制數
(2)有一個構造方法,把一個包含有多位數值的字串轉換到內部的符號和位元組陣列中
(3)提供加減乘除的功能
public class BigInteger{
int sign;
byte[] val;
public Biginteger(String val) {
sign = ;
val = ;
}
public BigInteger add(BigInteger other) {
}
public BigInteger subtract(BigInteger other) {
}
public BigInteger multiply(BigInteger other){
}
public BigInteger divide(BigInteger other){
}
}
備註:要想寫出這個類的完整程式碼,是非常複雜的,如果有興趣的話,可以參看 jdk 中自帶的 java.math.BigInteger 類的原始碼。面試的人也知道誰都不可能在短時間內寫出這個類的完整程式碼的,他要的是你是否有這方面的概念和意識,他最重要的還是考查你的能力,所以,你不要因為自己無法寫出完整的最終結果就放棄答這道題,你要做的就是你比別人寫得多,證明你比別人強,你有這方面的思想意識就可以了,畢竟別人可能連題目的意思都看不懂,什麼都沒寫, 你要敢於答這道題, 即使只答了一部分, 那也與那些什麼都不懂的人區別出來,拉開了距離, 算是矮子中的高個, 機會當然就屬於你了。 另外, 答案中的框架程式碼也很重要,體現了一些面向物件設計的功底,特別是其中的方法命名很專業,用的英文單詞很精準,這也是能力、經驗、專業性、英語水平等多個方面的體現,會給人留下很好的印象,在程式設計能力和其他方面條件差不多的情況下, 英語好除了可以使你獲得更多機會外, 薪水可以高出一千元。
10 、使用 final關鍵字修飾一個變數時, 是引用不能變, 還是引用的物件不能變?
使用 final 關鍵字修飾一個變數時,是指引用變數不能變,引用變數所指向的物件中的內容還是可以改變的。例如,對於如下語句:
final StringBuffer a=new StringBuffer("immutable");
執行如下語句將報告編譯期錯誤:
a=new StringBuffer("");
但是,執行如下語句則可以通過編譯:
a.append(" broken!");
有人在定義方法的引數時,可能想採用如下形式來阻止方法內部修改傳進來的引數物件:
public void method(final StringBuffer param){
}
實際上,這是辦不到的,在該方法內部仍然可以增加如下程式碼來修改引數物件:
param.append("a");
11 、”==”和 equals 方法究竟有什麼區別?
(單獨把一個東西說清楚,然後再說清楚另一個,這樣,它們的區別自然就出來了,混在一起說,則很難說清楚)
==操作符專門用來比較兩個變數的值是否相等,也就是用於比較變數所對應的記憶體中所儲存的數值是否相同,要比較兩個基本型別的資料或兩個引用變數是否相等,只能用==操作符。
如果一個變數指向的資料是物件型別的,那麼,這時候涉及了兩塊記憶體,物件本身佔用一塊記憶體(堆記憶體) ,變數也佔用一塊記憶體,例如 Objet obj = new Object();變數 obj 是一個記憶體,new Object()是另一個記憶體,此時,變數 obj 所對應的記憶體中儲存的數值就是物件佔用的那塊記憶體的首地址。對於指向物件型別的變數,如果要比較兩個變數是否指向同一個物件,即要看這兩個變數所對應的記憶體中的數值是否相等,這時候就需要用==操作符進行比較。
equals 方法是用於比較兩個獨立物件的內容是否相同,就好比去比較兩個人的長相是否相同,它比較的兩個物件是獨立的。例如,對於下面的程式碼:
String a=new String("foo");
String b=new String("foo");
兩條 new 語句建立了兩個物件,然後用 a/b 這兩個變數分別指向了其中一個物件,這是兩個不同的物件,它們的首地址是不同的,即 a 和 b 中儲存的數值是不相同的,所以,表示式 a==b 將返回 false,而這兩個物件中的內容是相同的,所以,表示式 a.equals(b)將返回true。
在實際開發中,我們經常要比較傳遞進行來的字串內容是否等,例如,String input= …;input.equals(“quit”),許多人稍不注意就使用==進行比較了,這是錯誤的,隨便從網上找幾個專案實戰的教學視訊看看,裡面就有大量這樣的錯誤。記住,字串的比較基本上都是使用 equals 方法。
如果一個類沒有自己定義 equals 方法,那麼它將繼承 Object 類的 equals 方法,Object 類的 equals 方法的實現程式碼如下:
boolean equals(Object o){
return this==o;
}
這說明,如果一個類沒有自己定義 equals 方法,它預設的 equals 方法(從 Object 類繼承的)就是使用==操作符,也是在比較兩個變數指向的物件是否是同一物件,這時候使用equals 和使用==會得到同樣的結果,如果比較的是兩個獨立的物件則總返回 false。如果你編寫的類希望能夠比較該類建立的兩個例項物件的內容是否相同,那麼你必須覆蓋 equals方法,由你自己寫程式碼來決定在什麼情況即可認為兩個物件的內容是相同的。
12 、靜態變數和例項變數的區別?
在語法定義上的區別:靜態變數前要加 static 關鍵字,而例項變數前則不加。
在程式執行時的區別:例項變數屬於某個物件的屬性,必須建立了例項物件,其中的例項變數才會被分配空間,才能使用這個例項變數。靜態變數不屬於某個例項物件,而是屬於類,所以也稱為類變數,只要程式載入了類的位元組碼,不用建立任何例項物件,靜態變數就會被分配空間,靜態變數就可以被使用了。總之,例項變數必須建立物件後才可以通過這個物件來使用,靜態變數則可以直接使用類名來引用。
例如,對於下面的程式,無論建立多少個例項物件,永遠都只分配了一個 staticVar 變數,並且每建立一個例項物件,這個 staticVar 就會加1;但是,每建立一個例項物件,就會分配一個 instanceVar,即可能分配多個 instanceVar,並且每個 instanceVar 的值都只自加了1次。
public class VariantTest{
public static int staticVar = 0;
public int instanceVar = 0;
public VariantTest(){
staticVar++;
instanceVar++;
System.out.println( "staticVar=" + staticVar + ",instanceVar=" + instanceVar);
}
}
備註: 這個解答除了說清楚兩者的區別外, 最後還用一個具體的應用例子來說明兩者的差異,
體現了自己有很好的解說問題和設計案例的能力, 思維敏捷, 超過一般程式設計師, 有寫作能力!
13 、是否可以從一個 static 方法內部發出對非 static 方法的呼叫?
不可以。因為非 static 方法是要與物件關聯在一起的,必須建立一個物件後,才可以在該物件上進行方法呼叫,而 static 方法呼叫時不需要建立物件,可以直接呼叫。也就是說,當一個 static 方法被呼叫時,可能還沒有建立任何例項物件,如果從一個 static 方法中發出對非static 方法的呼叫,那個非 static 方法是關聯到哪個物件上的呢?這個邏輯無法成立,所以,一個 static 方法內部發出對非 static 方法的呼叫是不可以的。
14 、Integer 與 int 的區別(★★★經典題目)
int是java提供的8種原始資料型別之一。 Java為每個原始型別提供了封裝類, Integer是java為 int 提供的封裝類。int 的預設值為0,而 Integer 的預設值為 null,即 Integer 可以區分出未賦值和值為0的區別,int 則無法表達出未賦值的情況,例如,要想表達出沒有參加考試和考試成績為0的區別,則只能使用 Integer。在 JSP 開發中,Integer 的預設為 null,所以用el 表示式在文字框中顯示時,值為空白字串,而 int 預設的預設值為0,所以用 el 表示式在文字框中顯示時,結果為0,所以,int 不適合作為 web 層的表單資料的型別。
在 Hibernate 中,如果將 OID 定義為 Integer 型別,那麼 Hibernate 就可以根據其值是否為null 而判斷一個物件是否是臨時的,如果將 OID 定義為了 int 型別,還需要在 hbm 對映檔案中設定其 unsaved-value 屬性為0。
另外, Integer 提供了多個與整數相關的操作方法, 例如, 將一個字串轉換成整數, Integer中還定義了表示整數的最大值和最小值的常量。
15 、Math.round(11.5) 等於多少? Math.round(-11.5) 等於多少?
Math 類中提供了三個與取整有關的方法:ceil、floor、round,這些方法的作用與它們的英文名稱的含義相對應,例如,ceil 的英文意義是天花板,該方法就表示向上取整,Math.ceil(11.3)的結果為12,Math.ceil(-11.3)的結果是-11;floor 的英文意義是地板,該方法就表示向下取整,Math.ceil(11.6)的結果為11,Math.ceil(-11.6)的結果是-12;最難掌握的是round 方法,它表示“四捨五入”,演算法為 Math.floor(x+0.5),即將原來的數字加上0.5後再向下取整,所以,Math.round(11.5)的結果為12,Math.round(-11.5)的結果為-11。
16 、下面的程式碼有什麼不妥之處?
1. if(username.equals("zxx"){}
username 可能為 NULL,會報空指標錯誤;改為”zxx”.equals(username)
2. int x = 1;
return x==1?true:false;
這個改成 return x==1;就可以!
17 、請說出作用域 public ,private ,protected
這四個作用域的可見範圍如下表所示。
說明:如果在修飾的元素上面沒有寫任何訪問修飾符,則表示 friendly。
作用域 | 當前類 | 同一包(package) | 子孫類 | 其他包(package) |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
friendly | √ | √ | × | × |
private | √ | × | × | × |
備註:只要記住了有4種訪問許可權,4個訪問範圍,然後將全選和範圍在水平和垂直方向上分別按排從小到大或從大到小的順序排列,就很容易畫出上面的圖了。
18 、Overload 和 和 Override 的區 的區別。Overloaded 的方法是否可以改變返回值 的方法是否可以改變返回值的型別 ?
Overload 是過載的意思,Override 是覆蓋的意思,也就是重寫。
過載 Overload 表示同一個類中可以有多個名稱相同的方法,但這些方法的引數列表各不相同(即引數個數或型別不同) 。
重寫 Override 表示子類中的方法可以與父類中的某個方法的名稱和引數完全相同,通過子類建立的例項物件呼叫這個方法時, 將呼叫子類中的定義方法, 這相當於把父類中定義的那個完全相同的方法給覆蓋了, 這也是面向物件程式設計的多型性的一種表現。 子類覆蓋父類的方法時, 只能比父類拋 出更少的異常,或者是丟擲父類丟擲的異常的子異常,因為子類可以解決父類的一些問題,不能比父類有更多的問題。子類方法的訪問許可權只能比父類的更大,不能更小。如果父類的方法是 private 型別,那麼,子類則不存在覆蓋的限制,相當於子類中增加了一個全新的方法。
至於 Overloaded 的方法是否可以改變返回值的型別這個問題,要看你倒底想問什麼呢?這個題目很模糊。如果幾個 Overloaded 的方法的引數列表不一樣,它們的返回者型別當然也可以不一樣。但我估計你想問的問題是:如果兩個方法的引數列表完全一樣,是否可以讓它們的返回值不同來實現過載 Overload。這是不行的,我們可以用反證法來說明這個問題,因為我們有時候呼叫一個方法時也可以不定義返回結果變數, 即不要關心其返回結果, 例如,我們呼叫 map.remove(key)方法時,雖然 remove 方法有返回值,但是我們通常都不會定義接收返回結果的變數, 這時候假設該類中有兩個名稱和引數列表完全相同的方法, 僅僅是返回型別不同,java 就無法確定程式設計者倒底是想呼叫哪個方法了,因為它無法通過返回結果型別來判斷。
override 可以翻譯為覆蓋,從字面就可以知道,它是覆蓋了一個方法並且對其重寫,以求達到不同的作用。 對我們來說最熟悉的覆蓋就是對介面方法的實現, 在介面中一般只是對方法進行了宣告, 而我們在實現時, 就需要實現介面宣告的所有方法。 除了這個典型的用法以外,我們在繼承中也可能會在子類覆蓋父類中的方法。在覆蓋要注意以下的幾點:
1、覆蓋的方法的標誌必須要和被覆蓋的方法的標誌完全匹配,才能達到覆蓋的效果;
2、覆蓋的方法的返回值必須和被覆蓋的方法的返回一致;
3、覆蓋的方法所丟擲的異常必須和被覆蓋方法的所丟擲的異常一致,或者是其子類;
4、被覆蓋的方法不能為 private,否則在其子類中只是新定義了一個方法,並沒有對其進行覆蓋。overload 對我們來說可能比較熟悉,可以翻譯為過載,它是指我們可以定義一些名稱相同的方法,通過定義不同的輸入引數來區分這些方法,然後再呼叫時,VM 就會根據不同的引數樣式,來選擇合適的方法執行。在使用過載要注意以下的幾點:
1、在使用過載時只能通過不同的引數樣式。例如,不同的引數型別,不同的引數個數,不同的引數順序(當然,同一方法內的幾個引數型別必須不一樣,例如可以是 fun(int,float),但是不能為 fun(int,int)) ;
2、不能通過訪問許可權、返回型別、丟擲的異常進行過載;
3、方法的異常型別和數目不會對過載造成影響;
4、對於繼承來說,如果某一方法在父類中是訪問許可權是 priavte,那麼就不能在子類對其進行過載,如果定義的話,也只是定義了一個新方法,而不會達到過載的效果。
19 、構造器 Constructor 是否可被 override?
每個class都必須有自己的constructor,它不能從父類中繼承。一個class可以有多個,但至少有一個, 如果沒有顯示宣告建構函式,編譯器會提供一個預設的不帶引數的建構函式,注意的事,只要有一個顯示 宣告的constructor,將不會提供default constructor.
- constructor 不可以有返回值,如果有返回值將會被視為一個普通的方法,不過與類同名。但是在constructor 裡可以寫return語句,public A(){return;} 是允許的。
- 在子類呼叫其constructor的時候,compliler會自動為其加上super();所以如果父類中沒有顯示的宣告不帶參的建構函式,將會有編譯錯誤。
- 在constructor裡可以用this()/super()呼叫自己/父類中的其他建構函式,呼叫自己會有recursive invocation error.注意的是,this();或者super()都必須寫在其第一句話,所以,this();和super();顯然不能同時被呼叫。
-構造器 Constructor 不能被繼承,因此不能重寫 Override ,但可以被過載 Overload 。
20 、介面是否可繼承介面? 抽象類是否可實現(implements) 介面?抽象類是否可 抽象類是否可繼承具體類 繼承具體類(concrete class)? 抽象類中是否可以有靜態的 main 方法?
從定義上講:
Java介面是一系列方法的宣告,是一些方法特徵的集合,一個介面只有方法的特徵沒有方法的實現,因此這些方法可以在不同的地方被不同的類實現,而這些實現可以具有不同的行為(功能)。使用了關鍵詞abstract宣告的類叫作“抽象類”。如果一個類裡包含了一個或多個抽象方法,類就必須指定成abstract(抽象)。“抽象方法”,屬於一種不完整的方法,只含有一個宣告,沒有方法主體。
介面可以繼承介面。抽象類可以實現(implements)介面,抽象類可以繼承具體類。抽象類中可以有靜態的 main 方法。
備註: 只要明白了介面和抽象類的本質和作用, 這些問題都很好回答, 你想想, 如果你是 java語言的設計者,你是否會提供這樣的支援,如果不提供的話,有什麼理由嗎?如果你沒有道理不提供,那答案就是肯定的了。
只有記住抽象類與普通類的唯一區別:就是不能建立例項物件和允許有 abstract 方法。
21 、寫 clone()方法時,通常都有一行程式碼,是什麼 方法時,通常都有一行程式碼,是什麼?
clone 有預設行為,
super.clone();
因為首先要把父類中的成員複製到位,然後才是複製自己的成員。
22 、面向物件的特徵有哪些方面
計算機軟體系統是現實生活中的業務在計算機中的對映, 而現實生活中的業務其實就是一個個物件協作的過程。 面向物件程式設計就是按現實業務一樣的方式將程式程式碼按一個個物件進行組織和編寫, 讓計算機系統能夠識別和理解用物件方式組織和編寫的程式程式碼, 這樣就可以把現實生活中的業務物件對映到計算機系統中。面向物件的程式語言有4個主要的特徵。
封裝:封裝是保證軟體部件具有優良的模組性的基礎, 封裝的目標就是要實現軟體部件的“高內聚、低耦合”,防止程式相互依賴性而帶來的變動影響。在面向物件的程式語言中,物件是封裝的最基本單位,面向物件的封裝比傳統語言的封裝更為清晰、更為有力。面向物件的封裝就是把描述一個物件的屬性和行為的程式碼封裝在一個“模組”中,也就是一個類中,屬性用變數定義,行為用方法進行定義,方法可以直接訪問同一個物件中的屬性。通常情況下, 只要記住讓變數和訪問這個變數的方法放在一起,將一個類中的成員變數全部定義成私有的,只有這個類自己的方法才可以訪問到這些成員變數,這就基本上 實現物件的封裝,就很容易找出要分配到這個類上的方法了,就基本上算是會面向物件的程式設計了。把握一個原則:把對同一事物進行操作的方法和相關的方法放在同一個類中,把方法和它操作的資料放在同一個類中。
例如,人要在黑板上畫圓,這一共涉及三個物件:人、黑板、圓,畫圓的方法要分配給哪個物件呢?由於畫圓需要使用到圓心和半徑, 圓心和半徑顯然是圓的屬性, 如果將它們在類中定義成了私有的成員變數,那麼,畫圓的方法必須分配給圓,它才能訪問到圓心和半徑這兩個屬性,人以後只是呼叫圓的畫圓方法、表示給圓發給訊息而已,畫圓這個方法不應該分配在人這個物件上, 這就是面向物件的封裝性,即將 物件封裝成一個高度自治和相對封閉的個體,物件狀態(屬性)由這個物件自己的行為(方法)來讀取和改變 。一個更便於理解的例子就是,司機將火車剎住了,剎車的動作是分配給司機,還是分配給火車,顯然,應該分配給火車, 因為司機自身是不可能有那麼大的力氣將一個火車給停下來的, 只有火車自己才能完成這一動作, 火車需要呼叫內部的離合器和剎車片等多個器件協作才能完成剎車這個動作,司機剎車的過程只是給火車發了一個訊息,通知火車要執行剎車動作而已。抽象:
抽象就是找出一些事物的相似和共性之處, 然後將這些事物歸為一個類, 這個類只考慮這些事物的相似和共性之處, 並且會忽略與當前主題和目標無關的那些方面, 將注意力集中在與當前目標有關的方面。例如,看到一隻螞蟻和大象,你能夠想象出它們的相同之處,那就是抽象。抽象包括行為抽象和狀態抽象兩個方面。例如,定義一個 Person 類,如下:
class Person{
String name;
int age;
}
人本來是很複雜的事物,有很多方面,但因為當前系統只需要瞭解人的姓名和年齡,所以上面定義的類中只包含姓名和年齡這兩個屬性, 這就是一種抽像, 使用抽象可以避免考慮一些與目標無關的細節。 我對抽象的理解就是不要用顯微鏡去看一個事物的所有方面, 這樣涉及的內容就太多了,而是要善於劃分問題的邊界,當前系統需要什麼,就只考慮什麼。
繼承:在定義和實現一個類的時候, 可以在一個已經存在的類的基礎之上來進行, 把這個已經存在的類所定義的內容作為自己的內容, 並可以加入若干新的內容, 或修改原來的方法使之更適合特殊的需要,這就是繼承。繼承是子類自動共享父類資料和方法的機制,這是類之間的一種關係,提高了軟體的可重用性和可擴充套件性。
多型:
多型是指程式中定義的引用變數所指向的具體型別和通過該引用變數發出的方法呼叫在程式設計時並不確定, 而是在程式執行期間才確定, 即一個引用變數倒底會指向哪個類的例項物件,該引用變數發出的方法呼叫到底是哪個類中實現的方法,必須在由程式執行期間才能決定。因為在程式執行時才確定具體的類,這樣,不用修改源程式程式碼,就可以讓引用變數繫結到各種不同的類實現上, 從而導致該引用呼叫的具體方法隨之改變, 即不修改程式程式碼就可以改變程式執行時所繫結的具體程式碼,讓程式可以選擇多個執行狀態,這就是多型性。多型性增強了軟體的靈活性和擴充套件性。例如,下面程式碼中的 UserDao 是一個介面,它定義引用變數 userDao 指向的例項物件由 daofactory.getDao()在執行的時候返回,有時候指向的是UserJdbcDao 這個實現,有時候指向的是 UserHibernateDao 這個實現,這樣,不用修改原始碼,就可以改變 userDao 指向的具體類實現,從而導致 userDao.insertUser()方法呼叫的具體程式碼也隨之改變,即有時候呼叫的是 UserJdbcDao 的 insertUser 方法,有時候呼叫的是 UserHibernateDao 的 insertUser 方法:
UserDao userDao =daofactory.getDao();
userDao.insertUser(user);
比喻:人吃飯,你看到的是左手,還是右手?
23 、java 中實現多型的機制是什麼?(★★★經典題目)
靠的是父類或介面定義的引用變數可以指向子類或具體實現類的例項物件, 而程式呼叫的方法在執行期才動態繫結, 就是引用變數所指向的具體例項物件的方法, 也就是記憶體里正在執行的那個物件的方法,而不是引用變數的型別中定義的方法。
24 、abstract class 和 和 interface 有什麼區別?(★★經典題目)
含有 abstract 修飾符的 class 即為抽象類,abstract 類不能建立的例項物件。含有 abstract方法的類必須定義為abstract class, abstract class類中的方法不必是抽象的。 abstract class類中定義抽象方法必須在具體(Concrete)子類中實現,所以,不能有抽象構造方法或抽象靜態方法。如果的子類沒有實現抽象父類中的所有抽象方法,那麼子類也必須定義為 abstract型別。
介面(interface)可以說成是抽象類的一種特例,介面中的所有方法都必須是抽象的。介面中的方法定義預設為 public abstract 型別, 介面中的成員變數型別預設為 public static final。下面比較一下兩者的語法區別:
1.抽象類可以有構造方法,介面中不能有構造方法。
2.抽象類中可以有普通成員變數,介面中沒有普通成員變數
3.抽象類中可以包含非抽象的普通方法,介面中的所有方法必須都是抽象的,不能有非抽象的普通方法。
4. 抽象類中的抽象方法的訪問型別可以是 public,protected 和(預設型別,雖然eclipse 下不報錯,但應該也不行) ,但介面中的抽象方法只能是 public 型別的,並且預設即為 public abstract 型別。
5. 抽象類中可以包含靜態方法,介面中不能包含靜態方法
6. 抽象類和介面中都可以包含靜態成員變數,抽象類中的靜態成員變數的訪問型別可以任意,但介面中定義的變數只能是 public static final 型別,並且預設即為 public static final 型別。
7. 一個類可以實現多個介面,但只能繼承一個抽象類。下面接著再說說兩者在應用上的區別:
介面更多的是在系統架構設計方法發揮作用, 主要用於定義模組之間的通訊契約。 而抽象類
在程式碼實現方面發揮作用,可以實現程式碼的重用,例如,模板方法設計模式是抽象類的一個
典型應用,假設某個專案的所有 Servlet 類都要用相同的方式進行許可權判斷、記錄訪問日誌
和處理異常,那麼就可以定義一個抽象的基類,讓所有的 Servlet 都繼承這個抽象基類,在
抽象基類的 service 方法中完成許可權判斷、記錄訪問日誌和處理異常的程式碼,在各個子類中
public abstract classBaseServlet extends HttpServlet{
public final void service(HttpServletRequest request,HttpServletResponse response) throws IOExcetion,ServletException {
//記錄訪問日誌
//進行許可權判斷
if(具有許可權){
try{doService(request,response);
}catch(Excetpion e) {
//記錄異常資訊
}
}
}
protected abstract void doService(HttpServletRequest request,HttpServletResponse response) throws IOExcetion,ServletException;
//注意訪問許可權定義成 protected,顯得既專業,又嚴謹,因為它是專門給子類用的
}
public class MyServlet1 extends BaseServlet{
protected voiddoService(HttpServletRequest request, HttpServletResponse response) throws IOExcetion,ServletException
{
//本 Servlet 只處理的具體業務邏輯程式碼
}
}
父類方法中間的某段程式碼不確定,留給子類幹,就用模板方法設計模式。
備註: 這道題的思路是先從總體解釋抽象類和介面的基本概念, 然後再比較兩者的語法細節,最後再說兩者的應用區別。比較兩者語法細節區別的條理是:先從一個類中的構造方法、普通成員變數和方法(包括抽象方法) ,靜態變數和方法,繼承性等6個方面逐一去比較回答,接著從第三者繼承的角度的回答, 特別是最後用了一個典型的例子來展現自己深厚的技術功底。
深入理解Java的介面和抽象類
對於面向物件程式設計來說,抽象是它的一大特徵之一。在Java中,可以通過兩種形式來體現OOP的抽象:介面和抽象類。這兩者有太多相似的地方,又有太多不同的地方。很多人在初學的時候會以為它們可以隨意互換使用,但是實際則不然。今天我們就一起來學習一下Java中的介面和抽象類。下面是本文的目錄大綱:
一.抽象類
二.介面
三.抽象類和介面的區別
一.抽象類
在瞭解抽象類之前,先來了解一下抽象方法。抽象方法是一種特殊的方法:它只有宣告,而沒有具體的實現。抽象方法的宣告格式為:
1. abstract void fun();
抽象方法必須用abstract關鍵字進行修飾。如果一個類含有抽象方法,則稱這個類為抽象類,抽象類必須在類前用abstract關鍵字修飾。因為抽象類中含有無具體實現的方法,所以不能用抽象類建立物件。
下面要注意一個問題:在《JAVA程式設計思想》一書中,將抽象類定義為“包含抽象方法的類”,但是後面發現如果一個類不包含抽象方法,只是用abstract修飾的話也是抽象類。也就是說抽象類不一定必須含有抽象方法。個人覺得這個屬於鑽牛角尖的問題吧,因為如果一個抽象類不包含任何抽象方法,為何還要設計為抽象類?所以暫且記住這個概念吧,不必去深究為什麼。
1 [public] abstract class ClassName {
2 abstract void fun();
3 }
從這裡可以看出,抽象類就是為了繼承而存在的,如果你定義了一個抽象類,卻不去繼承它,那麼等於白白建立了這個抽象類,因為你不能用它來做任何事情。對於一個父類,如果它的某個方法在父類中實現出來沒有任何意義,必須根據子類的實際需求來進行不同的實現,那麼就可以將這個方法宣告為abstract方法,此時這個類也就成為abstract類了。
包含抽象方法的類稱為抽象類,但並不意味著抽象類中只能有抽象方法,它和普通類一樣,同樣可以擁有成員變數和普通的成員方法。注意,抽象類和普通類的主要有三點區別:
1)抽象方法必須為public或者protected(因為如果為private,則不能被子類繼承,子類便無法實現該方法),預設情況下預設為public。
2)抽象類不能用來建立物件;
3)如果一個類繼承於一個抽象類,則子類必須實現父類的抽象方法。如果子類沒有實現父類的抽象方法,則必須將子類也定義為為abstract類。
在其他方面,抽象類和普通的類並沒有區別。
二.介面
介面,英文稱作interface,在軟體工程中,介面泛指供別人呼叫的方法或者函式。從這裡,我們可以體會到Java語言設計者的初衷,它是對行為的抽象。在Java中,定一個介面的形式如下:
[public] interface InterfaceName {
}
介面中可以含有 變數和方法。但是要注意,介面中的變數會被隱式地指定為public static final變數(並且只能是public static final變數,用private修飾會報編譯錯誤),而方法會被隱式地指定為public abstract方法且只能是public abstract方法(用其他關鍵字,比如private、protected、static、 final等修飾會報編譯錯誤),並且介面中所有的方法不能有具體的實現,也就是說,介面中的方法必須都是抽象方法。從這裡可以隱約看出介面和抽象類的區別,介面是一種極度抽象的型別,它比抽象類更加“抽象”,並且一般情況下不在介面中定義變數。
要讓一個類遵循某組特地的介面需要使用implements關鍵字,具體格式如下:
class ClassName implements Interface1,Interface2,[....]{
}
可以看出,允許一個類遵循多個特定的介面。如果一個非抽象類遵循了某個介面,就必須實現該介面中的所有方法。對於遵循某個介面的抽象類,可以不實現該介面中的抽象方法。
三.抽象類和介面的區別
1.語法層面上的區別
1)抽象類可以提供成員方法的實現細節,而介面中只能存在public abstract 方法;2)抽象類中的成員變數可以是各種型別的,而介面中的成員變數只能是public static final型別的;
3)介面中不能含有靜態程式碼塊以及靜態方法,而抽象類可以有靜態程式碼塊和靜態方法;
4)一個類只能繼承一個抽象類,而一個類卻可以實現多個介面。
2.設計層面上的區別
1)抽象類是對一種事物的抽象,即對類抽象,而介面是對行為的抽象。抽象類是對整個類整體進行抽象,包括屬性、行為,但是介面卻是對類區域性(行為)進行抽象。舉個簡單的例子,飛機和鳥是不同類的事物,但是它們都有一個共性,就是都會飛。那麼在設計的時候,可以將飛機設計為一個類Airplane,將鳥設計為一個類Bird,但是不能將 飛行 這個特性也設計為類,因此它只是一個行為特性,並不是對一類事物的抽象描述。此時可以將 飛行 設計為一個介面Fly,包含方法fly( ),然後Airplane和Bird分別根據自己的需要實現Fly這個介面。然後至於有不同種類的飛機,比如戰鬥機、民用飛機等直接繼承Airplane即可,對於鳥也是類似的,不同種類的鳥直接繼承Bird類即可。從這裡可以看出,繼承是一個 “是不是”的關係,而 介面 實現則是 “有沒有”的關係。如果一個類繼承了某個抽象類,則子類必定是抽象類的種類,而介面實現則是有沒有、具備不具備的關係,比如鳥是否能飛(或者是否具備飛行這個特點),能飛行則可以實現這個介面,不能飛行就不實現這個介面。
2)設計層面不同,抽象類作為很多子類的父類,它是一種模板式設計。而介面是一種行為規範,它是一種輻射式設計。什麼是模板式設計?最簡單例子,大家都用過ppt裡面的模板,如果用模板A設計了ppt B和ppt C,ppt B和ppt C公共的部分就是模板A了,如果它們的公共部分需要改動,則只需要改動模板A就可以了,不需要重新對ppt B和ppt C進行改動。而輻射式設計,比如某個電梯都裝了某種報警器,一旦要更新報警器,就必須全部更新。也就是說對於抽象類,如果需要新增新的方法,可以直接在抽象類中新增具體的實現,子類可以不進行變更;而對於介面則不行,如果介面進行了變更,則所有實現這個介面的類都必須進行相應的改動。下面看一個網上流傳最廣泛的例子:門和警報的例子:門都有open( )和close( )兩個動作,此時我們可以定義通過抽象類和介面來定義這個抽象概念:
abstract class Door {
public abstract void open();
public abstract void close();
}
或者:
interface Door {
public abstract void open();
public abstract void close();
}
但是現在如果我們需要門具有報警alarm( )的功能,那麼該如何實現?下面提供兩種思路:
1)將這三個功能都放在抽象類裡面,但是這樣一來所有繼承於這個抽象類的子類都具備了報警功能,但是有的門並不一定具備報警功能;
2)將這三個功能都放在接口裡面,需要用到報警功能的類就需要實現這個介面中的open( )和close( ),也許這個類根本就不具備open( )和close( )這兩個功能,比如火災報警器。從這裡可以看出, Door的open() 、close()和alarm()根本就屬於兩個不同範疇內的行為,open()和close()屬於門本身固有的行為特性,而alarm()屬於延伸的附加行為。因此最好的解決辦法是單獨將報警設計為一個介面,包含alarm()行為,Door設計為單獨的一個抽象類,包含open和close兩種行為。再設計一個報警門繼承Door類和實現Alarm介面。
interface Alram {
void alarm();
}
abstract class Door {
void open();
void close();
}
class AlarmDoor extends Door implements Alarm {
void oepn() {
//....
}
void close() {
//....
}
void alarm() {
//....
}
}
25 、abstract 的 的 method 是否可同時是 static, 是否可同時是 native,是否可同 ,是否可同時是 時是 synchronized?(★★★深度)
1、static method:靜態方法是使用公共記憶體空間的,就是說所有物件都可以直接引用,不需要建立物件再使用該方法。
abstract 的 method 不可以是 static 的,因為抽象的方法是要被子類實現的,而 static 與子類扯不上關係!
2、native 方法表示該方法要用另外一種依賴平臺的程式語言實現的, 不存在著被子類實現的問題,所以,它也不能是抽象的,不能與 abstract 混用。例如,FileOutputSteam 類要硬體打交道,底層的實現用的是作業系統相關的 api 實現,例如,在 windows 用 c 語言實現的,所以,檢視 jdk 的原始碼,可以發現 FileOutputStream 的 open 方法的定義如下:
private native void open(String name) throws FileNotFoundException;
如果我們要用 java 呼叫別人寫的 c 語言函式,我們是無法直接呼叫的,我們需要按照 java的要求寫一個 c 語言的函式, 又我們的這個 c 語言函式去呼叫別人的 c 語言函式。 由於我們的 c 語言函式是按 java 的要求來寫的,我們這個 c 語言函式就可以與 java 對接上,java 那邊的對接方式就是定義出與我們這個 c 函式相對應的方法,java 中對應的方法不需要寫具體的程式碼,但需要在前面宣告 native。
3、對於synchronized的概念,這裡有篇詳細的講解,點選傳送門進入。
關於 synchronized 與 abstract 合用的問題,我覺得也不行, 因為在我幾年的學習和開發中,從來沒見到過這種情況,並且我覺得 synchronized 應該是作用在一個具體的方法上才有意義。而且,方法上的 synchronized 同步所使用的同步鎖物件是 this,而抽象方法上無法確定 this 是什麼。(文章由牛客整理,本人在引用時很多地方進行的修改和增添,由於能力有限、時間倉促,文章不免出錯,敬請指出)