Java 基本功之(二)Java 面向对象
轉載自https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/basis/Java%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md#1121-jvm
Java 面向對象
- 2.1. 類和對象
- 2.1.1. 面向對象和面向過程的區別(重要)
- 2.1.2. 構造器 Constructor 是否可被 override?
- 2.1.3. 在 Java 中定義一個不做事且沒有參數的構造方法的作用
- 2.1.4. 成員變量與局部變量的區別有哪些?
- 2.1.5. 創建一個對象用什么運算符?對象實體與對象引用有何不同?
- 2.1.6. 一個類的構造方法的作用是什么? 若一個類沒有聲明構造方法,該程序能正確執行嗎? 為什么?
- 2.1.7. 構造方法有哪些特性?
- 2.1.8. 在調用子類構造方法之前會先調用父類沒有參數的構造方法,其目的是?
- 2.1.9. 對象的相等與指向他們的引用相等,兩者有什么不同?
- 2.2. 面向對象三大特征
- 2.2.1. 封裝
- 2.2.2. 繼承
- 2.2.3. 多態
- 2.3. 修飾符
- 2.3.1. 在一個靜態方法內調用一個非靜態成員為什么是非法的?
- 2.3.2. 靜態方法和實例方法有何不同
- 2.3.3. 常見關鍵字總結:static,final,this,super
- 2.4. 接口和抽象類
- 2.4.1. 接口和抽象類的區別是什么?(重要)
- 2.5. 其它重要知識點
- 2.5.1. String StringBuffer 和 StringBuilder 的區別是什么? String 為什么是不可變的?(重要)
- 2.5.2. Object 類的常見方法總結(重要)
- 2.5.3. == 與 equals(重要)
- 2.5.4. hashCode 與 equals (重要)
- 2.5.4.1. hashCode()介紹
- 2.5.4.2. 為什么要有 hashCode
- 2.5.4.3. hashCode()與 equals()的相關規定
- 2.5.5. Java 序列化中如果有些字段不想進行序列化,怎么辦?
- 2.5.6. 獲取用鍵盤輸入常用的兩種方法
2.1. 類和對象
2.1.1. 面向對象和面向過程的區別(重要)
面向過程 :面向過程性能比面向對象高。 因為類調用時需要實例化,開銷比較大,比較消耗資源,所以當性能是最重要的考量因素的時候,比如單片機、嵌入式開發、Linux/Unix 等一般采用面向過程開發。但是,面向過程沒有面向對象易維護、易復用、易擴展。
面向對象 :面向對象易維護、易復用、易擴展。 因為面向對象有封裝、繼承、多態性的特性,所以可以設計出低耦合的系統,使系統更加靈活、更加易于維護。但是,面向對象性能比面向過程低。
參見 issue : 面向過程 :面向過程性能比面向對象高??
這個并不是根本原因,面向過程也需要分配內存,計算內存偏移量,Java 性能差的主要原因并不是因為它是面向對象語言,而是 Java 是半編譯語言,最終的執行代碼并不是可以直接被 CPU 執行的二進制機械碼。
而面向過程語言大多都是直接編譯成機械碼在電腦上執行,并且其它一些面向過程的腳本語言性能也并不一定比 Java 好。
2.1.2. 構造器 Constructor 是否可被 override?
Constructor 不能被 override(重寫),但是可以 overload(重載),所以你可以看到一個類中有多個構造函數的情況。
2.1.3. 在 Java 中定義一個不做事且沒有參數的構造方法的作用
Java 程序在執行子類的構造方法之前,如果沒有用 super()來調用父類特定的構造方法,則會調用父類中“沒有參數的構造方法”。因此,如果父類中只定義了有參數的構造方法,而在子類的構造方法中又沒有用 super()來調用父類中特定的構造方法,則編譯時將發生錯誤,因為 Java 程序在父類中找不到沒有參數的構造方法可供執行。解決辦法是在父類里加上一個不做事且沒有參數的構造方法。
2.1.4. 成員變量與局部變量的區別有哪些?
從語法形式上看:成員變量是屬于類的,而局部變量是在代碼塊或方法中定義的變量或是方法的參數;成員變量可以被 public,private,static 等修飾符所修飾,而局部變量不能被訪問控制修飾符及 static 所修飾;但是,成員變量和局部變量都能被 final 所修飾。
從變量在內存中的存儲方式來看:如果成員變量是使用static修飾的,那么這個成員變量是屬于類的,如果沒有使用static修飾,這個成員變量是屬于實例的。而對象存在于堆內存,局部變量則存在于棧內存。
從變量在內存中的生存時間上看:成員變量是對象的一部分,它隨著對象的創建而存在,而局部變量隨著方法的調用而自動消失。
成員變量如果沒有被賦初值:則會自動以類型的默認值而賦值(一種情況例外:被 final 修飾的成員變量也必須顯式地賦值),而局部變量則不會自動賦值。
2.1.5. 創建一個對象用什么運算符?對象實體與對象引用有何不同?
new 運算符,new 創建對象實例(對象實例在堆內存中),對象引用指向對象實例(對象引用存放在棧內存中)。一個對象引用可以指向 0 個或 1 個對象(一根繩子可以不系氣球,也可以系一個氣球);一個對象可以有 n 個引用指向它(可以用 n 條繩子系住一個氣球)。
2.1.6. 一個類的構造方法的作用是什么? 若一個類沒有聲明構造方法,該程序能正確執行嗎? 為什么?
主要作用是完成對類對象的初始化工作。可以執行。因為一個類即使沒有聲明構造方法也會有默認的不帶參數的構造方法。如果我們自己添加了類的構造方法(無論是否有參),Java 就不會再添加默認的無參數的構造方法了,這時候,就不能直接 new 一個對象而不傳遞參數了,所以我們一直在不知不覺地使用構造方法,這也是為什么我們在創建對象的時候后面要加一個括號(因為要調用無參的構造方法)。如果我們重載了有參的構造方法,記得都要把無參的構造方法也寫出來(無論是否用到),因為這可以幫助我們在創建對象的時候少踩坑。
2.1.7. 構造方法有哪些特性?
2.1.8. 在調用子類構造方法之前會先調用父類沒有參數的構造方法,其目的是?
幫助子類做初始化工作。
2.1.9. 對象的相等與指向他們的引用相等,兩者有什么不同?
對象的相等,比的是內存中存放的內容是否相等。而引用相等,比較的是他們指向的內存地址是否相等。
2.2. 面向對象三大特征
2.2.1. 封裝
封裝是指把一個對象的狀態信息(也就是屬性)隱藏在對象內部,不允許外部對象直接訪問對象的內部信息。但是可以提供一些可以被外界訪問的方法來操作屬性。就好像我們看不到掛在墻上的空調的內部的零件信息(也就是屬性),但是可以通過遙控器(方法)來控制空調。如果屬性不想被外界訪問,我們大可不必提供方法給外界訪問。但是如果一個類沒有提供給外界訪問的方法,那么這個類也沒有什么意義了。就好像如果沒有空調遙控器,那么我們就無法操控空凋制冷,空調本身就沒有意義了(當然現在還有很多其他方法 ,這里只是為了舉例子)。
public class Student {private int id;//id屬性私有化private String name;//name屬性私有化//獲取id的方法public int getId() {return id;}//設置id的方法public void setId(int id) {this.id = id;}//獲取name的方法public String getName() {return name;}//設置name的方法public void setName(String name) {this.name = name;} }2.2.2. 繼承
不同類型的對象,相互之間經常有一定數量的共同點。例如,小明同學、小紅同學、小李同學,都共享學生的特性(班級、學號等)。同時,每一個對象還定義了額外的特性使得他們與眾不同。例如小明的數學比較好,小紅的性格惹人喜愛;小李的力氣比較大。繼承是使用已存在的類的定義作為基礎建立新類的技術,新類的定義可以增加新的數據或新的功能,也可以用父類的功能,但不能選擇性地繼承父類。通過使用繼承,可以快速地創建新的類,可以提高代碼的重用,程序的可維護性,節省大量創建新類的時間 ,提高我們的開發效率。
關于繼承如下 3 點請記住:
2.2.3. 多態
多態,顧名思義,表示一個對象具有多種的狀態。具體表現為父類的引用指向子類的實例。
多態的特點:
- 對象類型和引用類型之間具有繼承(類)/實現(接口)的關系;
- 對象類型不可變,引用類型可變;
- 方法具有多態性,屬性不具有多態性;
- 引用類型變量發出的方法調用的到底是哪個類中的方法,必須在程序運行期間才能確定;
- 多態不能調用“只在子類存在但在父類不存在”的方法;
- 如果子類重寫了父類的方法,真正執行的是子類覆蓋的方法,如果子類沒有覆蓋父類的方法,執行的是父類的方法。
2.3. 修飾符
2.3.1. 在一個靜態方法內調用一個非靜態成員為什么是非法的?
由于靜態方法可以不通過對象進行調用,因此在靜態方法里,不能調用其他非靜態變量,也不可以訪問非靜態變量成員。
2.3.2. 靜態方法和實例方法有何不同
在外部調用靜態方法時,可以使用"類名.方法名"的方式,也可以使用"對象名.方法名"的方式。而實例方法只有后面這種方式。也就是說,調用靜態方法可以無需創建對象。
靜態方法在訪問本類的成員時,只允許訪問靜態成員(即靜態成員變量和靜態方法),而不允許訪問實例成員變量和實例方法;實例方法則無此限制。
2.3.3. 常見關鍵字總結:static,final,this,super
詳見筆主的這篇文章: https://snailclimb.gitee.io/javaguide/#/docs/java/basic/final,static,this,super
2.4. 接口和抽象類
2.4.1. 接口和抽象類的區別是什么?(重要)
備注:
總結一下 jdk7~jdk9 Java 中接口概念的變化(相關閱讀):
2.5. 其它重要知識點
2.5.1. String StringBuffer 和 StringBuilder 的區別是什么? String 為什么是不可變的?(重要)
簡單的來說:String 類中使用 final 關鍵字修飾字符數組來保存字符串,private final char value[],所以 String 對象是不可變的。
補充(來自issue 675):在 Java 9 之后,String 類的實現改用 byte 數組存儲字符串 private final byte[] value;而 StringBuilder 與 StringBuffer 都繼承自 AbstractStringBuilder 類,在 AbstractStringBuilder 中也是使用字符數組保存字符串char[]value 但是沒有用 final 關鍵字修飾,所以這兩種對象都是可變的。
StringBuilder 與 StringBuffer 的構造方法都是調用父類構造方法也就是AbstractStringBuilder 實現的,大家可以自行查閱源碼。
AbstractStringBuilder.java
abstract class AbstractStringBuilder implements Appendable, CharSequence {/*** The value is used for character storage.*/char[] value;/*** The count is the number of characters used.*/int count;AbstractStringBuilder(int capacity) {value = new char[capacity];}}線程安全性
String 中的對象是不可變的,也就可以理解為常量,線程安全。AbstractStringBuilder 是 StringBuilder 與 StringBuffer 的公共父類,定義了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 對方法加了同步鎖或者對調用的方法加了同步鎖,所以是線程安全的。StringBuilder 并沒有對方法進行加同步鎖,所以是非線程安全的。
性能
每次對 String 類型進行改變的時候,都會生成一個新的 String 對象,然后將指針指向新的 String 對象。StringBuffer 每次都會對 StringBuffer 對象本身進行操作,而不是生成新的對象并改變對象引用。相同情況下使用 StringBuilder 相比使用 StringBuffer 僅能獲得 10%~15% 左右的性能提升,但卻要冒多線程不安全的風險。
對于三者使用的總結:
2.5.2. Object 類的常見方法總結(重要)
Object 類是一個特殊的類,是所有類的父類。它主要提供了以下 11 個方法:
public final native Class<?> getClass() //native方法,用于返回當前運行時對象的Class對象,使用了final關鍵字修飾,故不允許子類重寫。public native int hashCode() //native方法,用于返回對象的哈希碼,主要使用在哈希表中,比如JDK中的HashMap。 public boolean equals(Object obj) //用于比較2個對象的內存地址是否相等,String類對該方法進行了重寫用戶比較字符串的值是否相等。protected native Object clone() throws CloneNotSupportedException //naitive方法,用于創建并返回當前對象的一份拷貝。一般情況下,對于任何對象 x,表達式 x.clone() != x 為true,x.clone().getClass() == x.getClass() 為true。Object本身沒有實現Cloneable接口,所以不重寫clone方法并且進行調用的話會發生CloneNotSupportedException異常。public String toString() //返回類的名字@實例的哈希碼的16進制的字符串。建議Object所有的子類都重寫這個方法。public final native void notify() //native方法,并且不能重寫。喚醒一個在此對象監視器上等待的線程(監視器相當于就是鎖的概念)。如果有多個線程在等待只會任意喚醒一個。public final native void notifyAll() //native方法,并且不能重寫。跟notify一樣,唯一的區別就是會喚醒在此對象監視器上等待的所有線程,而不是一個線程。public final native void wait(long timeout) throws InterruptedException //native方法,并且不能重寫。暫停線程的執行。注意:sleep方法沒有釋放鎖,而wait方法釋放了鎖 。timeout是等待時間。public final void wait(long timeout, int nanos) throws InterruptedException //多了nanos參數,這個參數表示額外時間(以毫微秒為單位,范圍是 0-999999)。 所以超時的時間還需要加上nanos毫秒。public final void wait() throws InterruptedException //跟之前的2個wait方法一樣,只不過該方法一直等待,沒有超時時間這個概念protected void finalize() throws Throwable { } //實例被垃圾回收器回收的時候觸發的操作2.5.3. == 與 equals(重要)
== : 它的作用是判斷兩個對象的地址是不是相等。即,判斷兩個對象是不是同一個對象(基本數據類型比較的是值,引用數據類型比較的是內存地址)。
equals() : 它的作用也是判斷兩個對象是否相等。但它一般有兩種使用情況:
情況 1:類沒有覆蓋 equals() 方法。則通過 equals() 比較該類的兩個對象時,等價于通過“==”比較這兩個對象。
情況 2:類覆蓋了 equals() 方法。一般,我們都覆蓋 equals() 方法來比較兩個對象的內容是否相等;若它們的內容相等,則返回 true (即,認為這兩個對象相等)。
舉個例子:
說明:
- String 中的 equals 方法是被重寫過的,因為 object 的 equals 方法是比較的對象的內存地址,而 String 的 equals 方法比較的是對象的值。
- 當創建 String 類型的對象時,虛擬機會在常量池中查找有沒有已經存在的值和要創建的值相同的對象,如果有就把它賦給當前引用。如果沒有就在常量池中重新創建一個 String 對象。
2.5.4. hashCode 與 equals (重要)
面試官可能會問你:“你重寫過 hashcode 和 equals 么,為什么重寫 equals 時必須重寫 hashCode 方法?”
2.5.4.1. hashCode()介紹
hashCode() 的作用是獲取哈希碼,也稱為散列碼;它實際上是返回一個 int 整數。這個哈希碼的作用是確定該對象在哈希表中的索引位置。hashCode() 定義在 JDK 的 Object.java 中,這就意味著 Java 中的任何類都包含有 hashCode() 函數。
散列表存儲的是鍵值對(key-value),它的特點是:能根據“鍵”快速的檢索出對應的“值”。這其中就利用到了散列碼!(可以快速找到所需要的對象)
2.5.4.2. 為什么要有 hashCode
我們先以“HashSet 如何檢查重復”為例子來說明為什么要有 hashCode: 當你把對象加入 HashSet 時,HashSet 會先計算對象的 hashcode 值來判斷對象加入的位置,同時也會與該位置其他已經加入的對象的 hashcode 值作比較,如果沒有相符的 hashcode,HashSet 會假設對象沒有重復出現。但是如果發現有相同 hashcode 值的對象,這時會調用 equals()方法來檢查 hashcode 相等的對象是否真的相同。如果兩者相同,HashSet 就不會讓其加入操作成功。如果不同的話,就會重新散列到其他位置。(摘自我的 Java 啟蒙書《Head first java》第二版)。這樣我們就大大減少了 equals 的次數,相應就大大提高了執行速度。
通過我們可以看出:hashCode() 的作用就是獲取哈希碼,也稱為散列碼;它實際上是返回一個 int 整數。這個哈希碼的作用是確定該對象在哈希表中的索引位置。hashCode()在散列表中才有用,在其它情況下沒用。在散列表中 hashCode() 的作用是獲取對象的散列碼,進而確定該對象在散列表中的位置。
2.5.4.3. hashCode()與 equals()的相關規定
推薦閱讀:Java hashCode() 和 equals()的若干問題解答
2.5.5. Java 序列化中如果有些字段不想進行序列化,怎么辦?
對于不想進行序列化的變量,使用 transient 關鍵字修飾。
transient 關鍵字的作用是:阻止實例中那些用此關鍵字修飾的的變量序列化;當對象被反序列化時,被 transient 修飾的變量值不會被持久化和恢復。transient 只能修飾變量,不能修飾類和方法。
2.5.6. 獲取用鍵盤輸入常用的兩種方法
- 方法 1:通過 Scanner
- 方法 2:通過 BufferedReader
總結
以上是生活随笔為你收集整理的Java 基本功之(二)Java 面向对象的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 剑指 Offer 11. 旋转数组的最小
- 下一篇: Java 基本功之(三)Java 核心技