Backend

99 Ngày Java — Ngày 10: Phương thức & truyền tham số

SSite Admin
6 tháng 08, 2026 7 phút đọc 33 lượt xem
99 Ngày Java — Ngày 10: Phương thức & truyền tham số

Chín ngày qua, mọi code của chúng ta đều chen chúc trong main — tính điểm cũng main, đảo mảng cũng main. Hôm nay ta học cách đóng gói logic thành phương thức (method): đặt tên cho một việc, gọi lại trăm lần không copy-paste. Kèm theo là hai chủ đề phỏng vấn kinh điển: overloading — nhiều method trùng tên, và sự thật đằng sau câu thần chú "Java luôn pass-by-value" mà Ngày 09 đã hứa.

Sketchnote Ngày 10: phương thức trong Java — khai báo, overloading, pass-by-value, phạm vi biến

Khai báo method: đặt tên cho một việc

Method là một cỗ máy nhỏ có tên: nhận nguyên liệu (tham số), làm đúng một việc, và có thể trả về thành phẩm (giá trị trả về). Cú pháp gồm bốn mảnh:

// [modifier] [kiểu trả về] [tên](tham số) { thân — và return nếu có trả về }
static int tong(int a, int b) {      // nhận 2 int, TRẢ VỀ int
    return a + b;                    // return: trả kết quả VÀ thoát method
}

static void chao(String ten) {       // void = không trả về gì
    System.out.println("Chào " + ten + "!");
    // return; — được phép, chỉ để thoát sớm
}

public static void main(String[] args) {
    int kq = tong(3, 4);             // GỌI: đối số 3, 4 → tham số a, b
    chao("Mọt Dev");                 // 7 được gán vào kq
    System.out.println(tong(kq, 10)); // 17 — kết quả dùng trực tiếp được
}
  • Tham số (parameter) là biến khai trong ngoặc; đối số (argument) là giá trị thật lúc gọi — 34 rót vào ab theo đúng thứ tự.

  • return làm hai việc cùng lúc: trả kết quảthoát ngay — code sau return không bao giờ chạy. Method khai kiểu trả về thì mọi nhánh (nhớ if–else Ngày 06) đều phải dẫn tới một return.

  • Tên method dùng camelCase động từ: tinhTong, inBangDiem — đọc phát biết làm gì. Một method chỉ nên làm một việc; dài quá một màn hình là dấu hiệu nên tách.

  • Khoan thắc mắc chữ static — nó nghĩa là "thuộc về lớp, gọi không cần đối tượng". Ngày 11–18 sẽ trả lời trọn vẹn; tạm thời cứ viết static cho method gọi từ main.

Overloading — trùng tên, khác chữ ký

Java cho phép nhiều method cùng tên miễn danh sách tham số khác nhau (khác số lượng hoặc khác kiểu) — gọi là overloading. Compiler nhìn đối số lúc gọi để chọn bản khớp nhất, ngay tại thời điểm biên dịch:

static int    max(int a, int b)       { return a > b ? a : b; }
static double max(double a, double b) { return a > b ? a : b; }
static int    max(int a, int b, int c) { return max(max(a, b), c); }

max(3, 7)        // → bản int:    7
max(2.5, 1.5)    // → bản double: 2.5
max(1, 9, 4)     // → bản 3 tham số: 9
max(3, 7.0)      // → bản double! int 3 được widening (Ngày 05) thành 3.0

// ✗ KHÔNG phải overload — chỉ khác kiểu trả về thì compiler chịu:
// static double max(int a, int b) { ... }   → lỗi biên dịch!
  • Bộ "tên + danh sách kiểu tham số" gọi là chữ ký (signature)kiểu trả về không thuộc chữ ký, nên chỉ khác kiểu trả về là lỗi biên dịch.

  • Trường hợp max(3, 7.0) cho thấy overloading bắt tay với ép kiểu tự động Ngày 05: không có bản (int, double) nên 3 được widening lên double.

  • Bạn đã dùng overloading từ Ngày 01 mà không biết: println có hơn 10 bản — nhận int, String, char[]… đều in được là nhờ vậy.

  • Còn "một method nhận bao nhiêu đối số cũng được" kiểu String.join Ngày 09? Đó là varargs — hẹn Ngày 25.

Pass-by-value — sự thật đằng sau câu thần chú

Câu hỏi phỏng vấn quốc dân: "Java pass-by-value hay pass-by-reference?" Đáp án gọn: luôn luôn pass-by-value — mọi đối số đều bị copy khi rót vào tham số. Nhưng cái được copy là gì mới là chỗ thú vị: với primitive là giá trị, còn với object (mảng Ngày 08, chuỗi Ngày 09…) là tham chiếu — và bản sao tham chiếu vẫn trỏ về cùng một đối tượng:

static void tangSo(int x)      { x = x + 100; }          // sửa BẢN SAO
static void suaMang(int[] m)   { m[0] = 99; }            // sửa NỘI DUNG chung
static void thayMang(int[] m)  { m = new int[] { 7 }; }  // gán lại BẢN SAO tham chiếu

