Thêm hành vi

Bài tiếp này chúng tôi sẽ hướng dẫn các bạn về các phương thức truy cập của Java.

Các phương thức truy cập

Xem xét bên trong đối tượng Adult của chúng ta chỉ bằng cách tham chiếu trực tiếp đến các biến thì cũng thuận tiện nhưng thường thì đó không phải là một ý tưởng hay khi một đối tượng lại moi móc vào bên trong một đối tượng khác theo cách đó. Điều đó vi phạm nguyên lý bao đóng mà chúng ta đã nói trước đó, và nó cũng cho phép một đối tượng chọc ngoáy vào trạng thái nội tại của đối tượng khác. Lựa chọn khôn ngoan hơn là cho một đối tượng có khả năng nói cho đối tượng khác biết giá trị các biến cá thể của nó khi được hỏi tới. Bạn dùng các phương thức truy cập để làm điều này.

Các phương thức truy cập là các phương thức giống như những phương thức khác nhưng chúng thường tuân thủ theo một quy ước đặt tên riêng. Để cung cấp giá trị một biến cá thể cho đối tượng khác, hãy tạo ra một phương thức có tên là getVariableName(). Tương tự như thế, để cho phép các đối tượng khác thiết đặt các biến cá thể của đối tượng của bạn, hãy tạo ra phương thức setVariableName().

Trong cộng đồng Java, các phương thức truy cập này thường được gọi là các getter và các setter vì tên chúng bắt đầu bằng get và set. Chúng là những phương thức đơn giản nhất mà bạn từng thấy, vì thế chúng là những ví dụ tốt để minh họa cho những khái niệm phương thức đơn giản. Bạn nên biết rằng phương thức truy cập là thuật ngữ khái quát để chỉ những phương thức nhận thông tin về một đối tượng nào đó. Không phải tất cả các phương thức truy cập đều tuân theo quy tắc đặt tên dành cho getter và setter như chúng ta sẽ thấy sau này.

Đây là một số đặc tính chung của các getter và setter:

-         Định tố truy cập của các getter và setter điển hình là public.

-         Các getter điển hình là không nhận tham số nào.

-         Các setter điển hình là chỉ nhận một tham số, đó là giá trị mới cho biến cá thể mà chúng thiết đặt.

-         Kiểu trả về của getter điển hình là cùng kiểu với biến cá thể mà nó báo lại giá trị.

-         Kiểu trả lại của setter điển hình là void, nghĩa là chúng không trả lại gì hết (chúng chỉ đặt giá trị cho biến cá thể).

Khai báo các phương thức truy cập

Chúng ta có thể thêm các phương thức truy cập cho biến cá thể age của Adult như sau:

public int getAge() {

          return age;

}

public void setAge(int anAge) {

          age = anAge;

}               

Phương thức getAge() trả lại giá trị của biến age bằng cách dùng từ khóa return. Các phương thức không trả lại giá trị gì thì ngầm hiểu có câu lệnh return void; ở cuối cùng. Trong phương thức lấy giá trị này, chúng ta tham chiếu đến biến age bằng cách dùng tên của biến.

Chúng ta cũng có thể viết return this.age;;. Biến this quy về đối tượng hiện tại. Nó được ngầm hiểu khi bạn tham chiếu trực tiếp đến một biến cá thể. Một số lập trình viên hướng đối tượng Smalltalk thích dùng this bất cứ khi nào họ nói đến một biến cá thể, cũng giống như họ luôn dùng từ khóa self khi viết mã lệnh bằng Smalltalk. Bản thân tôi cũng thích như thế nhưng Java không yêu cầu như vậy và làm thế sẽ chỉ thêm chật chỗ trên màn hình, vì vậy các ví dụ trong tài liệu này sẽ không dùng this trừ trường hợp mã lệnh sẽ không tường minh nếu thiếu nó.

Gọi các phương thức

Giờ ta đã có các phương thức truy cập, chúng ta sẽ thay việc truy cập trực tiếp đến biến age trong phương thức main() bằng lời gọi phương thức. Bây giờ main() sẽ như sau:

