Được chỉnh sửa ngày 20/12/2022.
Chào mừng các bạn đến với bài học Java thứ 27, bài học về Gói ghém dữ liệu, và về cách sử dụng các phương thức getter và setter. Đây là bài viết trong chuỗi bài về lập trình ngôn ngữ Java của Yellow Code Books.
Bài học hôm nay chúng ta cùng xem qua cách tổ chức các phương thức getter và setter trong Java. Hai phương thức này là gì? Chúng có liên hệ như thế nào đến việc gói ghém dữ liệu? Và gói ghém dữ liệu là gì? Mời các bạn cùng đọc tiếp nhé.
Làm Quen Với Getter, Setter Và Gói Ghém Dữ Liệu
Getter và setter là hai tên gọi của hai thể loại phương thức. Chúng có liên quan đến đặc tính gói ghém dữ liệu trong lập trình hướng đối tượng, tiếng Anh gọi đặc tính này là encapsulation. Trước đó, các định nghĩa về khả năng truy cập cũng là một dạng của gói ghém dữ liệu.
Chúng ta làm quen với gói ghém dữ liệu. Tính gói ghém dữ liệu là một trong những đặc tính cơ bản trong kế thừa, bên cạnh các đặc tính khác, như tính kế thừa (inheritance) mà bạn đã biết, tính đa hình (polymorphism) và tính trừu tượng (abstraction) mà bạn sẽ làm quen sau. Đặc tính gói ghém dữ liệu giúp che đi các thuộc tính của một lớp, khiến cho các lớp khác không thể “nhìn thấy”, không thể thay đổi hay chỉnh sửa các giá trị của các thuộc tính này. Bạn cũng được biết các khả năng truy cập private hay protected đều giúp ích rất nhiều trong việc gói ghém dữ liệu đúng không nào.
Vậy getter và setter liên quan gì đến gói ghém dữ liệu? Các phương thức này giúp việc quản lý các thuộc tính private hay protected của một lớp được thoải mái và tường minh hơn. Bạn có thể chỉ định các thuộc tính này sao cho chỉ được phép đọc (thông qua phương thức getter), hoặc chỉ được phép ghi (thông qua phương thức setter), hoặc cho phép cả đọc lẫn ghi (thông qua cả getter và setter) từ các lớp bên ngoài.
Định nghĩa trên chắc đủ rõ ràng cho bạn để hiểu loại phương thức này đúng không. Nếu chưa thực sự hiểu về nó thì mời bạn đọc tiếp các phần sau nhé.
Tổ Chức Các Phương Thức Getter Và Setter Như Thế Nào Để Đảm Bảo Tính Gói Ghém Dữ Liệu
Để getter và setter được tạo ra nhưng vẫn đảm bảo sự gói ghém cho các thuộc tính trong một lớp, chúng ta có các nguyên tắc sau.
– Luôn có thói quen định nghĩa khả năng truy cập cho các thuộc tính trong một lớp là private.
– Sau đó tùy nhu cầu mà xây dựng phương thức getter hay setter, hay cả getter và setter cho từng thuộc tính private đó. Với các phương thức setter bạn nên đặt tên theo cấu trúc setTênThuộcTính(). Còn với getter thì bạn nên đặt là getTênThuộcTính(). Việc xuất hiện của getter và setter cho các thuộc tính private là không bắt buộc. Bạn có thể không cần khai báo bất cứ getter hay setter nào cả nếu như bạn không muốn thuộc tính đó được thấy bởi bất kỳ lớp nào. Hoặc bạn chỉ cần xây dựng getter cho một thuộc tính private nếu bạn muốn thuộc tính đó chỉ được xem, không được sửa chữa. Hoặc chỉ xây dựng setter cho một thuộc tính private nếu bạn muốn thuộc tính đó chỉ có khả năng chỉnh sửa từ bên ngoài mà không được xem. Hoặc có cả getter và setter nếu nó được phép xem và sửa.
Trường hợp thuộc tính private có cả getter lẫn setter thoạt nghe đến bạn có thể nghĩ rằng, tại sao không xây dựng public cho các thuộc tính này, tại sao lại phải rườm rà set nó là private, sau đó lại thêm cả getter lẫn setter?
Bạn nên biết, sự rườm rà này rất đáng giá, bạn nên tập làm quen với nguyên tắc gói ghém dữ liệu kiểu này. Đầu tiên nó tạo cho bạn một thói quen làm việc theo nguyên tắc, OOP là một lĩnh vực có khá nhiều nguyên tắc và đây là nguyên tắc đầu tiên mà bạn có thể dễ dàng để thực hiện. Sau đó, cách xây dựng getter và setter như thế này giúp giảm thiểu lỗi cho ứng dụng, ai mà biết với việc khai báo các thuộc tính của lớp là public ngay từ đầu, các lớp khác đều có quyền thấy và sửa chữa các thuộc tính này có gây ra bất kỳ lỗi tiềm ẩn nào hay không. Bạn tưởng tượng xem khi đối tượng HinhTron được khởi tạo với một banKinh được chỉ định ở constructor, nhưng khi bắt đầu vào tính diện tích thì ở đâu đó lại truy xuất đến thuộc tính banKinh public và chỉnh sửa nó, phương thức tính diện tích lúc này có thể sẽ cho ra kết quả không mong muốn. Nhưng với getter và setter được khai báo, bạn có quyền thêm các dòng code đảm bảo tính đúng đắn của dữ liệu truyền vào cho thuộc tính banKinh (bài thực hành số 2 dưới đây giúp bạn rõ hơn), hơn nữa bạn còn có thể kiểm tra các điều kiện xem thuộc tính này có đang được dùng để tính toán ở đâu không, có cần phải thay đổi ngay lúc này hay delay bao lâu để tránh việc sai số cho các tính toán hay không.
Bài Thực Hành Số 1
Nào, để dễ hiểu một chút, chúng ta cùng đến với bài thực hành sau. Chúng ta hãy xây dựng lại lớp HinhHoc theo đúng tiêu chí của gói ghém dữ liệu bằng cách chỉ định các thuộc tính ở lớp này là private.
public class HinhTron { private final float PI = 3.14f; private float banKinh; private float chuVi; private float dienTich; // Constructor public HinhTron(float banKinh) { this.banKinh = banKinh; } public void tinhChuVi() { chuVi = 2 * PI * banKinh; } public void tinhDienTich() { dienTich = PI * banKinh * banKinh; } }
Bạn có thể thấy, lớp HinhTron đã gói ghém các thuộc tính của nó, sao cho banKinh, chuVi và dienTich đều là private. banKinh chỉ được khởi tạo duy nhất khi HinhTron được khởi tạo. Sau đó, việc tinhChuVi() hay tinhDienTich() diễn ra an toàn, banKinh không thể được thay đổi ở bất kỳ một chỗ nào đó khác trong toàn bộ chương trình. Các lớp khác bên ngoài cũng không thể nào tự thay đổi các thuộc tính chuVi và dienTich, nó thuộc quyền quản lý và tùy ý thay đổi của riêng HinhTron mà thôi. Bạn hãy xem main() sử dụng HinhTron như sau.
public class MainClass { public static void main(String[] args) { HinhTron hinhTron = new HinhTron(20); hinhTron.tinhChuVi(); hinhTron.tinhDienTich(); } }
Nếu giờ đây bạn muốn main() in ra kết quả của các phép tính chu vi và diện tích, hãy thêm getter cho các thuộc tính này bên trong HinhTron.
public class HinhTron { private final float PI = 3.14f; private float banKinh; private float chuVi; private float dienTich; // Constructor public HinhTron(float banKinh) { this.banKinh = banKinh; } public float getChuVi() { return chuVi; } public float getDienTich() { return dienTich; } public void tinhChuVi() { chuVi = 2 * PI * banKinh; } public void tinhDienTich() { dienTich = PI * banKinh * banKinh; } }
Khi đó main() có thể đọc được các giá trị bên trong HinhTron như thế này.
public class MainClass { public static void main(String[] args) { HinhTron hinhTron = new HinhTron(20); hinhTron.tinhChuVi(); hinhTron.tinhDienTich(); float chuVi = hinhTron.getChuVi(); float dienTich = hinhTron.getDienTich(); System.out.println("Chu vi hình tròn: " + chuVi + "; Và diện tích: " + dienTich) ; } }
Bạn đã hiểu cách dùng getter và setter rồi đúng không nào. Bài thực hành tiếp theo đây cho chúng ta thấy thêm một lợi ích và cách sử dụng nữa của việc sử dụng chúng.
Bài Thực Hành Số 2
Bài thực hành này xây dựng lớp SinhVien với các thuộc tính private kèm với các phương thức getter và setter cho chúng. Bạn có thể thấy rằng bạn hoàn toàn có thể tùy biến sao cho chính setter là một phương thức private (bạn không cho ghi vào thuộc tính từ bên ngoài lớp luôn, nhưng chính setter private này lại giúp kiểm tra chút ràng buộc điều kiện). Phương thức getter getTuoi() cũng được xử lý một chút trước khi trả kết quả ra bên ngoài.
public class SinhVien { private String ten; private int tuoi; public SinhVien(String ten, int tuoi) { setTen(ten); setTuoi(tuoi); } private void setTen(String ten) { if (ten == null || ten.isEmpty()) { // Nếu biến ten chưa khởi tạo (mang giá trị null), hoặc biến ten có nội dung rỗng // Thì hãy lưu với tên là "Không biết" this.ten = "Không biết"; } else { this.ten = ten; } } public String getTen() { return ten; } private void setTuoi(int tuoi) { // Kiểm tra tuổi có hợp lý hay không, nếu hợp lý thì lưu vào, // nếu không sẽ tìm cách báo lỗi bằng cách lưu giá trị âm if (tuoi > 18) { this.tuoi = tuoi; } else { this.tuoi = -1; } } public String getTuoi() { if (this.tuoi != -1) { // Tuổi hợp lệ return String.valueOf(tuoi); } else { return "Tuổi không hợp lệ"; } } }
Với khai báo lớp như trên. Giả sử mình khởi tạo các đối tượng sinh viên này ở main() như sau.
public class MainClass { public static void main(String[] args) { // Khởi tạo các đối tượng sinhVien // với các thông tin về tên và tuổi SinhVien sinhVien1 = new SinhVien("", 23); SinhVien sinhVien2 = new SinhVien("Peter", 17); // In thông tin các sinh viên System.out.println("Sinh viên 1 có tên: " + sinhVien1.getTen() + ", tuổi: " + sinhVien1.getTuoi()); System.out.println("Sinh viên 2 có tên: " + sinhVien2.getTen() + ", tuổi: " + sinhVien2.getTuoi()); } }
Sau đây là kết quả in ra console.
Sinh viên 1 có tên: Không biết, tuổi: 23 Sinh viên 2 có tên: Peter, tuổi: Tuổi không hợp lệ
Dùng InteliJ Để Khai Báo Getter Và Setter Tự Động
Như trên mình có nói, việc đặt tên cho getter và setter nên là getTênThuộcTính() và setTênThuộcTính(). Tuy nhiên, InteliJ đã hỗ trợ chúng ta một công cụ tự động phát sinh tên cho các phương thức getter và setter của thuộc tính private nào đó .
Bạn hãy cùng mình thử nghiệm tính năng sinh code tự động qua các bước sau. Đầu tiên bạn hãy thử tạo một lớp ToaDo có khai báo một hay nhiều thuộc tính private như sau.

Nếu bạn click lên một trong các thuộc tính private này, bạn sẽ thấy icon cảnh báo màu vàng bên thanh trái của editor. Vì hệ thống thấy rằng chúng ta khai báo private cho thuộc tính, mà không sử dụng chúng ở bên trong lớp, thì hiển nhiên là có vấn đề rồi, vì bên ngoài lớp có nhìn thấy các giá trị này đâu.
Khi này bạn cứ click vào một trong các cảnh báo này, một dialog nhỏ xuất hiện như sau.

Bạn đã thấy có các tùy chọn Create getter for ‘x’ và Create setter for ‘x’. Vâng, đó là các cách mà InteliJ sẽ thêm tự động các phương thức getter và setter. Bạn có thể tự trải nghiệm nhé, mình toàn dùng chúng cho việc tạo nhanh code đấy.
Kết Luận
Vậy là chúng ta vừa xem qua kiến thức về xây dựng các phương thức getter và setter trong hướng đối tượng. Thực ra, việc có nên xây dựng getter và setter cho các thuộc tính của lớp trong Java hay cứ khai báo public hết cho chúng, từng là chủ đề tranh cãi. Vì nhìn chung lại getter và setter sẽ làm cho các lớp trở nên rườm rà hơn. Có một số ý kiến cũng cho rằng hiệu năng của ứng dụng cũng giảm đi với getter và setter. Nhưng như bài học có nói đến, rằng đây là một trong những cách gói ghém dữ liệu của lớp. Với getter và setter, bạn sẽ có thể chỉ định được thuộc tính đó hoàn toàn ẩn, hay chỉ đọc, hay chỉ ghi, hay có thể đọc và ghi. Một cách để làm cho code bạn được tường minh hơn, ít lỗi hơn, và tuân thủ nguyên tắc OOP hơn.
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ài tiếp theo đây chúng ta sẽ tìm hiểu về từ khóa static bên trong một lớp nhé.
Cảm ơn anh vì hướng dẫn rất cụ thể.
Em muốn hỏi là trong ví dụ SinhVien trên, sao thuộc tính tuổi lại có kiểu giá trị là String, rồi khi mình dùng hàm if lại so sánh tuổi > 18 được ạ?
Chào bạn Thảo. Thực ra thì thuộc tính tuổi nên dùng int hay hơn, nhưng với ví dụ này mình muốn làm cho nó “mạo hiểm” hơn, bằng cách khai báo tuổi là String. Khi xem lại mình đồng ý với bạn dùng kiểu int sẽ dễ hiểu hơn, có lẽ mình sẽ sửa lại nội dung ví dụ này. Tuy nhiên, bạn có thấy ở phương thức setTuoi(int), mình đã dùng kiểu int để so sánh tuổi > 18 trước khi chuyển đổi từ int sang String bằng cách gọi String.valueOf(tuoi), bạn xem lại nha.
Chào anh, em đã kiểm tra các bài học java trước đó nhưng không tìm thấy ý nghĩa của Integer.valueOf(tuoi) và String.valueOf(tuoi). Anh có thể giải thích dùm em được không
Chào bạn, Integer.valueOf() là phương thức static có sẵn bên trong lớp Integer của hệ thống. Phương thức này giúp mình chuyển đổi kiểu dữ liệu từ kiểu String về kiểu int. Chẳng hạn chuyển từ Integer.valueOf(“5”) sẽ cho ra 5 vậy mà. Đôi khi chúng ta sẽ dùng các phương thức có sẵn này để đỡ phải viết lại các thuật toán chuyển đổi. Bạn thấy trong bài học cũng có sử dụng thêm String.valueOf() cho mục đích ngược lại từ int sang String. Với ví dụ của bài học thì do mình sử dụng tuổi kiểu String ở một nơi, và tuổi kiểu int ở một nơi khác, nên mới có chuyện chuyển đổi qua lại giữa hai giá trị này cho các bạn có thêm kiến thức tham khảo. Bạn có thể sử dụng đồng nhất hoặc là int hoặc là String cho đỡ phải dùng đến phương thức chuyển đổi nhé.
Chào anh!
Trong ví dụ về lớp sinh viên, trong phương thức getter-setter tại sao anh không dùng if, else if và else để đặt điều kiện biên cho số tuổi phải lớn hơn 18 và khác số âm mà anh phải dùng gettuoi khác số âm và settuoi để tuổi lớn hơn 18 ạ và tại sao anh ko đặt cả hai đk là không âm và hơn 18 cho một phương thức getter hoặc setter mà phải dùng cả hai hoặc ngược lại. Mong anh giải đáp vì e đã suy nghĩ khá lâu rồi mà vẫn chưa hiểu , e tiếp xúc với java là đã mê nó rồi. Em cảm ơn ạ, mong anh giải đáp. Thank anh.
Chào bạn Minh. Cảm ơn bạn đã giúp mình tìm lỗi. Tuy nhiên mình có vài ý sau, Với ví dụ của mình trên kia, mình chỉ mong là các bạn hiểu ý đồ của bài thực hành là gì thôi. Còn việc viết như vậy đã đúng hay chưa, thì thú thật là nhiều khi ở thực tế chả ai viết như mình đâu bạn ạ. Đúng là như bạn nói, nên ràng buộc hết tất cả các điều kiện trước khi lưu tuổi vào, chứ ai lại thấy tuổi không hợp lệ lại lưu -1 vào như mình đâu. Nhưng qua đây mình cũng chỉ thể hiện ra là có các cách dùng getter và setter như vậy đấy, còn dùng như thế nào cho đúng thì mình nhờ các bạn suy xét lại giúp mình ở các project của các bạn nhé.
Anh ơi cho em hỏi hàm nhập thông tin của em khi đi qua phương thức set này nó lại nhận luôn giá trị sai ạ! Có phải tại hàm nhập có vấn đề không nhỉ?
Vd: Em cái trong set điều kiện mã hàng phải có kích thước bằng 5 && mã hàng bắt đầu bằng “HH” thì mới gán giá trị cho lên hàm get, nhưng em nhập sai thì nó vẫn nhận và in giá trị đó ra.
Em cảm ơn!
Trong trường hợp nghi vấn như thế này thì bạn có thể thực hiện debug ứng dụng, để có thể check từng bước một xem lỗi xảy ra từ bước nào. Để biết rõ hơn về debug thì bạn có thể tham khảo ở các bài viết trên mạng, tùy vào IDE mà bạn đang dùng, hoặc bạn có thể chat với mình theo Facebook fan page để mình biết thêm thông tin nhé.