Home
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try a Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

4

Local AI

--

Uedu Code

--

Taipei AQI 57 · Taichung AQI 74 · Tainan AQI 63 · Kaohsiung AQI 64
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 08

物件與類別

類別、物件、建構子、this、封裝與 static
Interaction實作 30 minute(s)

Learning Objectives

  • 說明類別是藍圖、物件是用 new 在堆積上建立的實體,變數存的是指向物件的參照
  • 寫建構子替新物件設定初始值,用 this 分辨欄位與同名的參數
  • 把欄位設成 private、透過方法存取,在方法裡檢查資料是否合理(封裝)
  • 說明兩個變數指向同一個物件時,透過其中一個修改,另一個也看得到;== 比較的是參照
  • 分辨 static 欄位(整個類別共用一份)與一般欄位(每個物件各有一份)

類別與物件

寫程式時常常要處理「一群有相同結構的資料」:每位學生都有姓名和分數、每本書都有書名和價格。 Java 用類別(class)描述這種結構,再依照類別建立一個一個的物件(object):

class Student {          // 類別:描述學生「有什麼資料、能做什麼事」
    String name;         // 欄位(field):每個物件各有一份
    int score;

    void show() {        // 方法(method):物件能做的事
        System.out.println(name + ":" + score + " 分");
    }
}

Student amy = new Student();   // 依照類別建立一個物件
amy.name = "Amy";              // 用「變數.欄位」存取物件的資料
amy.show();                    // 用「變數.方法()」呼叫物件的方法

類別像是設計圖,物件是依照設計圖蓋出來的房子:一張設計圖可以蓋很多棟,每一棟都有自己的門牌和住戶。

本課的逐步執行動畫分成兩欄:左邊是變數(每次呼叫方法都有自己的一個框架),右邊是物件。 new 建立的物件放在堆積(heap)這塊記憶體裡;變數本身不裝物件,只存一個指向物件的參照(reference),動畫用箭頭表示。

用 new 建立物件

第 13 行執行完,右邊出現一個 Student 物件,amy 的圓點用箭頭指向它。物件剛建立時,欄位是預設值:參照型別(例如 String)是 null、數字是 0、boolean 是 false。呼叫 amy.show() 時多出一個框架,裡面的 this 指向 amy 那個物件;呼叫 bob.show() 時,this 就換成指向 bob 的物件。

建構子與 this

一個一個設定欄位很麻煩,也容易忘記。建構子(constructor)是 new 的時候自動執行的特殊方法, 負責替新物件設定初始值:

class Book {
    String title;
    int price;

    Book(String title, int price) {   // 建構子:名稱和類別相同,沒有回傳型別
        this.title = title;           // this.title 是欄位,title 是參數
        this.price = price;
    }
}

Book a = new Book("Java 入門", 450);
  • 建構子的名稱必須和類別完全相同,而且不寫回傳型別(連 void 都不寫)。寫了 void 就變成一般方法,new 的時候不會執行
  • 參數和欄位同名時,單寫 title 指的是參數;要指欄位必須寫 this.title。this 代表「正在建立(或正在呼叫方法)的這個物件」
  • 同一個類別可以有好幾個參數不同的建構子(多載)。在建構子的第一行寫 this(…),可以呼叫另一個建構子,避免重複的程式碼
  • 類別裡完全沒有寫建構子時,Java 會自動提供一個不接受參數的預設建構子;只要你寫了任何一個建構子,預設建構子就不再自動提供

建構子、this(…) 與方法

注意第 23 行 new Book("演算法"):先進入只有一個參數的建構子,它的第一行 this(title, 300) 再呼叫兩個參數的建構子,所以右邊會疊出兩層框架,兩層的 this 都指向同一個新物件。

想一想:物件、預設值與建構子

1.

下面的程式會印出什麼?

class Cat {
    String name;
    int age;
}

public class Main {
    public static void main(String[] args) {
        Cat c = new Cat();
        c.name = "Mimi";
        System.out.println(c.name + " " + c.age);
    }
}
2.

下面的程式會印出什麼?

class Box {
    int size;
    Box(int size) {
        size = size;
    }
}

public class Main {
    public static void main(String[] args) {
        Box b = new Box(5);
        System.out.println(b.size);
    }
}
3.

類別裡只寫了 Dog(String name) { … } 這一個建構子,執行 new Dog() 會怎樣?

第 1 次作答

封裝:private 欄位與存取方法

如果任何程式都能直接改 acc.balance = -5000;,物件裡的資料就可能變得不合理。封裝(encapsulation)的做法是:

  1. 欄位加上 private,類別外面的程式不能直接讀寫
  2. 提供 public 的方法讓外面使用,方法裡先檢查資料是否合理,再修改欄位
class BankAccount {
    private int balance;

    void deposit(int amount) {
        if (amount <= 0) return;   // 不合理的金額直接拒絕
        balance += amount;
    }

    int getBalance() { return balance; }   // getter:讓外面讀取,但不能直接改
}

在類別外面寫 acc.balance 會編譯錯誤:balance has private access in BankAccount。 只讀取欄位的方法習慣叫 getXxx(),修改欄位的叫 setXxx(…)(getter/setter)。不是每個欄位都需要 setter:餘額只能透過存款、提款改變,就不應該提供 setBalance。

修飾字 誰可以使用
public 任何類別
protected 同一個套件,以及子類別(第 09 課)
不寫 同一個套件(package)裡的類別
private 只有同一個類別裡面

封裝的銀行帳戶

main 沒有辦法直接碰 balance,只能呼叫 deposit、withdraw、getBalance。看第 35 行存入 -50 時,方法在第 11 行檢查之後就 return,欄位沒有被改;第 36 行領 1500 超過餘額,withdraw 回傳 false。

變數存的是參照

int、double 這類基本型別的變數直接存值;類別、陣列、String 這類參照型別的變數,存的是指向物件的參照。這會影響三件事:

1. 指派只複製參照。 Point b = a; 不會複製物件,而是讓 b 也指向 a 那個物件。之後透過 b 改欄位,用 a 讀到的也是改過的值。 要真的得到一份副本,必須自己 new 一個新物件,把欄位一一複製過去。

2. == 比較的是「是不是同一個物件」。 兩個物件的欄位完全一樣,只要是分別 new 出來的,== 就是 false。 要比較內容,類別要提供 equals 方法(第 09 課)。字串也一樣:比較文字內容要用 s1.equals(s2),不要用 ==。

3. 把物件傳給方法,傳的是參照的複本。 方法拿到的參照和呼叫端的指向同一個物件,所以方法裡改欄位,呼叫端看得到。 (Java 永遠是傳值:傳進去的是「參照這個值」的複本。方法裡讓參數改指向別的物件,不會影響呼叫端的變數。)

null 表示「沒有指向任何物件」。對 null 呼叫方法或存取欄位會拋出 NullPointerException。 當一個物件不再被任何變數指到,程式就再也用不到它,Java 的垃圾回收(garbage collection)會在適當的時候自動釋放它的記憶體。

兩個變數指向同一個物件

第 19 行之後,a 和 b 的箭頭指向同一個 Point。第 29、30 行把兩個變數都設成 null 之後,第一個 Point 再也沒有變數指到,從右邊消失:這就是垃圾回收會處理的物件。

static:屬於類別的成員

一般欄位是每個物件各有一份;加上 static 的欄位則屬於類別本身,整個程式只有一份,所有物件共用。 適合用來記錄「跟整個類別有關」的資料,例如一共建立了幾個物件、共用的常數。

class Ticket {
    static int count = 0;   // 所有 Ticket 共用
    int number;             // 每張票各自的號碼

    Ticket() {
        count++;
        number = count;
    }
}

System.out.println(Ticket.count);   // 用「類別名稱.欄位」存取

static 方法同樣屬於類別,用 類別名稱.方法() 呼叫,例如 Math.sqrt(2)、Integer.parseInt("5")。 static 方法執行時沒有 this,所以不能直接使用一般欄位或一般方法。這就是為什麼在 main 裡直接呼叫自己寫的非 static 方法, 編譯器會報錯 non-static method … cannot be referenced from a static context:要先 new 出物件,再用物件呼叫。

常數通常寫成 static final,名稱全部大寫,例如 static final double TAX_RATE = 0.05;。

共用的計數器

第一次用到 Ticket 類別時,動畫左邊會多出「靜態欄位」框,裡面是整個類別共用的 Ticket.count。每建立一張票,count 就加 1;每個物件自己的 number 則各不相同。

想一想:參照、== 與 static

1.

下面的程式會印出什麼?

class Counter {
    int n;
}

public class Main {
    static void add(Counter c) { c.n++; }
    static void add(int x) { x++; }

    public static void main(String[] args) {
        Counter c = new Counter();
        int x = 0;
        add(c);
        add(x);
        System.out.println(c.n + " " + x);
    }
}
2.

下面的程式會印出什麼?

class P {
    int v;
    P(int v) { this.v = v; }
}

public class Main {
    public static void main(String[] args) {
        P a = new P(3);
        P b = new P(3);
        P c = a;
        System.out.println((a == b) + " " + (a == c));
    }
}
3.

下面的程式會印出什麼?

class Player {
    static int total = 0;
    int score;
    Player(int s) { score = s; total += s; }
}

public class Main {
    public static void main(String[] args) {
        Player p1 = new Player(10);
        Player p2 = new Player(25);
        System.out.println(p1.score + " " + Player.total);
    }
}
第 1 次作答

動手寫:長方形類別

完成 Rectangle 類別:建構子把寬、高存進欄位,area() 回傳面積,perimeter() 回傳周長。main 已經寫好,執行後的輸出要和預期相同。

你的輸出
預期輸出

重點回顧

  • 類別描述資料(欄位)與行為(方法);new 依照類別在堆積上建立物件,變數存的是指向物件的參照
  • 物件的欄位有預設值(數字 0、boolean 為 false、參照為 null);方法裡的區域變數沒有預設值
  • 建構子的名稱和類別相同、沒有回傳型別,new 時自動執行;參數和欄位同名時用 this.欄位 指欄位,this(…) 呼叫另一個建構子
  • 封裝:欄位設成 private,透過方法存取,在方法裡檢查資料
  • 指派物件變數只複製參照;== 比較是不是同一個物件;把物件傳給方法,方法裡改欄位,呼叫端看得到
  • static 成員屬於類別、整個程式只有一份;static 方法沒有 this,不能直接使用一般欄位與方法
這一課完成了!
Next lesson:繼承
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。