Được chỉnh sửa ngày 19/12/2022.
Chào mừng các bạn đã đến với bài học Java thứ 25, bài học về Khả năng truy cập (Access Modifier). Đây là bài viết trong chuỗi bài về lập trình ứng dụng Java của Yellow Code Books.
Bài hôm nay chúng ta sẽ nói đến một vấn đề đã được hứa khá lâu, từ khi các bạn mới vừa làm quen với các thuộc tính và phương thức của lớp, đó là kiến thức về khả năng truy cập vào các thành phần của lớp. Vậy thì cái khả năng truy cập này là gì và chúng quan trọng như thế nào, mời các bạn cùng theo dõi bài học nhé.
Khả Năng Truy Cập (Access Modifier) Là Gì?
Khả năng truy cập, hay còn gọi là access modifier, là các từ khóa được Java cung cấp sẵn. Và bạn, hay các lập trình viên khác sẽ sử dụng các định nghĩa của các từ khóa này, để thể hiện các quyền được truy cập vào các thành phần của lớp. Các thành phần của lớp ở đây mình muốn nhắc đến bao gồm các thuộc tính, các phương thức, và cả các constructor của một lớp.
Các từ khóa liên quan đến access modifier trong Java chính là: private, protected, public và default.
Định Nghĩa Khả Năng Truy Cập Như Thế Nào?
Chúng ta khoan hãy nói đến từng khả năng truy cập đã nêu trên, mà hãy điểm lại xem làm cách nào để khai báo một khả năng truy cập nhé.
Bạn hãy nhớ lại đi, trong các cú pháp liên quan đến việc khai báo các thành phần của lớp, mình đều có nêu cách khai báo khả năng truy cập này rồi đấy.
Chẳng hạn như khi khai báo thuộc tính của lớp này.
[khả_năng_truy_cập] kiểu_thuộc_tính tên_thuộc_tính [= giá_trị_ban_đầu];
Hay khi khai báo phương thức của lớp này.
[kả_năng_truy_cập] kiểu_trả_về tên_phương_thức () { // Các dòng code }
Hay khi khai báo một constructor này.
[khả_năng_truy_cập] tên_phương_thức () { // Các dòng code }
Vậy bạn có thể hiểu cách để định nghĩa một khả năng truy cập vào các thành phần lớp rồi đúng không nào. Dưới đây là một ví dụ đầy đủ cho việc khai báo các khả năng truy cập này ở lớp HinhTron.
public class HinhTron { protected final float PI = 3.14f; private float banKinh; // Constructor public HinhTron(float banKinh) { this.banKinh = banKinh; } protected float tinhChuVi() { return 2 * PI * banKinh; } protected float tinhDienTich() { return PI * banKinh * banKinh; } public void xuatThongTin() { System.out.println("Đây là Hình tròn"); } }
Ý Nghĩa Của Các Khả Năng Truy Cập
Nào, giờ chúng ta sẽ đi qua từng khả năng truy cập xem chúng có ý nghĩa và lợi ích gì nhé.
Trước hết, mình xin tổng hợp các khả năng truy cập dựa trên một bảng dưới đây, để cho các bạn dễ nhớ. Bảng này chỉ đơn giản mang các giá trị “Có” nếu như định nghĩa này cho phép truy cập ở một phạm vi nào đó, và mang giá trị “Không” cho ý nghĩa ngược lại.
Phạm vi | private | default | protected | public |
---|---|---|---|---|
Bên trong lớp | Có | Có | Có | Có |
Ở lớp khác, nhưng trong cùng package | Không | Có | Có | Có |
Ở lớp con, trong cùng package | Không | Có | Có | Có |
Ở lớp con, khác package | Không | Không | Có | Có |
Ở bất cứ lớp nào khác (không phải lớp con), khác package | Không | Không | Không | Có |
Giờ mình sẽ nói rõ hơn từng khả năng truy cập.
Khả Năng Truy Cập private
Nếu bạn chỉ định một thành phần nào đó (thuộc tính, phương thức hay constructor) của lớp là private, thì như bảng trên, bạn chỉ có thể truy xuất đến thành phần này bên trong lớp đó mà thôi.
Khả năng private này mang ý nghĩa bảo vệ các thành phần bên trong lớp, không cho các lớp bên ngoài có thể “nhìn thấy” (đọc và chỉnh sửa) được.
- Nếu private được chỉ định cho một thuộc tính, thuộc tính đó sẽ không được phép truy cập, hay chỉnh sửa từ các lớp khác, trừ khi bạn xây dựng các phương thức getter và setter cho thuộc tính đó mà mình sẽ nói sau.
- Còn nếu private được chỉ định cho một phương thức, phương thức đó sẽ không được truy cập hay kế thừa từ lớp khác.
- Và nếu private được chỉ định cho constructor, constructor này sẽ không dùng được để khởi tạo đối tượng cho lớp đó.
Bạn nên tập một thói quen, đó là luôn khai báo private cho các thuộc tính bên trong lớp. Với phương thức thì nếu bạn không chắc nó có được dùng ở đâu đó bên ngoài lớp này hay không, thì cứ chỉ định private luôn cho chắc cú.
Bài Thực Hành Số 1
Bài thực hành này mình tạo lớp HinhTron có khai báo các thành phần là private. Bạn có thể thấy các thành phần này khi dùng bên trong lớp HinhTron thì rất tốt, không bị báo lỗi từ hệ thống.
public class HinhTron { private final float PI = 3.14f; private float banKinh; // Constructor private HinhTron() { } private float tinhChuVi() { return 2 * PI * banKinh; } private float tinhDienTich() { return PI * banKinh * banKinh; } }
Vậy bạn có thể tự trắc nghiệm xem, với việc gọi đến các thành phần này của HinhTron từ main(), thì dòng code nào sau đây sẽ bị hệ thống báo lỗi nhé.
public class MainClass { public static void main(String[] args) { HinhTron hinhTron = new HinhTron(); hinhTron.banKinh = 20; hinhTron.tinhDienTich(); } }
Kết quả dòng bị báo lỗi ở dưới đây.
Dòng 3 báo lỗi Dòng 4 báo lỗi Dòng 5 báo lỗi
Khả Năng Truy Cập default
Thực ra không có một kiểu định nghĩa khả năng default nào cả. default ở đây có nghĩa là bạn không định nghĩa khả năng truy cập gì hết, nó giống như các bài học mở đầu mà chúng ta đã làm quen, khi đó chúng ta tạm thời để trống các khai báo khả năng truy cập này.
Và với việc để trống khả năng truy cập cho các thành phần của lớp, thì bạn có thể thấy rằng, nếu bên trong lớp đó hoặc các lớp khác trong cùng package, sẽ có thể nhìn thấy và truy xuất đến các thành phần đó. Khả năng nhìn thấy và truy xuất chỉ bị chặn khi bạn gọi đến các thành phần này từ một lớp nằm ngoài package mà thôi.
Khả năng này nên ít được dùng hơn, vì bạn nên tập thói quen chỉ định khả năng truy cập một cách tường minh từ bây giờ.
Bài Thực Hành Số 2
Ở đây mình bỏ hết các khai báo khả năng truy cập cho các thành phần của lớp HinhTron.
package shapes; public class HinhTron { final float PI = 3.14f; float banKinh; // Constructor HinhTron() { } float tinhChuVi() { return 2 * PI * banKinh; } float tinhDienTich() { return PI * banKinh * banKinh; } }
Và tương tự, với các dòng code khai báo ở main() như sau, bạn đoán xem dòng nào sẽ bị lỗi nhé.
package main; import shapes.HinhTron; public class MainClass { public static void main(String[] args) { HinhTron hinhTron = new HinhTron(); hinhTron.banKinh = 20; hinhTron.tinhDienTich(); } }
Kết quả dòng bị lỗi như sau.
Dòng 7 báo lỗi Dòng 8 báo lỗi Dòng 9 báo lỗi
Sở dĩ các dòng này báo lỗi, là vì chúng ở package main khác với package shapes của HinhTron. Bạn thử đưa chúng về cùng một package rồi kiểm chứng lại nhé.
Khả Năng Truy Cập protected
Khả năng này mang ý nghĩa bảo vệ các thành phần của lớp cho các lớp con của nó dùng lại hoặc ghi đè. Do đó có thể hiểu, khả năng protected sẽ trao quyền sử dụng hoàn toàn tự do cho các lớp con, dù có ở cùng hay khác package. Khả năng này chỉ giới hạn với các lớp không phải lớp con của nó và nằm ngoài package mà thôi.
Bài Thực Hành Số 3
Lần này bạn hãy xem các khai báo protected cho các thành phần bên trong HinhTron như sau.
package shapes; public class HinhTron { protected final float PI = 3.14f; protected float banKinh; // Constructor public HinhTron(float banKinh) { this.banKinh = banKinh; } protected float tinhChuVi() { return 2 * PI * banKinh; } protected float tinhDienTich() { return PI * banKinh * banKinh; } }
Và với code ở main(), bạn lại thử đoán xem dòng code nào bị báo lỗi.
package main; import shapes.HinhTron; public class MainClass { public static void main(String[] args) { HinhTron hinhTron = new HinhTron(10); hinhTron.tinhDienTich(); } }
Kết quả dòng bị lỗi như sau.
Dòng 8 báo lỗi
Phương thức tinhDienTich() rơi vào trường hợp “Không” duy nhất của protected. Bởi nó vừa gọi từ một lớp không có quan hệ kế thừa với HinhTron, và cũng nằm ngoài package so với HinhTron nữa.
Bài Thực Hành Số 4
Vẫn với code của Bài thực hành số 3, lần này mình khai báo thêm HinhTru kế thừa từ HinhTron, và main() như sau. Bạn xem có dòng nào báo lỗi không nhé.
package shapes; public class HinhTru extends HinhTron { public float chieuCao; // Constructor public HinhTru(float banKinh, float chieuCao) { super(banKinh); this.chieuCao = chieuCao; } public float tinhTheTich() { return tinhDienTich() * chieuCao; } }
package main; import shapes.HinhTru; public class MainClass { public static void main(String[] args) { HinhTru hinhTru = new HinhTru(10, 20); hinhTru.tinhTheTich(); } }
Đây là kết quả các dòng báo lỗi.
Không dòng nào báo lỗi hết.
Vì HinhTru kế thừa các phương thức từ cha và set lại khả năng truy cập là public.
Khả Năng Truy Cập public
Có lẽ không cần tốn nhiều giấy mực để nói về khả năng này. Nếu bạn chỉ định một thành phần trong lớp là public, thì coi như tất cả các lớp nào khác đều có thể truy xuất đến các thành phần này.
Kết Luận
Vậy là mình vừa “trả” xong món nợ về kiến thức khả năng truy cập, hay còn gọi là access modifier. Từ bài học sau chúng ta sẽ dùng nhiều các khai báo khả năng của ngày hôm nay.
Cảm ơn bạn đã đọc các bài viết của Yellow Code Books. Bạn hãy ủng hộ blog bằng cách:
– Đánh giá 5 sao ở mỗi bài viết nếu thấy thích.
– Comment bên dưới mỗi bài viết nếu có thắc mắc.
– Để lại địa chỉ email của bạn ở thanh bên phải để nhận được thông báo sớm nhất khi có bài viết mới.
– Chia sẻ các bài viết của Yellow Code Books đến nhiều người khác.
– Ủng hộ blog theo hướng dẫn ở thanh bên phải để blog ngày càng phát triển hơn.
Bài Kế Tiếp
Bạn đã biết đến việc sử dụng từ khóa final trong khai báo biến và thuộc tính? Vậy thì bài sau chúng ta cùng xem từ khóa này được dùng trong phương thức và ngay cả khi khai báo một lớp, sẽ như thế nào nhé.
hgf
Đang lau bàn phím hả bạn 😀
cảm ơn ad, bài viết hay. Mong ad ra bài nhanh hơn chứ cả tuần mới có một bài lâu quá
Ô kê, cảm ơn bạn, mình sẽ cố gắng thu xếp thời gian viết nhiều nhiều.
cảm ơn anh, bài viết rất hữu ích!
khả năng truy cập default trong vd dòng 8 hình như không có lỗi anh ời
Bạn phải đảm bảo lớp HinhTron và lớp MainClass nằm ở 2 package khác nhau thì mới bào lỗi nhé
tuyệt vời anh ơi Yeah