public static void main(String[] args) {

          Adult myAdult = new Adult();

          System.out.println("Name: " + myAdult.name);

          System.out.println("Age: " + myAdult.getAge());

          System.out.println("Race: " + myAdult.race);

          System.out.println("Gender: " + myAdult.gender);

}

Nếu bạn chạy lại mã lệnh, sẽ cho ra kết quả như cũ. Lưu ý rằng gọi phương thức của một đối tượng rất dễ dàng. Hãy sử dụng mẫu sau:

 instanceName.methodName()

Nếu phương thức này không cần tham số (ví dụ như getter), bạn vẫn phải viết cặp ngoặc đơn sau tên phương thức khi gọi. Nếu phương thức cần tham số (như setter), thì đặt chúng trong cặp ngoặc đơn, phân cách bởi dấu phẩy nếu có hơn một tham số.

Một lưu ý nữa về setter trước khi ta chuyển sang chủ đề khác: nó nhận một tham số kiểu int có tên là anAge. Sau đó nó gán giá trị của tham số này cho biến cá thể age. Chúng ta có thể đặt cho tham số này cái tên bất kỳ mà ta muốn. Tên không quan trọng nhưng khi bạn tham chiếu đến nó trong phương thức thì phải gọi chính xác cái tên mình đã đặt.

Trước khi chuyển sang phần khác, hãy thử dùng qua setter. Thêm dòng sau vào main() ngay sau khi chúng ta khởi tạo một đối tượng Adult:

myAdult.setAge(35);

Bây giờ thì chạy lại mã lệnh. Kết quả cho thấy tuổi là 35. Những gì diễn ra phía sau khung cảnh này là:

Chúng tra truyền một giá trị số nguyên cho phương thức thông qua tham số.

JRE cấp bộ nhớ cho tham số này và đặt tên cho nó là anAge.

Các phương thức không phải là phương thức truy cập

Các phương thức truy cập thật hữu ích, nhưng ta muốn các đối tượng Adult của ta có thể làm điều gì đó hơn là chỉ chia sẻ dữ liệu của chúng, bởi vậy chúng ta cần bổ sung thêm các phương thức khác. Chúng ta muốn Adult có thể nói, vậy hãy bắt đầu từ đây. Phương thức speak() sẽ như sau:

public String speak() {

          return "hello";

}

Bây giờ thì cú pháp này đã quen thuộc với bạn. Phương thức này trả về một chuỗi. Hãy dùng phương thức này và làm sáng sủa phương thức main(). Thay đổi lời gọi đầu tiên đến println() thành:

System.out.println(myAdult.speak());

Chạy lại mã lệnh bạn sẽ nhìn thấy dòng chữ hello trên màn hình.

Các chuỗi ký tự (Strings)

Cho đến nay chúng ta đã sử dụng một vài biến kiểu String (chuỗi ký tự) nhưng chúng ta vẫn chưa thảo luận về chúng. Xử lý chuỗi trong C tốn rất nhiều công sức vì chúng là các mảng các ký tự 8 bít kết thúc bằng null mà bạn phải tự thao tác. Trong ngôn ngữ Java, chuỗi là đối tượng thuộc hạng nhất, có kiểu String, kèm theo đó là các phương thức cho phép bạn thao tác với nó. Mã lệnh Java cũng gần như ngôn ngữ C về vấn đề chuỗi có kiểu dữ liệu nguyên thủy là char, chứa chỉ một ký tự Unicode đơn lẻ, ví dụ như 'a'.

Chúng ta đã biết cách khởi tạo một đối tượng String và thiết đặt giá trị cho nó, nhưng có nhiều cách khác để thực hiện việc này. Sau đây là vài cách để khởi tạo một cá thể String có giá trị là "hello":

String greeting = "hello";

String greeting = new String("hello");

Vì chuỗi trong ngôn ngữ Java là đối tượng hạng nhất, bạn có thể dùng new để khởi tạo một đối tượng thuộc kiểu chuỗi. Đặt một biến kiểu String cho ta kết quả tương tự, vì Java tạo ra một đối tượng String để chứa chuỗi trực kiện, sau đó gán đối tượng này cho biến cá thể.

