day10_物件導向(屬性丶方法)①

java_pedestrian發表於2020-12-22

通過上次的學習,我們得知:類 = 屬性 + 方法,而屬性描述的是狀態,方法描述的是行為動作。下面我們來詳細學習一下類和方法。

類的成員之一:屬性

程式碼示例

public class Demo {
    //宣告private變數 age
    private int age;
    //宣告public變數 name
    public String name = "Lila";
}

變數的分類:

  • 成員變數在方法體外,類體內宣告的變數稱為成員變數。
  • 區域性變數在方法體內部宣告的變數稱為區域性變數。

區域性變數除形參外,均需顯式初始化。

成員變數(屬性)和區域性變數的區別

物件屬性的預設初始化賦值 

當一個物件被建立時,會對其中各種型別的成員變數自動進行初始化賦值。除了基本資料型別之外的變數型別都是引用型別,如上面的Person及前面講過的陣列。

引用和物件怎麼區分

  • “引用”:是儲存物件記憶體地址的一個變數。只要這個變數中儲存的是一個物件的記憶體地址,那麼這個變數就叫做“引用”。
  • “物件”:堆裡new出來的。

當例項變數是一個引用型別

  • 必須先建立物件,通過“引用.”的方式訪問。

類的成員之二:方 法(method)

什麼是方法(method、函式):

  • 方法是類或物件行為特徵的抽象,用來完成某個功能操作。在某些語言中也稱為函式或過程。
  • 將功能封裝為方法的目的是,可以實現程式碼重用,簡化程式碼
  • Java裡的方法不能獨立存在,所有的方法必須定義在類裡。

 程式碼示例

package demo02;
/*
    注意:
        程式開始執行的時候是先執行main方法。
        因為main方法是一個入口。

        在java語言中所有的方法體中的程式碼都必須遵循自上而下的順序依次逐行執行。
        這個必須記住。

        main方法不需要程式設計師手動呼叫,是由JVM呼叫的。
        但是除了main方法之外其他的方法,都需要程式設計師
        手動呼叫,方法只有呼叫的時候才會執行,方法不呼叫
        是不會執行的。
*/
public class MethodTest{
    // 主方法。入口。
    public static void main(String[] args){ // 自上而下依次逐行執行。
        // 計算100和200的求和。
        sumInt(100, 200);
        // 計算666和888的求和。
        sumInt(666, 888);
        // 計算111和222的和
        sumInt(111, 222);
    }

    // 專門在這個類體當中定義一個方法,這個方法專門來完成求和。
    // x y z在以下的sumInt方法中都屬於區域性變數
    // 區域性變數有一個特點:方法結束之後,區域性變數佔用的記憶體會自動釋放。
    public static void sumInt(int x, int y){ // 方法裡的程式碼:自上而下的順序依次逐行執行。
        int z = x + y;
        System.out.println(x + "+" + y + "=" + z);
    }

}