public static void main(String[] args) {
    int so = 1;
    tangSo(so);
    System.out.println(so);        // 1 — primitive: copy GIÁ TRỊ, bản gốc bình an

    int[] mang = { 1, 2, 3 };
    suaMang(mang);
    System.out.println(mang[0]);   // 99 — copy THAM CHIẾU: hai bên nhìn CÙNG mảng!

    thayMang(mang);
    System.out.println(mang[0]);   // vẫn 99 — gán lại chỉ đổi bản sao tham chiếu
}
  • Sửa nội dung qua bản sao tham chiếu → bản gốc thấy thay đổi (cùng nhìn một mảng trên heap).

  • Gán lại tham số → chỉ xoay bản sao sang chỗ khác, bản gốc không suy chuyển — vì thế không thể viết method "swap hai biến int" trong Java!

  • Ghép với Ngày 09: String bất biến nên truyền đi đâu cũng an toàn tuyệt đối — không ai "sửa nội dung" được, chỉ có thể gán lại bản sao của họ.

Bàn phím và màn hình code — method là những cỗ máy nhỏ được đặt tên, lắp ghép thành chương trình lớn

Phạm vi biến (scope)

Method sinh ra ranh giới sống cho biến: biến khai ở đâu thì chỉ sống trong cặp ngoặc nhọn đó — gọi là biến local:

public static void main(String[] args) {
    int diem = 9;                    // sống từ đây đến hết main

    if (diem >= 5) {
        String xepLoai = "Đậu";      // sống TRONG khối if — ra ngoài là biến mất
        System.out.println(xepLoai);
    }
    // System.out.println(xepLoai);  // ✗ lỗi biên dịch: ngoài phạm vi!

    for (int i = 0; i < 3; i++) { }  // i chỉ sống trong vòng for (Ngày 07)
    // System.out.println(i);        // ✗ i đã "hết đời"

    int chuaGan;
    // System.out.println(chuaGan);  // ✗ biến local KHÔNG có giá trị mặc định!
}
  • Tham số cũng là biến local của method — x trong tangSo chết khi method kết thúc, đó chính là lý do pass-by-value "vô hại" ở trên.

  • Biến local không có giá trị mặc định — khác với mảng Ngày 08 (phần tử tự về 0). Dùng trước khi gán là lỗi biên dịch, không phải lỗi runtime.

  • Phạm vi càng hẹp càng tốt: khai biến sát nơi dùng, vòng đời ngắn thì bug ít chỗ trốn.

Bài tập nhỏ

  • Viết static boolean laSoNguyenTo(int n) rồi dùng nó in mọi số nguyên tố dưới 100 (vòng lặp Ngày 07 tái xuất).

  • Overload dienTich(double canh) (hình vuông) và dienTich(double dai, double rong) (chữ nhật) — gọi cả hai.

  • Viết static void tangLuong(double[] luong, double phanTram) tăng lương cả mảng — giải thích vì sao bản gốc thay đổi dù Java pass-by-value.

  • Thử viết method "hoán đổi hai biến int" — chứng kiến nó bất lực, rồi giải thích bằng bài học hôm nay.

Kết luận

Bạn vừa có công cụ tổ chức code đầu tiên: method — khai báo với tham số và giá trị trả về, overload để một tên phục vụ nhiều kiểu dữ liệu, hiểu pass-by-value đủ sâu để trả lời phỏng vấn lẫn tránh bug, và scope giữ biến sống đúng chỗ. Đây cũng là viên gạch cuối của Giai đoạn 1 — nền tảng ngôn ngữ. Từ Ngày 11, ta bước vào vùng đất lớn nhất của Java: lập trình hướng đối tượng — bắt đầu với lớp và đối tượng, nơi dữ liệu và method về chung một nhà. Hẹn gặp lại!

S

Site Admin

Engineer and writer. Building things with TypeScript and distributed systems.

Bình luận (0)

Bạn cần đăng nhập bằng Google để bình luận.

Hãy là người bình luận đầu tiên.

Bài viết liên quan

99 Ngày Spring — Ngày 14: Bean Validation — @Valid & bộ constraint chuẩn

Ngày 14 của 99 Ngày Spring: Bean Validation khai báo luật ngay trên DTO — @Valid kích hoạt, bộ constraint chuẩn (@NotBlank vs @NotEmpty vs @NotNull, @Size, @Email, @Min/@Max), bẫy int vs Integer, validate object lồng nhau không tự lan, và lỗi 400 gom một lượt.

10 thg 8, 20265 phút0
99 Ngày Java — Ngày 14: Kế thừa (Inheritance)

Ngày 14 của 99 Ngày Java: extends trao gia tài từ lớp cha, override với @Override và super., thứ tự khai sinh cha trước con sau qua super(...), protected trả nợ Ngày 13, và phép thử is-a vs has-a — khi nào nên composition thay vì kế thừa.

10 thg 8, 20266 phút0
99 Ngày Spring — Ngày 13: @RequestBody & DTO — đừng công khai entity ra API

Ngày 13 của 99 Ngày Spring: @RequestBody + Jackson deserialize JSON thành object, DTO là hợp đồng API — chặn lỗ hổng mass assignment chiều vào và rò rỉ dữ liệu chiều ra, request/response DTO khác nhau là bình thường, và Jackson annotations tinh chỉnh hợp đồng JSON.

9 thg 8, 20266 phút9