Menu

Nạp chồng phương thức trong Java: cùng tên, khác tham số

Cách nạp chồng phương thức (method overloading) trong Java cho phép nhiều phương thức dùng chung một tên nhưng nhận tham số khác nhau, cách trình biên dịch chọn phiên bản và các lỗi mơ hồ cần tránh.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Một tên, nhiều phiên bản

Ở trang trước, bạn đã thấy tham số của một phương thức quyết định nó nhận gì. Nạp chồng phương thức (method overloading) đi xa hơn: bạn có thể đặt cùng một tên cho nhiều phương thức, miễn là danh sách tham số của chúng khác nhau. Trình biên dịch coi chúng là các phương thức riêng biệt và chọn đúng phương thức dựa vào các đối số bạn truyền vào.

Đó là lý do System.out.println in được cả int, String, boolean lẫn double: không chỉ có một println, mà có nhiều phiên bản nạp chồng dùng chung tên đó. Bạn viết lời gọi theo ý mình, trình biên dịch sẽ ghép nó với phiên bản phù hợp.

Cả hai phương thức đều tên là square, nhưng một cái nhận int, cái còn lại nhận double. Giá trị 5 là một int, nên phiên bản thứ nhất chạy; 2.5 là một double, nên phiên bản thứ hai chạy.

Thế nào là một phiên bản nạp chồng khác

Các phiên bản nạp chồng phải khác nhau ở danh sách tham số, tức là có ít nhất một trong các điểm sau:

  • khác số lượng tham số,
  • khác kiểu tham số, hoặc
  • khác thứ tự các kiểu.

Mỗi lời gọi có danh sách tham số khớp với đúng một trong ba phương thức join, nên không có gì nhầm lẫn.

Kiểu trả về không được tính

Một cái bẫy phổ biến với người mới: cố nạp chồng chỉ dựa vào kiểu trả về. Kiểu trả về không thuộc chữ ký mà trình biên dịch dùng, nên đoạn code sau không biên dịch được:

// Does NOT compile - same name, same parameters, only the return type differs
static int   value() { return 1; }
static double value() { return 1.0; }   // error: value() is already defined

Trình biên dịch không phân biệt được hai phương thức này, vì ở chỗ gọi như value(), không có đối số nào gợi ý bạn muốn phiên bản nào. Bạn có thể cho các phiên bản nạp chồng kiểu trả về khác nhau, nhưng chỉ khi danh sách tham số của chúng vốn đã khác nhau.

Java chọn phiên bản nạp chồng như thế nào

Khi có nhiều phiên bản cùng nhận được đối số của bạn, Java chọn phiên bản cụ thể nhất và ưu tiên khớp kiểu chính xác hơn là chuyển đổi mở rộng kiểu. Xem điều gì xảy ra với một đối số int:

show(7) khớp chính xác với int, dù long và double cũng chứa được 7 sau khi mở rộng. Chỉ khi phiên bản khớp chính xác bị xóa, trình biên dịch mới mở rộng int thành long, rồi thành double. Việc chọn này được quyết định hoàn toàn lúc biên dịch, dựa trên kiểu khai báo của các đối số.

Cẩn thận với lời gọi mơ hồ

Nếu không có phiên bản nào rõ ràng là phù hợp nhất, trình biên dịch không đoán mà báo lỗi. Trường hợp này hay gặp nhất với null, giá trị hợp với mọi kiểu tham chiếu:

static void handle(String s) { }
static void handle(StringBuilder b) { }

handle(null);   // error: reference to handle is ambiguous

Cả hai phiên bản đều nhận null, và không cái nào cụ thể hơn, nên lời gọi không biên dịch được. Cách sửa là ghi rõ kiểu bằng ép kiểu, như handle((String) null), hoặc thiết kế lại để các phiên bản không đụng nhau. Cũng cần cẩn thận như vậy khi trộn autoboxing với mở rộng kiểu; hãy giữ các bộ nạp chồng đủ đơn giản để mỗi lời gọi chỉ có một lựa chọn hiển nhiên.

Nạp chồng hàm khởi tạo

Nạp chồng không chỉ dành cho phương thức thông thường: hàm khởi tạo dùng nó liên tục để đưa ra nhiều cách tạo một đối tượng. Một hàm khởi tạo không tham số có thể chuyển việc cho một hàm khởi tạo đầy đủ hơn bằng this(...):

Hai hàm khởi tạo dùng chung tên Point nhưng khác số lượng tham số, giống hệt các phương thức nạp chồng. Chuyển tiếp bằng this(...) giúp logic khởi tạo nằm ở một chỗ duy nhất.

Overloading và overriding

Hai khái niệm này nghe giống nhau nhưng không liên quan:

  • Overloading (nạp chồng): cùng tên, danh sách tham số khác nhau, trong cùng một lớp. Trình biên dịch chọn phiên bản lúc biên dịch. Mục đích là đưa ra nhiều biến thể của một thao tác.
  • Overriding (ghi đè): lớp con định nghĩa lại một phương thức kế thừa với cùng tên và cùng tham số. Java chọn phiên bản lúc chạy dựa vào kiểu thực của đối tượng. Mục đích là thay thế hành vi (bạn sẽ gặp nó ở phần kế thừa và đa hình).

Nếu danh sách tham số giống hệt nhau, bạn đang ghi đè (hoặc gây lỗi trùng phương thức nếu trong cùng một lớp); nếu khác nhau, bạn đang nạp chồng.

Tiếp theo: varargs

Nạp chồng cho phép bạn viết join(a, b) và join(a, b, c) thành các phương thức riêng. Nhưng nếu bạn muốn nhận bất kỳ số lượng đối số nào mà không phải khai báo một phiên bản cho mỗi số lượng thì sao? Cú pháp varargs của Java cho phép một phương thức duy nhất nhận danh sách đối số có độ dài thay đổi, và đó là nội dung của trang tiếp theo.

Câu hỏi thường gặp

Nạp chồng phương thức trong Java là gì?

Nạp chồng phương thức (method overloading) là định nghĩa nhiều phương thức cùng tên trong cùng một lớp, mỗi phương thức có danh sách tham số khác nhau (khác số lượng tham số, khác kiểu, hoặc khác thứ tự các kiểu). Trình biên dịch quyết định gọi phiên bản nào bằng cách so khớp các đối số bạn truyền vào với tham số của từng phiên bản. Đây là quyết định lúc biên dịch, không phải lúc chạy.

Hai phương thức trong Java có thể chỉ khác nhau ở kiểu trả về không?

Không. Kiểu trả về không thuộc chữ ký phương thức khi xét nạp chồng, nên khai báo int total() và double total() trong cùng một lớp là lỗi biên dịch. Các phiên bản nạp chồng phải khác nhau ở danh sách tham số: số lượng, kiểu hoặc thứ tự tham số. Kiểu trả về có thể khác, nhưng chỉ đi kèm một khác biệt về tham số, không thể khác riêng mình nó.

Overloading và overriding trong Java khác nhau thế nào?

Overloading là nhiều phương thức cùng tên nhưng khác tham số trong cùng một lớp, được trình biên dịch chọn lúc biên dịch. Overriding là khi lớp con định nghĩa lại một phương thức kế thừa với cùng tên và cùng tham số, được chọn lúc chạy dựa trên kiểu thực tế của đối tượng. Overloading là đưa ra nhiều biến thể; overriding là thay thế hành vi.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu