Java面試之基礎問題答案口述整理
物件導向的理解
物件導向思想就是在計算機程式設計過程中,把具體事物的屬性特性和行為特徵抽象出來,描述成計算機事件的設計思想。它區別於程式導向的思想,強調的是通過呼叫物件的行為來實現功能,而不是自己一步步去操作實現。舉個洗衣服的例子,採用程式導向的思想去完成洗衣服這個需求,需要一步步實現,首先把衣服脫下來,再找個盆,加入洗衣粉,加水浸泡,開始洗衣服,然後清洗,再擰乾最後晾起來,它強調的是步驟;而採用物件導向的思想去完成這個需求時,我們只需要找到一個物件,然後呼叫物件的行為來實現需求,而這個物件就是全自動洗衣機,使用這個物件的洗衣功能就能完成需求,它不關注中間步驟,強調的是物件。
物件和類的關係:
類是對一類事物的描述,是抽象的;
物件是一類事物的例項,是具體的;
類是物件的模板,物件是類的實體。
Java語言是一種物件導向的語言,包含了三大基本特徵,即封裝、繼承和多型。
封裝就是把一個物件的屬性私有化,對於需要訪問的屬性提供一些可以被外界訪問的公共方法。採用private
關鍵字來完成封裝操作,被private
關鍵字修飾的的成員變數和成員方法,只能在本類中訪問。適當的封裝可以讓程式碼更容易理解和維護,也加強了程式碼的安全性。
繼承就是子類繼承父類的屬性和行為,使得子類物件具有與父類相同的屬性和行為。子類可以直接訪問父類中的非私有的屬性和行為(共性抽取)。同時子類可以擁有自己的屬性和方法實現對父類的擴充套件,同時子類也可以根據需要重寫父類方法實現對父類的增強。Java只支援單繼承。
多型指的是同一行為具有多個不同的表現形式。比如跑這一行為,貓、狗、馬等跑起來是不用一樣的。像這樣同一行為,通過不同事物可以體現不同形態,這就是多型。在Java中有兩種形式可以實現多型:繼承(多個子類對同一方法的重寫)和實現(實現介面時重寫介面中的同一方法)。在Java中多型體現在:父類引用指向子類物件、方法重寫。多型的優點在於使程式編寫更簡單,同時具有良好的擴充套件性。
抽象類和介面
說到抽象類,先說抽象方法,抽象方法就是沒有方法體的方法,我們把包含抽象方法的類叫做抽象類。採用格式修飾符 + abstract class + 類名
來定義一個抽象類。抽象類方法可以是public
、protected
和default
修飾。
- 抽象類不能建立物件;
- 抽象類中,不一定包含抽象方法,但是有抽象方法的類必定是抽象類;
- 抽象類的子類必須重寫父類中所有的抽象方法,除非該子類也是抽象類;
介面是Java中的一種引用型別,是方法的集合,介面內部封裝了方法,包含抽象方法(JDK7及以前),預設方法和靜態方法(JDK8),私有方法(JDK9)。採用修飾符 + interface + 介面名
來定義一個介面。介面不能建立物件,可以被實現(implements
關鍵字)。介面方法預設是public
修飾。實現介面的類必須實現介面中所有抽象方法,否則必須是一個抽象類。通過對介面中抽象方法進行重寫可以進行介面多實現,這是多型的體現。介面的好處在於:1)制定標準。制定標準的目的就是為了讓定義和實現分離,而介面作為完全的抽象,是標準制定的不二之選。2)提供抽象。介面的抽象特性得以讓介面的呼叫者和實現者可以完全的解耦。
類中各部分的初始化順序
一個類中的初始化順序
類內容(靜態變數、靜態程式碼塊)==> 例項內容(成員變數、初始化塊、構造器)
具有繼承關係的兩個類的初始化順序
父類靜態變數、靜態程式碼塊 ==> 子類靜態變數、靜態程式碼塊 ==> 父類成員變數、初始化塊、構造器 ==> 子類成員變數、初始化塊、構造器
Java建立類的例項的幾種方法
-
new
關鍵字User user = new User();
-
反射
// 方法1: Class.forName("全類名") User u1 = (User) Class.forName("com.chiaki.domain.User").newInstance(); // 方法2: 已有例項物件.getclass() User u = new User(); User u2 = u.getClass().newInstance(); // 方法3: 類名.class User u3 = User.class.newInstance();
-
呼叫物件的
clone()
方法,只限於實現了java.lang.Cloneable
介面的類。User user = new User(); User userCopy = (User) user.clone();
-
運用反序列化手段
序列化指將物件狀態轉化為可保持或傳輸的格式的過程,被序列化的物件必須實現
java.io.Serializable
介面(被state
和transient
關鍵字修飾的成員變數不能被序列化)。因此通過反序列化手段可以將流轉化成物件,從而完成物件的建立。
JVM、JDK、JRE的關係
JVM全稱Java Virtual Machine,即Java虛擬機器,是執行Java位元組碼(.class檔案)的虛擬機器。JVM面對不同的OS會有特定的實現,目的在於使用相同的位元組碼檔案,在不同OS都會給出相同結果(一次編譯,處處執行)。
JDK全程Java Development Kit,即Java開發工具包。JDK = Java開發工具(編譯器javac、jar、javadoc等) + JRE。
JRE是Java執行時環境,用於執行已經編譯的Java程式。JRE = JVM + Java核心類庫(java.lang包)。
總結:JDK = Java開發工具 + JRE = Java開發工具 + JVM + Java核心類庫。
Java的資料型別
基本型別:byte[1]、short[2]、int[4]、long[8]、float[4]、double[8]、char[2]、boolean[1]
引用型別:介面、陣列、類
為什麼有了double後還需要long?
- long與double在java中本身都是用64位儲存的,但是他們的儲存方式不同,導致double可儲存的範圍比long大很多;
- long可以準確儲存19位數字,而double只能準備儲存16位數字(實際測試,是17位)。double由於有exp位,可以存16位以上的數字,但是需要以低位的不精確作為代價。如果一個大於17位的long型數字存到double上,就會丟失數字末尾的精度;
- 如果需要高於19位數字的精確儲存,則必須用BigInteger來儲存,當然會犧牲一些效能。
重寫和過載
重寫(Override)的範圍是在繼承關中的子類中,發生在執行期,指的是方法名相同,引數列表相同,返回型別相同,異常範圍小於等於父類,訪問修飾符的範圍大於等於父類;
過載(Overload)的範圍是在同一個類中,發生在編譯期,指的是方法名相同,引數列表不同(順序、個數),返回型別、異常以及訪問修飾符都可以修改。
構造方法可以過載,但不能重寫。
String、StringBuffer 和 StringBuilder
可變性:String類中使用final關鍵字修飾字元陣列,所以String物件不可變;StringBuffer和StringBuilde繼承自AbstractStringBuilder類,沒有使用final修飾,是可變的。
執行緒安全性:String物件不可變,執行緒安全;StringBuffer類中的方法使用synchronized關鍵字修飾,是執行緒安全的;StringBuilder類中的方法沒有進行同步處理,執行緒不安全。
效能:對String物件進行改變時都會生成新的String物件,然後將引用指向新的String物件。StringBuffer和StringBuilder每次都是對自身物件進行操作,但由於StringBuffer需要進行同步處理,其效能比StringBuilder低。
== 與 equals()
== : 判斷兩個物件的地址是否相等,即判斷兩個物件是不是同一個物件。當物件為基本資料型別時,比較的是值;當物件為引用資料型別時,比較的是記憶體地址。
equals() : 判斷兩個物件是否相等。分兩種情況:
- 未重寫:與 == 等價;
- 已重寫:比較兩個物件的內容是否相等。
hashCode() 與 equals()
hashCode()用於獲取物件的雜湊碼,返回一個int型別整數,雜湊碼可以用於確定物件在雜湊表中的索引位置,因此hashCode()在雜湊表中才有用。
equals() : 判斷兩個物件是否相等。分兩種情況:
- 未重寫:與 == 等價;
- 已重寫:比較兩個物件的內容是否相等。
hashCode()和equals()的相關規定:
- 若兩個物件A和B相等,那麼A.equals(B)和B.equals(A)均返回true;
- 若兩個物件A和B相等,那麼A和B的hashCode值也一定相等;
- hashCode值相同的兩個物件,其不一定相等(比如String物件“通話”和“重地”);
- 基於以上3點,在重寫equals()方法後,也必須重寫hashCode()方法;
- 以HashSet為例,先使用hashCode()方法判斷,相同再使用equal()方法。如果只重寫了equals()方法而不重寫hashCode()方法,會造成hashCode()的值不同,而equals()方法判斷結果未true的情況,從而違背上述規定。
程式與執行緒
程式是程式的一次執行過程。系統執行一個程式就是一個程式從建立、執行到消亡的過程。
執行緒是比程式更小的執行單位,一個程式中可以有多個執行緒。執行緒共享程式的堆和方法區的資源,同時執行緒還有私有的程式計數器、虛擬機器棧和本地方法棧。
執行緒的基本狀態:NEW、RUNNABLE、BLOCKED、WAITING、TIME_WAITING、TERMINATED
final、finally和finalize
final關鍵字可以用於修飾變數、方法和類。final修飾變數時,如果是基本資料型別變數,則該變數一旦初始化後便不能修改,如果是引用型別變數,則改變了初始化後不能再指向另一物件;final修飾方法會將方法鎖定,防止任何繼承類對方法進行修改;final修飾類時表明該類不能被繼承,final修飾的類中的所有成員方法都會被隱式指定為final方法。
finally常與try-catch程式碼塊一起使用,通常將一定要執行的程式碼放入finally塊中,比如關閉資源的相關程式碼。無論是否捕獲或者處理異常,finally塊裡的語句都會被執行。
finally塊不被執行的情況:
- finally塊中第一行出現異常;
- 在前面的程式碼中使用了System.exit(int)已退出程式。
- 程式所在的執行緒死亡;
- 關閉CPU。
finalize是屬於Object類的方法,該方法一般由垃圾回收器呼叫。當呼叫System.gc()時,垃圾回收器會呼叫finalize()方法判斷一個物件是否可以回收。
Java中的異常處理
java.lang.Throwable類的兩個重要子類:Error和Exception。Error是程式無法處理的錯誤,主要是JVM出現的問題,比如虛擬機器錯誤(StackOverFlowError和OutOfMemoryError);Exception是程式可以處理的異常,主要有NullPointerException、ArrayIndexOutOfBoundsException、ArithmeticException等
異常處理的方法:
-
指定方法中丟擲指定異常
throw new ArrayIndexOutOfBoundsException("陣列下標越界了!");
-
在方法後宣告可能的異常
throws IOException
-
捕獲異常:try-catch-finally
獲取鍵盤輸入(筆試常用)
通過Scanner:
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
通過BufferedReader:
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String s = br.readLine();
Java的常用IO流
位元組輸入/輸出流:FileInputStrean/FileOutputStream
字元輸入/輸出流:BufferedReader/BufferedWriter、InputStreamReader/OutputStreamWriter
淺拷貝與深拷貝
淺拷貝:對基本資料型別進行值傳遞,對引用資料型別進行引用傳遞般的拷貝;
深拷貝:對基本資料型別進行值傳遞,對引用資料型別,建立一個新的物件,並複製其內容。
如何實現深拷貝?
實現Cloneable介面、反序列化方法