Chúng ta có thể làm nhiều thứ với các String và lớp này có rất nhiều phương thức hữu ích. Thậm chí không cần dùng một phưong thức, ta đã có thể làm vài việc lý thú với các chuỗi bằng cách nối một cặp chuỗi, nghĩa là kết hợp chúng, chuỗi này nối sau chuỗi kia:

System.out.println("Name: " + myAdult.getName());

Thay vì dùng dấu +, chúng ta có thể gọi phương thức concat() của đối tượng String để nối nó với một đối tượng String khác:

System.out.println("Name: ".concat(myAdult.getName()));

Mã lệnh này trông hơi lạ, ta hãy duyệt qua một chút, từ trái sang phải:

-         System là đối tượng có sẵn cho phép bạn tương tác với nhiều thứ trong môi trường hệ thống (bao gồm cả một vài khả năng của chính nền tảng Java)

-         out là biến lớp của System, nghĩa là nó có thể truy cập được mà không cần phải có một cá thể của System. Nó đại diện cho màn hình.

-         println() là phương thức của out nhận tham số kiểu String, in nó ra màn hình, và tiếp ngay sau là một ký tự xuống dòng để bắt đầu một dòng mới.

-         "Name: " là một chuỗi trực kiện. Nền tảng Java coi chuỗi trực kiện này là một cá thể String, bởi vậy chúng ta có thể gọi phương thức trực tiếp trên nó.

-         concat() là một phương thức của cá thể String, nhận tham số kiểu String và nối nó với chính cá thể String mà bạn đã gọi phương thức của nó.

-         myAdult là một cá thể Adult của ta.

-         getName() là phương thức truy cập biến cá thể name.

Như thế, JRE sẽ lấy tên của Adult, gọi concat(), và thêm “Bob” vào sau “Name: ".

Trong Eclipse, bạn có thể thấy các phương thức có sẵn trên bất kỳ đối tượng nào bằng cách đặt con trỏ vào sau dấu chấm sau tên biến chứa cá thể, sau đó nhấn Ctrl-phím cách. Thao tác này làm hiện ra ở bên trái dấu chấm một danh sách các phương thức của đối tượng. Bạn có thể cuộn lên cuộn xuống danh sách này bằng các phím mũi tên trên bàn phím, điểm sáng một phương thức bạn muốn rồi sau đó nhấn phím Enter để chọn nó. Ví dụ, để xem tất cả các phương thức có sẵn của đối tượng String, đặt con trỏ vào sau dấu chấm sau chữ “Name: ” rồi nhấn Ctrl-phím cách.

Sử dụng chuỗi

Bây giờ ta hãy dùng phép nối chuỗi trong lớp Adult. Đến lúc này, ta có một biến cá thể là name. Một ý tưởng hay là có một biến firstname và một biến lastname, sau đó nối chúng vào với nhau khi ai đó hỏi tên của Adult. Không vấn đề gì! Hãy thêm phương thức sau đây:

public String getName() {

          return firstname + " " + lastname;

}

Eclipse sẽ hiển thị những đường lượn sóng màu đỏ tại phương thức này vì các biến cá thể ấy còn chưa có, như vậy có nghĩa là mã lệnh sẽ không biên dịch được. Bây giờ ta thay biến cá thể name bằng 2 biến sau đây (với giá trị mặc định làm rõ ý nghĩa hơn):

protected String firstname = "firstname";

protected String lastname = "lastname";

Tiếp đó, thay đổi lời gọi println() đầu tiên như sau:

System.out.println("Name: " + myAdult.getName());

Bây giờ ta có một getter có trang trí hơn để lấy ra các biến tên. Nó sẽ nối các chuỗi đẹp đẽ để tạo ra một tên đầy đủ cho Adult. Chúng ta có thể viết phương thức getName() như sau:

public String getName() {

          return firstname.concat(" ").concat(lastname);

}

Mã lệnh này cũng làm cùng công việc đó, nhưng nó minh họa việc sử dụng tường minh một phương thức của String, nó cũng minh họa việc móc xích các lời gọi phương thức. Khi ta gọi concat() của firstname với một chuỗi trực kiện (dấu cách), nó sẽ trả lại một đối tượng String mới là ghép của hai chuỗi. Ngay sau đó ta lại tiếp tục gọi concat() của chuỗi ghép này để nối tên (firstname) và một dấu cách với lastname. Kết quả ta có tên đầy đủ theo khuôn dạng.

