Java Foundations/Khai báo method — đóng gói một đoạn việc vào một cái tên
27/39
Bài 27 / 39~18 phútPhương thức (method)Miễn phí lượt xem

Khai báo method — đóng gói một đoạn việc vào một cái tên

Hiểu method trong Java từ chữ ký (signature), parameter, return type đến cơ chế call stack. Vì sao mỗi method là một stack frame độc lập và điều đó nghĩa là gì cho debug.

Đến hết Module 3, bạn đã viết được chương trình vài chục dòng dồn hết vào main. Nhưng khi logic chạm 100–200 dòng, main thành khối khổng lồ không ai đọc nổi — sửa 1 chỗ sợ vỡ 10 chỗ. Cách giải quyết ai cũng biết: tách thành nhiều hàm. Nhưng trong Java, "hàm" không đứng một mình — nó phải thuộc về một class, và nó có signature quyết định cách JVM gọi nó.

Bài này giải thích cách khai báo method, signature gồm gì, return type có ý nghĩa gì ở cấp bytecode, và call stack — cơ chế JVM quản lý chuỗi gọi hàm mà hiểu nó giúp bạn đọc stack trace thành thạo.

1. Analogy — công thức trong sổ tay đầu bếp

Bạn có một cuốn sổ tay nhà bếp. Mỗi trang là một công thức có tên (bánh xèo), nguyên liệu đầu vào (bột gạo, tôm), và thứ trả ra (1 đĩa bánh xèo). Khi cần bánh xèo, bạn lật đúng trang — không phải viết lại công thức mỗi lần.

Đời thườngMethod
Tên công thứcTên method (makePancake)
Nguyên liệuParameters (flour, shrimp)
Món trả raReturn value (Pancake)
Sách công thứcClass chứa method
💡 Cách nhớ

Method = đặt tên cho một đoạn việc để có thể gọi lại nhiều lần, truyền input khác nhau, nhận output ra.

2. Cú pháp cơ bản

<modifier> <returnType> <methodName>(<paramType> <paramName>, ...) {
    // body
    return <value>;   // bat buoc neu returnType khac void
}

Ví dụ:

public class MathUtils {

    public static int add(int a, int b) {
        return a + b;
    }

    public static void printGreeting(String name) {
        System.out.println("Hello, " + name);
    }
}

Hai method trên có:

  • Modifierpublic static. static = method của class, không cần tạo object để gọi. Sẽ đào sâu ở phần 6.
  • Return typeint (trả số) hoặc void (không trả gì).
  • Tên — camelCase theo convention Java. add, printGreeting, calculateTax.
  • Parameter list — 0 hoặc nhiều cặp <kiểu> <tên>, phân cách bằng dấu phẩy.
  • Body — trong { }. Nếu có return type khác void, bắt buộcreturn trong mọi nhánh kết thúc.

Gọi method:

int sum = MathUtils.add(3, 5);           // sum = 8
MathUtils.printGreeting("Alice");         // in: Hello, Alice

2.1 void — method không trả gì

public static void log(String message) {
    System.out.println("[LOG] " + message);
}

void nghĩa là method chạy vì side effect (in, ghi file, gọi API, thay đổi state), không trả giá trị. Không cần return — hoặc chỉ return; trống để thoát sớm:

public static void login(User user) {
    if (user == null) return;       // thoat som
    if (!user.isActive()) return;
    // logic login
}

2.2 Return value — bắt buộc trong mọi nhánh

public static int sign(int x) {
    if (x > 0) return 1;
    if (x < 0) return -1;
    // quen return cho truong hop x == 0 -> COMPILE ERROR
}

Compiler bắt: "missing return statement". Java yêu cầu mọi đường thoát của method non-void phải có return. Fix:

public static int sign(int x) {
    if (x > 0) return 1;
    if (x < 0) return -1;
    return 0;
}

Hoặc switch expression cho compiler thấy exhaustive:

public static int sign(int x) {
    return Integer.signum(x);   // dung san trong JDK
}

3. Signature — thứ JVM dùng để phân biệt method

Signature của một method trong Java = tên + danh sách kiểu tham số. Return type không nằm trong signature.

int add(int a, int b)          // signature: add(int, int)
double add(double a, double b) // signature: add(double, double) — khac, hop le
int add(int x, int y)          // SAME signature voi cai dau — KHONG hop le trong cung class
String add(int a, int b)       // SAME signature — KHONG hop le

Có 2 hệ quả quan trọng:

  1. Overloading được: nhiều method cùng tên, khác parameter list. Bài sau.
  2. Return type đổi không tạo signature mới — muốn 2 method khác thì phải đổi số/kiểu parameter.
📚 Bytecode thấy signature thế nào?

Trong file .class, mỗi method được nhận diện bằng tên + descriptor — nhưng descriptor bao gồm return type. Ví dụ add(int,int) return intadd(II)I. Đây là lý do JVM runtime phân biệt chính xác, nhưng compiler Java theo JLS quyết định "cùng signature" ở source level chỉ dựa tên + params — rõ hơn ở JLS §8.4.2.

4. Call stack — JVM chạy chuỗi gọi method thế nào

Khi method A gọi method B, JVM đặt B lên trên A trong call stack. B chạy xong sẽ bị pop khỏi stack, và JVM quay về A chạy tiếp. Mỗi method đang chạy có stack frame riêng chứa parameter + local variable.

public static void main(String[] args) {
    int result = computeTotal(10, 20);
    System.out.println(result);
}

public static int computeTotal(int a, int b) {
    int sum = add(a, b);
    return sum * 2;
}

public static int add(int a, int b) {
    return a + b;
}

Luồng call stack khi chạy:

Ba stack frame chồng lên nhau khi add đang chạy: main giữ local result, computeTotal giữ local sum, add nhận hai tham số riêng và trả về tổng; đường bên phải cho thấy giá trị 30 rồi 60 leo ngược lên khi từng frame bị pop

Tại moment add đang chạy, stack có 3 frame (từ dưới lên): main, computeTotal, add. Mỗi frame có local variable độc lập — a trong computeTotala trong add2 biến khác nhau, dù cùng tên.

4.1 Stack trace — tấm ảnh chụp call stack khi có exception

Exception in thread "main" java.lang.ArithmeticException: / by zero
    at MathUtils.divide(MathUtils.java:15)
    at MathUtils.computeRatio(MathUtils.java:9)
    at MathUtils.main(MathUtils.java:4)

Đọc từ trên xuống: exception phát sinh ở divide line 15, được gọi từ computeRatio line 9, được gọi từ main line 4. Hiểu call stack giúp bạn debug bất cứ exception nào trong 30 giây.

💡 Quick tip debug

Khi thấy stack trace, nhìn dòng "at" đầu tiên trong code của bạn (bỏ qua JDK internal). Đó là nơi lỗi thực sự xảy ra. 80% bug đọc stack trace đúng là tìm ra ngay.

4.2 StackOverflowError — stack đầy

Call stack có kích thước giới hạn (thường ~512KB–1MB). Đệ quy không có điều kiện dừng:

public static int bad(int n) {
    return bad(n + 1);   // khong bao gio dung
}

Chạy đoạn này, JVM ném StackOverflowError sau vài chục ngàn frame. Bài recursion sẽ đào sâu.

5. Tham số — pass-by-value, không phải pass-by-reference

Đã học ở Module 2 bài "Biến và khai báo". Nhắc lại ngắn:

public static void tryChange(int x) {
    x = 100;   // chi sua ban copy
}

public static void main(String[] args) {
    int a = 5;
    tryChange(a);
    System.out.println(a);   // van in 5
}

Java luôn pass-by-value. Với reference type, "value" là địa chỉ — method copy địa chỉ, có thể sửa object qua địa chỉ đó, nhưng không đổi được biến của caller.

public static void reset(List<String> list) {
    list = new ArrayList<>();   // chi sua bien local, caller khong biet
}

public static void clearAll(List<String> list) {
    list.clear();               // sua object thuc te, caller thay
}

6. static vs instance — bây giờ chỉ cần biết tạm

Bạn sẽ thấy static trong mọi method của bài này. Tạm hiểu:

  • static method — thuộc class, không cần tạo object. Gọi qua tên class: MathUtils.add(1, 2).
  • Instance method — thuộc object, phải tạo object trước rồi gọi qua object: myCalc.add(1, 2).

Module OOP sắp tới sẽ đào sâu. Hiện tại, vì chưa học class/object đầy đủ, bài này dùng static hết để tập trung vào method mechanics.

⚠️ Tại sao mọi method của bài này đều static?

Đơn giản: mainstatic, và static method chỉ gọi trực tiếp static method khác. Gọi instance method phải qua object — cần khái niệm class/object chưa học. Sang module OOP, bạn sẽ biết khi nào chọn static và khi nào không.

7. Convention đặt tên

  • camelCase bắt đầu bằng chữ thường: add, computeTotal, findUserById.
  • Động từ hoặc cụm động từ — method làm một việc: save, load, validate, parse.
  • get<X> / set<X> cho truy xuất / gán property (convention JavaBean).
  • is<X> / has<X> / can<X> trả boolean: isEmpty, hasPermission, canEdit.
  • Ngắn gọn, rõ ý. calculate tốt hơn doCalcStuff. processOrder tốt hơn proc.

Tên method kém là dấu hiệu logic không rõ trong đầu bạn. Dừng lại đặt tên đến khi đọc 1 giây hiểu ngay — thường kéo theo thiết kế method cũng tốt.

8. Pitfall tổng hợp

Nhầm 1: Quên return ở method non-void.

public static int abs(int x) {
    if (x >= 0) return x;
    // quen return cho nhanh x < 0 -> compile error
}

✅ Đảm bảo mọi nhánhreturn. Compiler sẽ bắt giúp bạn.

Nhầm 2: Tưởng đổi tham số trong method làm đổi biến ngoài.

public static void addOne(int n) { n++; }
int x = 5; addOne(x); System.out.println(x);   // van 5

✅ Return giá trị mới: public static int addOne(int n) { return n + 1; } rồi x = addOne(x);.

Nhầm 3: Method quá dài (100+ dòng) dồn nhiều trách nhiệm. ✅ Single Responsibility — method làm 1 việc. Nếu tên method có chữ "And" ("parseAndValidate"), khả năng cao nên tách 2.

Nhầm 4: Tên method là danh từ.

public static int calculation(int a, int b) { ... }   // nghe nhu mot bien

✅ Động từ: calculate, computeCost, sumOf.

Nhầm 5: Return type không khớp signature override (sẽ gặp ở OOP). ✅ Khi override, return type phải là kiểu con hoặc cùng kiểu của method cha (covariant return).

9. 📚 Deep Dive Oracle

📚 Deep Dive Oracle (optional)

Spec / reference chính thức:

Ghi chú: JLS §8.4.2 phân biệt rõ "signature" (source level) và "descriptor" (bytecode level). Source signature bỏ return type; bytecode descriptor có. Đây là lý do bạn không overload được chỉ bằng đổi return type, dù ở bytecode chúng trông là 2 method khác nhau.

10. Tóm tắt

  • Method = đóng gói một đoạn việc vào một cái tên + input (parameters) + output (return).
  • Signature = tên + parameter list. Return type không nằm trong signature (ở source level).
  • Method non-void phải có returnmọi nhánh thoát. void không return hoặc return; trống.
  • Java luôn pass-by-value — đổi parameter không đổi biến caller; sửa object qua reference thì thấy.
  • Call stack quản lý chuỗi gọi; mỗi method đang chạy có stack frame riêng với local variable riêng.
  • Stack trace = ảnh chụp call stack khi exception. Đọc từ trên xuống, tìm dòng đầu tiên trong code của bạn.
  • static method = thuộc class, gọi qua ClassName.method(...). Instance method cần object — sẽ học ở OOP.
  • Tên method: động từ, camelCase, ngắn rõ ý.

11. Tự kiểm tra

Tự kiểm tra
0/5 câu đã trả lời
  1. Q1
    Đoạn sau compile được không?
    public static int classify(int x) {
        if (x > 0) return 1;
        if (x < 0) return -1;
    }
  2. Q2
    Hai method sau có hợp lệ trong cùng class không?
    public static int compute(int a, int b) { return a + b; }
    public static double compute(int a, int b) { return a + b; }
  3. Q3
    Đoạn sau in gì?
    public static void swap(int a, int b) {
        int temp = a;
        a = b;
        b = temp;
    }
    
    public static void main(String[] args) {
        int x = 1, y = 2;
        swap(x, y);
        System.out.println(x + " " + y);
    }
  4. Q4
    Đọc stack trace sau và nói lỗi xảy ra ở đâu trong code của bạn:
    Exception in thread "main" java.lang.NullPointerException
        at java.base/java.util.Objects.requireNonNull(Objects.java:208)
        at UserService.save(UserService.java:42)
        at App.main(App.java:12)
  5. Q5
    Vì sao gần như mọi method trong module này đều static?

Bài tiếp theo: Overloading — nhiều method cùng tên, khác signature

Bài này đáng gửi cho bạn học cùng?

Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.

Bài này có giúp bạn hiểu bản chất không?

Hỏi đáp về bài này

Chưa có câu hỏi

Đặt câu hỏi

Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).

Đặt câu hỏi đầu tiên

Bài tiếp theo

Overloading — nhiều method cùng tên, khác signature