Java 初學者指南:搞懂 this 與 static,不再霧裡看花【Thinking in Java筆記(4-2)】

Java 初學者指南:搞懂 this 與 static,不再霧裡看花【Thinking in Java筆記(4-2)】
Photo by orbtal media / Unsplash

在學 Java 的時候,有些關鍵字像是 thisstatic,常常出現在各種範例中,但說真的,很多人一開始只是照抄能跑就好,根本沒搞懂為什麼要這樣寫。本篇文章會用簡單易懂的方式帶你弄懂這兩個關鍵字,還會搭配一些實際的比喻,讓你讀完以後,不會只是“懂用”,而是真的“懂它為什麼這樣設計”

this 關鍵字是什麼?它不是你以為的那個“這個”

多個物件在呼叫同一個方法時怎麼分辨誰是誰?

想像你有兩根香蕉(是的,香蕉),它們各自有皮要被剝:

public class BananaPeel {
  public static void main(String[] args) {
    Banana a = new Banana(),
           b = new Banana();
    a.peel(1);
    b.peel(2);
  }
}

這邊 a.peel(1) 到底是誰在呼叫 peel?其實 Java 編譯器會偷偷幫你轉成:

Banana.peel(a, 1);
Banana.peel(b, 2);

雖然你不能真的這樣寫,但概念就是:它偷偷傳入了「是誰在叫這個方法」的資訊。這個資訊,就叫 this

所以 this 是什麼?它就是「目前這個物件的自己」。在方法內,如果你寫 this.doSomething(),就是在說「嘿,我自己做這件事」

方法裡呼叫其他方法要用 this 嗎?

不一定。例如:

public class Apricot {
  void pick() { /* ... */ }
  void pit() { pick(); /* 或 this.pick(); 都可以 */ }
}

如果你很懶(像我),你可以省略 this。Java 會自動幫你補上。但如果你想要讓語意更明確,或者要回傳整個物件本身時,就非 this 不可

回傳自己:鏈式操作的關鍵

在 Java 中有一種常見的寫法叫做 method chaining(方法鏈式調用),我們來透過 this 關鍵字理解它的運作

先看一般的版本:

public class Leaf {
  int i = 0;
  Leaf increment() {
    i++;
    return this;
  }
  void print() {
    System.out.println("i = " + i);
  }

  public static void main(String[] args) {
    Leaf x = new Leaf();
    x.increment();
    x.increment();
    x.increment();
    x.print();
  }
}

這種寫法每次呼叫 increment() 都是獨立的動作,一步一步執行,最後印出結果。每次呼叫 increment(),裡面其實都有 return this;,只是我們沒有拿它來做後續的動作

那如果我們要把這三次加法串在一起呢?就變成下面這樣:

public class Leaf {
  int i = 0;
  Leaf increment() {
    i++;
    return this;
  }
  void print() {
    System.out.println("i = " + i);
  }
  public static void main(String[] args) {
    Leaf x = new Leaf();
    x.increment().increment().increment().print();
  }
} /* Output:
i = 3
*/

這段可以拆成三個步驟來理解:

  1. 每次呼叫 increment() 方法,物件裡的 i 會加 1
  2. 然後這個方法會回傳 this,也就是目前這個物件本身
  3. 所以你可以接著繼續呼叫 increment() 或其他方法,形成類似「鏈條」的連續呼叫

這樣的寫法叫做 method chaining(方法鏈式調用),常見於像是設定值或建構者模式裡

如果用生活比喻來說,就像你到便利商店結帳時說:「給我一個袋子、沒有會員、載具、line pay」,你不是每講一句就結一次帳,而是一次把所有事情連續講完

透過 this 回傳自己,我們就能做到這樣的連續指令,讓語意更清楚、程式更有彈性

把自己傳給別人用?也可以!

class Person {
  public void eat(Apple apple) {
    Apple peeled = apple.getPeeled();
    System.out.println("Yummy");
  }
}

class Peeler {
  static Apple peel(Apple apple) {
    // ... remove peel
    return apple; // Peeled
  }
}

class Apple {
  Apple getPeeled() { return Peeler.peel(this); }
}

public class PassingThis {
  public static void main(String[] args) {
    new Person().eat(new Apple());
  }
} /* Output:
Yummy
*/

在這裡,Apple 物件在呼叫 getPeeled() 方法時,使用 this 將自己作為參數傳給 Peeler.peel() 方法。這樣做的意思是:「我要叫 Peeler 來處理我自己」

這個 this 代表的就是目前這顆 Apple 物件,也就是正在被呼叫方法的那一個實例。等於是 Apple 自己主動把自己丟進削皮機,請求處理。這種方式讓物件本身能夠參與流程,不只是被動的資料容器

就像在說:「師傅,這就是我,麻煩幫我剝皮一下,謝謝」

建構子裡的 this():喚起你家另一位建構子的召喚術

在 Java 中,一個類別可以有多個建構子(constructor),這些建構子的用途是用來建立物件並初始化變數的。不過如果你發現每個建構子裡都有一堆重複的程式碼,那就該思考怎麼讓它們彼此合作

Java 提供了 this() 的語法,讓你可以在一個建構子裡直接呼叫同一個類別的另一個建構子,避免重複碼,提升維護性

以下這個範例展示了四種不同的建構子,它們彼此之間透過 this(...) 做協同:

public class Flower {
  int petalCount = 0;
  String s = "initial value";
  Flower(int petals) {
    petalCount = petals;
    print("Constructor w/ int arg only, petalCount= "
      + petalCount);
  }
  Flower(String ss) {
    print("Constructor w/ String arg only, s = " + ss);
    s = ss;
  }
  Flower(String s, int petals) {
    this(petals);
	//!    this(s); // Can't call two!
    this.s = s; // Another use of "this"
    print("String & int args");
  }
  Flower() {
    this("hi", 47);
    print("default constructor (no args)");
  }
  void printPetalCount() {
	//! this(11); // Not inside non-constructor!
    print("petalCount = " + petalCount + " s = "+ s);
  }
  public static void main(String[] args) {
    Flower x = new Flower();
    x.printPetalCount();
  }
} /* Output:
Constructor w/ int arg only, petalCount= 47
String & int args
default constructor (no args)
petalCount = 47 s = hi
*/

在上述範例中,我們看到一個建構子裡用 this(...) 去呼叫同一類別的其他建構子,這樣的寫法讓我們可以重複利用邏輯,而不是每個建構子裡都寫一遍重複的程式碼。這就像是有一間廚房,你可以叫不同廚師幫你準備不同部分的菜,而不是每次都自己重頭做起

這樣的做法在程式設計中叫做「建構子串接」(constructor chaining),能讓程式碼更有彈性,也更容易維護。

不過使用上有幾個重要規則:

  • 一次只能呼叫一個
  • 必須放在建構子的第一行
  • 不能在普通方法中使用

想像你在蓋房子,一個建構子就像是不同的裝修團隊。你可以選擇只找粉刷牆壁的,也可以找包含粉刷、鋪地板、裝燈的團隊。如果你用 this(...),就像是在粉刷團隊裡打電話叫來鋪地板的同事幫忙,大家分工合作,避免重複做事

static 是什麼?沒有物件的世界也需要方法

初學者常會搞混兩種用法:

  • 一種是先 new 出來一個物件再呼叫方法,例如:
Dog myDog = new Dog();
myDog.bark();

這是物件導向的方式,你是叫一隻特定的狗來叫。

  • 另一種是直接透過類別來呼叫方法,例如:
Math.sqrt(4);

這就是使用 static 方法,不需要建立 Math 的物件。你只是問「全世界的數學大師們,4 的平方根是多少?」

static 的意思是:「這個方法或變數不是某個物件獨有的,而是整個類別共享的」你不需要 new 出一個物件就能用它,因為它根本不屬於任何一個物件。打個比方:你班導的手機號碼是公開的,全班都能打,不需要你是某個特定同學才能聯絡到老師;但如果是你朋友的手機號碼,那你就得是他的朋友(也就是他的實例)才會知道。所以 static 的東西,是對所有人開放的,不用先建立關係(物件)

以下是一個簡單範例:

如果 this 是“我自己”,那 static 就是“我們全體”。它不屬於某一個物件,而是屬於整個類別

public class StaticExample {
  static int staticValue = 5;
  
  static void staticMethod() {
    System.out.println("Static method called. staticValue = " + staticValue);
  }
  
  public static void main(String[] args) {
    StaticExample.staticMethod();
  }
} /* Output:
Static method called. staticValue = 5
*/

你不需要創造一個 StaticExample 的物件,也就是不用 new StaticExample(),就能直接用 StaticExample.staticMethod() 呼叫這個方法。因為這個方法是 static,它屬於類別本身,而不是某個物件實例。就好像教室牆上貼了一張公用時間表,每個人都能直接看,不用先請出某個特定的同學

小結

這篇文章幫你從各種角度看清楚了 thisstatic 的不同角色。

  • this 是物件的自我意識,只有當你真的有建立出一個物件實例時,它才存在。它幫你指向「現在我是哪個物件」,讓你能夠做出鏈式調用、把自己傳出去、或在建構子內協調自己的初始化流程
  • static 則像是工具櫃裡的萬用工具,它根本不需要你先創建某個物件。任何人只要知道類別名稱,就能直接拿來用。它沒有 this,因為它不屬於任何物件,只有一份給大家共用

懂了這兩個關鍵字,你就不會再在物件與類別的迷霧裡打轉。下次看到 thisstatic,不只是「哦,我看過這個」,而是「我知道為什麼它要這樣設計」。這才是會寫程式,而不是只會複製程式碼