格式詳解:

  • 修飾符此項是可選項,不是必須的,專門負責控制方法的訪問許可權和在記憶體中儲存的位置
  • 返回值型別:可以是任何型別,只要是java中合法的資料型別就行,資料型別包括基本資料型別和引用資料型別,也就是說返回值型別可以是:byte shortint long float double boolean char String......
  • 什麼是返回值?返回值一般指的是一個方法執行結束之後的結果。結果通常是一個資料,所以被稱為“值”,而且還叫“返回值”。方法就是為了完成某個特定的功能,方法結束之後大部分情況下都是有一個結果的,而體現結果的一般都是資料。資料得有型別。這就是返回值型別。
    • 方法執行結束之後的返回值實際上是給呼叫者了。誰呼叫方法返回值就就返回給誰。
    • 當一個方法執行結束不返回任何值的時候,返回值型別也不能空白,必須寫上void關鍵字。所以void表示該方法執行結束後不返回任何結果。
    • 如果返回值型別“不是void”,那麼你在方法體執行結束的時候必須使用"return 值;"這樣的語句來完成“值”的返回,如果沒有“return 值;”這樣的語句那麼編譯器會報錯。  return 值; 這樣的語句作用是什麼?作用是“返回值”,返回方法的執行結果。
    • 只要有“return”關鍵字的語句執行,當前方法必然結束。return只要執行,當前所在的方法結束,記住:不是整個程式結束。
    • 如果返回值型別是void,那麼在方法體當中不能有“return 值;”這樣的語句。但是可以有“return;”語句。這個語句“return;”的作用就是用來終止當前方法的。除了void之外,剩下的都必須有“return 值;”這樣的語句。
  • 方法名:此項需要是合法的識別符號,開發規範中要求方法名首字母小寫,後面每個單詞首字母大寫,遵循駝峰命名方式,見名知意,例如:login、getUsername、findAllUser 等。 
  • 形式引數列表(int a, int b):此項又被稱為形參,其實每一個形參都是“區域性變數”,形參的個數為 0~N 個,如果是多個引數,則採用半形“,”進行分隔,形參中起決定性作用的

    是引數的資料型別,引數名就是變數名,變數名是可以修改的,也就是說(int a , int b)也可以寫成(int x , int y)。形式引數列表中的每一個引數都是“區域性變數”,方法結束之後記憶體釋放。形參的個數是:0~N個。

  • 方法體:由一對兒大括號括起來,在形參的後面,這個大括號當中的是實現功能的核心程式碼,方法體由 java 語句構成,方法體當中的程式碼只能遵循自上而下的順序依次逐行執行,不能跳行執行,核心程式碼在執行過程中如果需要外部提供資料,則通過形參進行獲取。

方法的分類:

按照是否有形參及返回值

當一個方法宣告之後,我們應該如何去讓這個方法執行呢,當然,這個時候就需要親自去呼叫這個方法了,方法通過方法名去呼叫,且只有被呼叫的方法才會執行

靜態方法呼叫的語法格式

  • 類名.方法名(實際引數列表);

成員方法呼叫的語法格式:

  • 物件名稱.方法名(實際引數列表);

下面我們就以靜態方法為列,進行程式碼說明

package demo02;

public class MethodTest {
    public static void main(String[] args) {
        MethodTest.sumInt(100, 200);
        MethodTest.sumDouble(1.0, 2.0);
    }

    public static void sumInt(int x, int y) {
        System.out.println(x + "+" + y + "=" + (x + y));//100+200=300
    }

    public static void sumDouble(double a, double b) {
        System.out.println(a + "+" + b + "=" + (a + b));//1.0+2.0=3.0
    }
}

需要注意的是,方法在呼叫的時候,實際傳給這個方法的資料被稱為實際引數列表,簡稱實參,java 語法中有這樣的規定:實參和形參必須一一對應,所謂的一一對應就是,個數要一樣,資料型別要對應相同。例如:實參(100 , 200)對應的形參(int x , int y),如果不是一一對應則編譯器就會報錯。當然也可能會存在自動型別轉換,例如:實參(100 , 200)也可以傳遞給這樣的形參(long a , long b)。

注意事項:

  • 當一個方法有返回值的時候,我們可以使用變數來接收這個方法的返回值。變數的定義需要指定變數的資料型別。其資料型別必須和返回值一樣或者可以發生自動型別轉換
  • 方法中只能呼叫方法或屬性,不可以在方法內部定義方法。
  • 方法被呼叫一次,就會執行一次

靜態方法呼叫說明

在方法呼叫的時候,什麼時候“類名.”是可以省略的。什麼時候不能省略?a()方法呼叫b()方法的時候,a和b方法都在同一個類中,“類名.”可以省略。如果不在同一個類中“類名.”不能省略。

break語句和return語句有什麼區別

  • break;用來終止switch和離它最近的迴圈。
  • return;用來終止離它最近的一個方法。

大家分析以下程式碼,編譯器會報錯嗎?

package demo02;

public class Test {
    public static int method1() {
        boolean flag = true;
        if (flag) {
            return 1;
        }
    }
}

還是編譯報錯,錯誤資訊是“缺少返回語句”,為什麼呢?實際上方法在宣告的時候指定了返回值型別為 int 型別,java 語法則要求方法必須能夠“百分百的保證”在結束的時候返回int 型別的資料,以上程式“return 1;”出現在 if 語句的分支當中,對於編譯器來說,它只知道“return 1;”有可能執行,也有可能不執行,所以 java 編譯器報錯了,不允許程式設計師這樣編寫程式碼。如果有返回值型別,我們必須保證return  語句百分之百執行。修改上面的程式碼如下

package demo02;

public class Test {
    public static int method1() {
        boolean flag = true;
        if (flag) {
            return 1;
        } else {
            return 0;
        }
    }
}

這樣就能編譯通過了,為什麼呢?這是因為編譯器能夠檢測出以上的 if..else..語句必然會有一個分支執行,這樣就不缺少返回語句了。其實以上程式碼也可以這樣寫: 

package demo02;

public class Test {
    public static int method1() {
        boolean flag = true;
        if (flag) {
            return 1;
        }
        return 0;
    }
}

以上程式碼也可以達到同樣的效果,因為“return 1;”如果不執行,則一定會執行“return 0;”,當然,如果執行了“return 1;”則方法一定會結束,“return 0;”也就沒有執行的機會了。再看下面的程式碼:

package demo02;

public class Test {
    public static int method1() {
        boolean flag = true;
        if (flag) {
            return 1;
            System.out.println("第 1 行");
        }
        System.out.println("第 2 行");
        return 0;
        System.out.println("第 3 行");
    }
}

以上程式還是編譯報錯,哪裡出錯了,為什麼呢?其中“第 1 行”和“第 3 行”程式碼沒有執行機會,編譯報錯,但“第 2 行”是有執行機會的。通過以上程式我們可以得出這樣的結論:在同一個“域”中,“return”語句後面是不能寫任何程式碼的,因為它無法執行到。

棧資料結構

要想理解方法執行過程中記憶體的分配,我們需要先學習一下棧資料結構,那麼什麼是資料結構呢?資料結構就是資料儲存的方式。其實資料結構是一門獨立的學科,不僅是在 java 程式設計中需要使用,在其它程式語言中也會使用,在大學的計算機課程當中,資料結構和演算法通常作為必修課出現,而且是在學習任何一門程式語言之前先進行資料結構和演算法的學習。資料結構是計算機儲存、組織資料的方式。資料結構是指相互之間存在一種或多種特定關係的資料元素的集合。通常情況下,精心選擇的資料結構可以帶來更高的執行或者儲存效率。資料結構往往同高效的檢索演算法和索引技術有關。

常見的資料結構有哪些呢?例如:棧、佇列、連結串列、陣列、樹、圖、堆、雜湊表等。目前我們先來學習一下棧(stack)資料結構,這是一種非常簡單的資料結構。如下圖所示: 

                                                                            

棧(stack)又名堆疊,它是一種運算受限的線性表。其限制是:僅允許在表的一端進行插入和刪除運算。這一端被稱為棧頂,相對地,把另一端稱為棧底。向一個棧插入新元素又稱作進棧、入棧或壓棧(push),它是把新元素放到棧頂元素的上面,使之成為新的棧頂元素;從一個棧刪除元素又稱作出棧、退棧或彈棧(pop),它是把棧頂元素刪除掉,使其相鄰的元素成為新的棧頂元素。

通過以上的學習,我們可以得知棧資料結構儲存資料有這樣的特點:先進後出,或者後進先出原則。也就是說最先進去的元素一定是最後出去,最後進去的元素一定是最先出去,因為一端是開口的,另一端是封閉的。 

方法執行過程中記憶體的變化

接下來我們開始學習方法執行過程中記憶體是如何變化的?我們先來看一張圖片: 

                                                  

