Lab2 - ADT&OOP 回顧總結

2022113415-罗昊然發表於2024-05-08

Lab2-ADT & OOP 回顧

在忙於幹活與忙於划水的薛定諤疊加態中度過一個月後想起了部落格,考慮到如果自己再不回顧之前的實驗+複習軟體構造內容就要和肯尼迪和安倍晉三一桌打復活賽結果會很悲慘的情況,決定開啟部落格開始碼字。

現在對Lab2- ADT & OOP的內容進行回顧

目錄
  • Lab2-ADT & OOP 回顧
    • Static與非Static的區別(變數與方法)
      • Static/非Static變數
      • 方法上
    • 重寫Hashcode和Equals方法
    • 總結
    • 參考資料

Static與非Static的區別(變數與方法)

Static/非Static變數

在C語言的學習中已經接觸過,static修飾下變數全域性可用,可以在當前整個程式原始碼中任意位置供其他函式呼叫。
在所有函式外初始化的資料結構預設視為被static修飾的變數。

# include <stdio.h>
# include <stdlib.h>
# include <windows.h>

// 計時用 基於含Windows api的標頭檔案
// 這裡初始化的資料是整個原始碼內可用的 
LARGE_INTEGER temp; // 用於儲存效能計數器的值
LONGLONG start, end, freq; // 用於儲存開始時間、結束時間和頻率
int origin_num[N];
int num1[N];
int main()
{
    int static overall_mark = 0; // 函式中也可使用
    // ...
}

而在Python,Java等物件導向的程式語言中,類內static修飾的變數稱為類變數/全域性變數/成員變數。如同C中static修飾的變數在執行之初初始化載入一樣,這些變數是在類被載入時候就被初始化的,只要類存在,類變數就存在。

非static修飾的成員變數是在物件new出來的時候劃分儲存空間,是與具體的物件繫結的,該成員變數僅為當前物件所擁有的。

在引用類變數時,我們直接透過類名.變數名呼叫,物件在引用例項變數時只能透過物件名.變數名呼叫。在類中呼叫成員變數時直接呼叫或者以類名.變數名方式呼叫,例項變數則用this或者直接呼叫。

例如:

public class Test1 {
    private static String testKey = "Init Key";
    private String testObjectKey;
    public Test1(){
        testObjectKey = "Init Object Key";
    }
    
    public static void main(String[] args){
        Test1 testObject = new Test1();
        System.out.println(testObject.testKey);
        System.out.println(Test1.testObjectKey);
    }
}

在上面的程式碼中,main方法裡第2行程式碼會提示Warning:

無法從 static 上下文引用非 static 欄位 'testObjectKey'

第三行程式碼則直接在testObjectKey顯示紅色,並在主方法內第一行程式碼處報錯:

透過例項引用訪問 static 成員 'Test1.testKey'

在之前的Lab1的P3中,如下:

public class FriendshipGraph {
    // Object "person" existed in current Friendship graph.
    private static Set<String> personSet;

    public FriendshipGraph(){
        personSet = new HashSet<>();
    }

筆者對FriendshipGraph類內用於統計圖內已有Person頂點的集合屬性personSet錯誤地新增了static關鍵字,並在主方法中直接透過屬性名呼叫personSet。這樣做就導致在測試類內先執行的測試方法會將之前的圖資料保留,從而影響後面測試方法的結果。

筆者並未意識到這個static關鍵字的問題,選擇在構造方法內將這個共用的靜態控制元件重新初始化為一新的空集合,這樣可以解決先執行的測試方法結果影響後面測試方法的結果的問題,但一旦遇到同時需要統計多個友誼圖的情況,便會出現後一個友誼圖覆蓋前一個友誼圖的問題。

最終修改程式碼,刪去static關鍵字並在主方法中初始化一個FriendshipGraph物件,用物件名.屬性名訪問每個物件單獨的personSet屬性用於統計,問題解決。

private static Set personSet; -> private static Set personSet;

(in main method)

FriendshipGraph friendshipGraph = new FriendshipGraph();

personSet -> friendshipGraph.personSet

方法上

靜態方法使用static關鍵字修飾,屬於類而不屬於物件,在例項化物件之前我們便可以透過“類名.方法名”呼叫靜態方法。

  • 在靜態方法中,可以呼叫靜態方法。
  • 在靜態方法中,不能呼叫非靜態方法。
  • 在靜態方法中,可以引用static變數。
  • 在靜態方法中,不能引用非static變數。
  • 在靜態方法中,不能使用super和this關鍵字(涉及父類與當前類的物件,靜態方法不能先於這些物件產生)。

非靜態方法不含有static關鍵字,屬於物件而不屬於類。必須透過new關鍵字建立物件後再透過物件呼叫。

  • 在普通方法中,可以呼叫普通方法。
  • 在普通方法中,可以呼叫靜態方法。
  • 在普通方法中,可以引用類變數和成員變數。
  • 在普通方法中,可以使用super和this關鍵字。

靜態方法的生命週期跟相應的類一樣長,類內帶static的方法與變數會隨著類的定義而被分配和裝載入記憶體中。直至執行緒結束,這些靜態的屬性和方法才會被銷燬。

非靜態方法的生命週期和類的例項化物件一樣長,只有當類例項化了一個物件,非靜態方法才會被建立,而當這個物件被銷燬時,非靜態方法也馬上被銷燬。

main方法不帶static關鍵字時,在IDEA環境下會提示Warning:

方法 'main' 沒有簽名 'public static void main(String[])'

如果main方法非靜態,就意味著Java虛擬機器在執行的時候需要先建立一個物件才能呼叫main方法,我們為作為程式的入口的main方法再建立一個額外的物件顯然是沒有必要的。

重寫Hashcode和Equals方法

在做物件間的比較時,我們通常採用equals方法來判斷它們是否相等。查閱Object的euqals實現:

public boolean equals(Object obj) {
    return (this == obj);
}

這裡的符號“==”是這樣定義的:

  • 對於基本型別(int,byte,boolean,long,short,double,float,char),==操作比較值相等
  • 對於物件型別,比較兩個物件的記憶體地址。
  • 對於封裝型別和基本型別間的比較,編譯器會轉換為基本型別後再比較

一個拼接好的hello world的字串和一個new出來的hello world作比較,它們的內容是一致的而地址值不同,這時候用equals判斷它們不相等,這顯然是不符合我們需要的。

重寫equals方法,目的就是實現符合我們需要的“合乎情理”的情景。如果不重寫的話,原始碼中就僅僅會比較倆個物件的地址值。

那麼hashcode的重寫又是否是必要的?只重寫Hashcode和equals其中的一個方法可行嗎?

首先一般程式開發中有規定:兩個物件如果使用equals比較為true,那麼它們的hash值必須一樣,因而我們重寫equals方法的同時,都會重寫hashCode。

只重寫equals方法是可行的,但對於資料量龐大的物件,比如這樣一個情況:在做倆個物件A和B的比較時,B物件是存放在集合裡的,且集合裡的資料多達幾萬條,呼叫equals方法來一個一個比較嘛效率就必然會出現明顯的下降。這個時候,hashcode方法直接進行比較顯然更為合理。

只重寫hashcode方法,由於規定物件equals比較為true則hash值必須一樣,也就是說hashcode相等是兩個物件實際相等的必要條件,因此是隻重寫hashcode方法是不成立的。

對於大多數我們需要進行比較,涉及判斷是否相等的類,需要進行equals與hashcode的重寫。

@Override
    public int hashCode() {
        final int prime = 31;
        int res = 1;
        res = prime * result + ((Key1 == null) ? 0 : Key1.hashCode());
        res = prime * result + ((Key2 == null) ? 0 : Key2.hashCode());
        return res;
    }

hashcode方法的重寫規則可以參考資料結構課中雜湊部分的內容結合實際情況來確定。一般來說可以在重寫時使用“31”作為係數進行hashcode的構造,這是因為31是質數,乘法構造可以使hashcode保持唯一,並且31作為一個質數具有如下性質:

n * 31 等價於 (n * 2 * 2* 2 * 2 * 2 - n) or (n << 5) - n

總結

  • static是類的,是一開始就載入的,是陪你走過歲月長河的
  • 非static是物件的,是隨new物件隨產生的,是生不帶來死不帶走的
  • 對於大多數我們需要進行比較,涉及判斷是否相等的類,需要進行equals與hashcode的重寫。

例如P1中的FriendshipGraph,Person類涉及比較需要進行equals與hashcode重寫,至於FriendshipGraph我們並不考慮需要對兩個圖進行比較的情況,因而重寫它的equals與hashcode方法是不必要的。

參考資料

https://blog.csdn.net/LINDAMAN00/article/details/97898803
https://zhuanlan.zhihu.com/p/258751142
https://zhuanlan.zhihu.com/p/60871182

相關文章