Java Foundations
27/39
Bài 27 / 39~12 phútPhương thức (method)Lộ trình · chặng 02/21Miễ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 cấu trúc method trong Java từ signature, tham số, return type đến cơ chế call stack, stack frame độc lập và kỹ năng đọc stack trace.

TL;DR: Phương thức (method) đóng gói một khối logic kèm kiểu trả về và danh sách tham số dưới một chữ ký (signature) xác định. Khi thực thi, JVM cấp phát một stack frame riêng biệt trên Call Stack cho mỗi lượt gọi, áp dụng cơ chế pass-by-value và tự động giải phóng frame khi phương thức kết thúc hoặc trả về kết quả.

Đến hết Module 3 với bài tập Mini-challenge FizzBuzz, 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ó.

1. Analogy công thức sổ tay đầu bếp minh họa method ra sao?

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 của một phương thức trong Java gồm những gì?

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

Ví dụ:

Java
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ó:

  • Modifier — public static. static = method của class, không cần tạo object để gọi. Sẽ đào sâu ở phần 6.
  • Return type — int (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ộc có return trong mọi nhánh kết thúc.

Gọi method:

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

2.1 void — method không trả gì

Java
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:

Java
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

Java
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:

Java
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:

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

3. Khái niệm method signature giúp JVM phân biệt hàm như thế nào?

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.

Java
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 có bao gồm return type. Ví dụ add(int,int) return int là add(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.

Thử ngẫmcompiler Java chặn hai method cùng tên cùng tham số dù return type khác nhau, nhưng descriptor bytecode lại phân biệt được add(II)I với add(II)D. Giới hạn "không overload theo return type" đến từ đâu — JVM hay chỉ compiler?

4. Cơ chế Call Stack quản lý chuỗi gọi phương thức hoạt động ra sao?

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.

Java
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 computeTotal và a trong add là 2 biến khác nhau, dù cùng tên.

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

Java
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:

Java
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.

Thử ngẫmstack chỉ ~512KB–1MB, mỗi lần gọi thêm một frame. Một hàm đệ quy xử lý danh sách 2 triệu phần tử thay vì vòng lặp — bạn nghĩ nó chịu được tới đâu trước khi StackOverflowError?

5. Vì sao cơ chế truyền tham số trong Java luôn là pass-by-value?

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

Java
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.

Java
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
}

Thử ngẫmmethod reset gán list = new ArrayList<>() chỉ đổi biến local — caller không thấy gì thay đổi. Method nào ở trên thật sự làm rỗng list của caller, và vì sao nó làm được còn reset thì không?

6. Điểm khác biệt cơ bản giữa static method và instance method là gì?

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: main là static, 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. Các quy ước đặt tên (convention) cho method gồm những gì?

  • 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. Các bẫy lỗi thường gặp nhất khi khai báo phương thức là gì?

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

Java
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ánh có return. 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.

Java
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ừ.

Java
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. Tài liệu Deep Dive Oracle quy định gì về method và call stack?

📚 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. Điểm cốt lõi cần nhớ về khai báo phương thức là gì?

  • Method đóng gói logic theo nguyên lý đơn trách nhiệm, xác định bởi chữ ký (tên hàm và kiểu tham số).
  • Mọi nhánh rẽ của method non-void bắt buộc phải có câu lệnh return giá trị tương ứng.
  • Call Stack phân bổ frame riêng cho từng lượt gọi; Java luôn truyền tham trị (pass-by-value) cho mọi kiểu dữ liệu.
  • Đặt tên method bằng động từ theo quy ước camelCase và ưu tiên phương thức ngắn gọn, dễ kiểm thử.

11. Tự kiểm tra kiến thức về khai báo phương thức như thế nào?

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 có giúp bạn hiểu bản chất không?

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.

Lộ trình Junior Java Backend Developer

Chặng 2/21 · Lập trình từ số 0 với Java — bài 27/32 của chặng

Thảo luận & góp ý

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

Bài tiếp theo

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