上圖是一張標準的 java 虛擬機器記憶體結構圖,目前我們只看其中的“棧”和“方法區”,其它的後期研究,方法區中儲存類的資訊,或者也可以理解為程式碼片段,方法在執行過程中需要的記憶體空間在棧中分配。java 程式開始執行的時候先通過類載入器子系統找到硬碟上的位元組碼(class)檔案,然後將其載入到 java 虛擬機器的方法區當中,開始呼叫 main 方法,main 方法被呼叫的瞬間,會給 main 方法在“棧”記憶體中分配所屬的活動空間,此時發生壓棧動作,main 方法的活動空間處於棧底。

也就是說,方法只定義不去呼叫的話,只是把它的程式碼片段儲存在方法區當中,java 虛擬機器是不會在棧記憶體當中給該方法分配活動空間的,只有在呼叫的瞬間,java 虛擬機器才會在“棧記憶體”當中給該方法分配活動空間,此時發生壓棧動作,直到這個方法執行結束的時候,這個方法在棧記憶體中所對應的活動空間就會釋放掉,此時發生彈棧動作。由於棧的特點是先進後出,所以最先呼叫的方法(最先壓棧)一定是最後結束的(最後彈棧)。比如:main 方法最先被呼叫,那麼它一定是最後一個結束的。換句話說:main 方法結束了,程式也就結束了(目前來說是這樣)。

接下來我們來看一段程式碼,同時畫出記憶體結構圖,以及使用文字描述該程式的記憶體變化:

package demo02;

public class MethodTest {
    public static void main(String[] args) {
        System.out.println("main begin");
        m1();
        System.out.println("main over");
    }

    public static void m1() {
        System.out.println("m1 begin");
        m2();
        System.out.println("m1 over");
    }

    public static void m2() {
        System.out.println("m2 begin");
        System.out.println("m2 over");
    }
}

執行結果如下圖所示:

                                                                         

通過上圖的執行結果我們瞭解到,main 方法最先被呼叫,但是它是最後結束的,其中 m2方法最後被呼叫,但它是最先結束的。大家別忘了呼叫的時候分配記憶體是壓棧,結束的時候是釋放記憶體彈棧哦。為什麼會是上圖的結果呢,我們來看看它執行的記憶體變化,請看下圖:

 

                                                               

通過上圖的分析,可以很快明白,為什麼輸出結果是這樣的順序,接下來我們再採用文字的方式描述它的記憶體變化:

  • ① 類載入器將 class 檔案載入到方法區。
  • ② 開始呼叫 main方法,在棧記憶體中給 main方法分配空間,開始執行 main方法,輸出”mainbegin”。
  • ③ 呼叫 m1()方法,在棧記憶體中給 m1()方法分配空間,m1()方法處於棧頂,具備活躍權,輸出”m1 begin”。
  • ④ 呼叫 m2()方法,在棧記憶體中給 m2()方法分配空間,m2()方法處於棧頂,具備活躍權,輸出”m2 begin”,繼續輸出”m2 over”。
  • ⑤ m2()方法執行結束,記憶體釋放,彈棧。
  • ⑥ m1()方法這時處於棧頂,具備活躍權,輸出”m1 over”。
  • ⑦ m1()方法執行結束,記憶體釋放,彈棧。
  • ⑧ main()方法這時處於棧頂,具備活躍權,輸出”main over”。
  • ⑨ main()方法執行結束,記憶體釋放,彈棧。
  • ⑩ 棧空了,程式結束。

大家是否還記得之前的課程中曾經提到方法體當中的程式碼是有執行順序的,必須遵循自上而下的順序依次逐行執行,當前行程式碼必須執行結束之後,下一行程式碼才能執行,不能跳行執行,還記得嗎?現在再和棧資料結構一起聯絡起來思考一下,為什麼要採用棧資料結構呢?是不是只有採用這種先進後出的棧資料結構才可以保證程式碼的執行順序呢!此時,你是不是感覺程式的設計者在此處設計的非常巧妙呢!

相關文章