Các toán tử số học và toán tử gán

Adult của chúng ta có thể nói, nhưng không thể di chuyển. Hãy thêm một vài hành vi để nó có thể đi lại được.

Đầu tiên, hãy thêm một biến cá thể để lưu lại số bước chân mà mỗi đối tượng Adult sẽ bước:

public int progress = 0;

Bây giờ bổ sung thêm phương thức có tên là walk():

public String walk(int steps) {

          progress = progress + steps;

          return "Just took " + steps + " steps";

}

Phương thức của chúng ta nhận một tham số là số nguyên để chỉ số bước cần bước, cập nhật progress để ghi nhận số bước chân, sau đó báo kết quả. Cũng là sáng suốt nếu ta bổ sung thêm một getter cho progress nhưng không thêm setter. Tại sao ? Vì ta sẽ không cho phép một đối tượng nào khác gán trực tiếp số bước chân đã đi. Nếu đối tượng khác muốn đề nghị ta bước đi, nó có thể gọi walk(). Đó chính là logic thực tế và đây cũng chỉ là một ví dụ minh họa. Ở các dự án thực sự, các quyết định thiết kế loại này luôn phải được đưa ra và thường không thể quyết định sớm từ trước, bất kể các bậc thầy thiết kế hướng đối tượng nói gì.

Trong phương thức của chúng ta, chúng ta đã cập nhật progress bằng cách thêm steps vào. Chúng ta lại lưu kết quả trong progress. Ta đã dùng toán tử gán cơ bản nhất, =, để lưu kết quả. Ta đã dùng toán tử số học + để cộng hai số hạng. Ta cũng có một cách khác để đạt được cùng mục đích. Mã lệnh dưới đây sẽ cho thấy điều đó:

public String walk(int steps) {

          progress += steps;

          return "Just took " + steps + " steps";

}

Sử dụng toán tử gán += sẽ giúp câu lệnh ngắn gọn hơn cách đầu tiên ta dùng. Nó tránh cho ta khỏi phải tham chiếu đến biến progress hai lần. Nhưng nó thực hiện cùng một việc: nó cộng steps vào progress và lưu kết quả trong progress.

Bảng dưới đây là danh sách và mô tả ngắn gọn của các toán tử số học và toán tử gán thường gặp nhất trong Java (lưu ý rằng một số toán tử số học là nhị phân), có hai toán hạng, và một số khác là đơn nguyên, có một toán hạng).

Toán tử

Cách dùng

Mô tả

+

a + b

Cộng a và b

+

+a

Nâng a lên thành kiểu int nếu a là kiểu byte, short, hoặc char

-

a - b

Lấy a trừ đi b

-

-a

Âm a

*

a * b

Nhân a với b

/

a / b

Chia a cho b

%

a % b

Trả lại phần dư của phép chia a cho b

(nói cách khác, đây là toán tử modulus)

 

 

++

a++

Tăng a thêm 1 đơn vị; tính giá trị của a trước khi tăng

++

++a

Tăng a thêm 1 đơn vị; tính giá trị của a sau khi tăng

--

a--

Giảm a đi 1 đơn vị; tính giá trị của a trước khi tăng

--

--a

Giảm a đi 1 đơn vị; tính giá trị của a sau khi tăng

+=

a += b

Giống như a = a + b

-=

a -= b

Giống như a = a - b

*=

a *= b

Giống như a = a * b

 

 

Chúng ta cũng đã biết một số thứ khác được gọi là toán tử trong ngôn ngữ Java. Ví dụ, dấu chấm . phân định tên của các gói và lời gọi phương thức; cặp ngoặc đơn ( params ) để phân định danh sách các tham số phân cách bằng dấu phẩy của một phương thức; và new, khi theo sau nó là tên của hàm tạo, để khởi tạo một cá thể. Chúng ta sẽ xem xét thêm một vài yếu tố trong phần tiếp theo.

Bài tiếp theo chúng tôi sẽ hướng dẫn các bạn về các lệnh thực thi điều kiện của Java

Theo dõi http://laptrinhtot.com để tiếp tục theo dõi các loạt bài mới nhất về Java nhé!

Tags: