1.1. Những khái niệm cơ bản của lập trình hướng đối tượng 1.1.1. Phương pháp tiếp cận của lập trình truyền thống Để hiểu rõ tại sao lại phải học lập trình hướng đối tượng thì chúng ta sẽ xem xét các phương pháp lập trình truyền thống, từ đó sẽ thấy được các ưu và nhược điểm của từng phương pháp. Nội dung chúng ta sẽ tìm hiểu các phương pháp sau: Lập trình truyền thống đã trải qua hai giai đoạn: Giai đoạn sơ khai, khi khái niệm lập trình mới ra đời, là lập trình tuyến tính. Giai đoạn tiếp theo, là lập trình hướng cấu trúc.
LỚP VÀ ĐỐI TƯỢNG
LỚP VÀ ĐỐI TƯỢNG
2.1.1 Khái niệm lớp và đối tượng
Lớp được coi như một khuôn mẫu cho đối tượng, bao gồm dữ liệu của đối tượng (fields hay properties) và các phương thức (methods) tác động lên dữ liệu đó.
Các đối tượng trong Java được tạo ra từ các lớp, được gọi là các thể hiện của lớp (class instance) Lớp trong Java tương tự như bản ghi trong lập trình C hoặc Pascal, nhưng nó cung cấp tính năng cao cấp hơn.
Ví dụ 1: Ta khai báo 1 lớp SinhVien gồm các thuộc tính:
Lớp này chính là 1 khuôn mẫu Khi ta tạo ra các đối tượng dựa trên lớp này, các đối tượng đều tương tự như mẫu trên
Tạo đối tượng a, khi đó a sẽ có 3 thuộc tính, và ta có thể thao tác gán giá trị các thuộc tính đó như sau:
LopHoc là "CNTT K20B", với các đối tượng b, c, có giá trị đặc trưng riêng cho từng loại đối tượng Nếu bạn đã từng học lập trình C hoặc Pascal, bạn sẽ nhận thấy rằng chúng có sự tương đồng với các bản ghi.
2.1.2 Khai báo/định nghĩa lớp
[] class [extends ] [implements ]
// Định nghĩa các vùng thuộc tính chứa dữ liệu của lớp [bổ ngữ] ;
[bổ ngữ] ;
// Định nghĩa các hàm thành phần của lớp constructor method_1 method_2
Trong đó: class, extends, implements là các từ khoá Những phần trong cặp [ và ] là tùy chọn Những phần này sẽ được đề cập chi tiết ở các phần sau
- class: là từ khóa của java
- ClassName: là tên chúng ta đặt cho lớp
- field_1, field_2: các thuộc tính, các biến, hay các thành phần dữ liệu của lớp
- constructor: là sự xây dựng, khởi tạo đối tượng lớp
- method_1, method_2: là các phương thức/hàm thể hiện các thao tác xử lý, tác động lên các thành phần dữ liệu của lớp
Chúng ta hãy xem ví dụ 2 code về lớp để định nghĩa những gì đã viết trong ví dụ 1
Trong ví dụ 2, chúng ta có một lớp SinhVien với các thuộc tính như hoTen (họ tên), namSinh (năm sinh) và lopHoc (lớp học).
Các tính chất của lớp trong java có các từ khóa đặc trưng như sau:
Trong lập trình, tính chất truy cập của các lớp rất quan trọng Tính chất "public" cho phép lớp có thể được truy cập từ mọi nơi trong hệ thống chương trình, bao gồm các lớp trong cùng gói, các lớp khác gói, và các lớp có quan hệ kế thừa Ngược lại, tính chất "protected" chỉ cho phép lớp được truy cập trong các lớp cùng gói và các lớp có quan hệ kế thừa.
Lớp trừu tượng trong lập trình là một khái niệm quan trọng, không có thể hiện và thường được sử dụng để tổng quát hóa các khái niệm Nó cho phép khai báo các thuộc tính và phương thức cụ thể trong các lớp dẫn xuất, giúp tổ chức mã nguồn một cách hiệu quả và dễ bảo trì.
27 final Lớp hằng, là lớp không cho phép tạo lớp dẫn xuất từ nó
2.1.3 Tạo đối tượng của lớp
Ví dụ 3: Tạo 2 đối tượng sinh viên a và sinh viên b dựa trên class đã định nghĩa ở ví dụ 2:
Vùng dữ liệu (fields) hay thuộc tính (properties) của lớp được khai báo bên trong lớp như sau:
[bổ ngữ] ;
: Có thể là kiểu dữ liệu nguyên thủy hoặc kiểu dữ liệu tham chiếu
: Có thể là public, protected, mặc định, private, static, final, volatile, transient
Bổ ngữ public xác định tính công khai của các thành phần dữ liệu, xác định phạm vi truy cập tương tự như đối với lớp
Bổ ngữ protected xác định tính bảo vệ của thuộc tính dữ liệu, cho phép truy cập từ tất cả các lớp trong gói cùng với lớp chứa thuộc tính và tất cả các lớp con, kể cả từ các gói khác.
3 Các thuộc tính mặc định
Các thuộc tính mặc định trong một lớp cho phép truy cập từ các lớp trong cùng gói, bao gồm cả lớp con của nó Điều này có nghĩa là phạm vi truy cập của các thành phần mặc định rộng hơn các thành phần private nhưng hẹp hơn các thành phần protected.
Các thuộc tính đi kèm với bổ ngữ private chỉ bị truy cập trong chính bản thân lớp chứa thuộc tính
Các thành phần static trong một lớp sẽ được chia sẻ chung cho tất cả các đối tượng, trong khi các thành phần không static sẽ có bản sao riêng cho từng đối tượng, với các giá trị khác nhau.
Ví dụ 4: Thuộc tính lopHoc = “K3A” trong lớp HocSinh
Thuộc tính final được xem như là hằng, giá trị của nó sẽ không thể thay đổi được ngay sau khi đã được khởi tạo
Như ở chương 3 chúng ta đã biết, Java có hai loại kiểu: nguyên thủy và tham chiếu
Đối với các biến final kiểu nguyên thủy, khi chúng được khởi tạo giá trị thì sẽ không thay đổi được,
Đối với các biến final kiểu tham chiếu, giá trị tham chiếu của chúng (đối tượng) không thể thay đổi, tuy nhiên, các thành phần hoặc trạng thái bên trong của đối tượng đó vẫn có thể được thay đổi.
Biến final, còn được gọi là biến “trắng”, không cần khởi tạo giá trị khi khai báo nhưng phải được gán giá trị trước khi sử dụng Trong khi đó, biến static không yêu cầu khởi tạo giá trị trước khi sử dụng vì nó tự động được gán giá trị mặc định.
Các hàm được khai báo là final trong một lớp có nội dung đầy đủ và không thể bị ghi đè trong các lớp con, điều này có nghĩa là chúng không thể thay đổi.
Các biến final static được sử dụng như các hằng (constant) trong chương trình, tương tự như từ khóa const trong ngôn ngữ C Thông thường, tên của các biến hằng được viết hoa và gán giá trị ngay khi khai báo.
Đối với các thành phần final, chương trình dịch có thể thực hiện được tối ưu hóa trong việc sinh mã
Lớp final trong lập trình không thể được mở rộng, nghĩa là các hàm thành phần của nó không thể bị thay đổi ở các lớp con Điều này cho thấy lớp final là loại lớp "đầy đủ", trong khi lớp abstract (sẽ được đề cập sau) là không đầy đủ Rõ ràng, một lớp không thể đồng thời là final và abstract.
Thư viện Java API bao gồm nhiều lớp final, trong đó có lớp java.lang.String, không cho phép tạo ra các lớp con từ nó.
Ví dụ 5: class SinhVien{ public String hoTen;
28 private int namSinh; protected String lopHoc; public static String tenTruong = "ĐH Hồng Đức";
- Thuộc tính "hoTen" có thể được truy cập đến từ tất cả các đối tượng khác (public)
- Thuộc tính "namSinh" chỉ có thể truy cập được từ các đối tượng có kiểu "SinhVien"(private)
GÓI (PACKAGES)
2.2.1 Tính đóng gói trong Java
Tính đóng gói trong Java là quá trình ẩn giấu các thuộc tính và phương thức của đối tượng, ngăn cản việc truy cập trực tiếp đến dữ liệu Thay vào đó, người dùng phải sử dụng các phương thức mà đối tượng cung cấp để truy cập và thao tác với dữ liệu của chính nó.
Trong Java, phạm vi truy cập (Access Modifier) là thành phần quan trọng của một lớp, phương thức hay thuộc tính Có bốn loại Access Modifier trong Java: default, private, public và protected, mỗi loại có những đặc điểm và quy định riêng.
Trong Java, nếu bạn không chỉ định phạm vi truy cập (Access Modifier), hệ thống sẽ hiểu rằng phạm vi truy cập mặc định là "default" Với phạm vi này, chỉ có thể truy cập từ trong cùng một lớp (class) và gói (package) mà thôi.
– private (riêng tư): Nếu như bạn khai báo phạm vi truy cập là private thì chỉ duy nhất trong lớp (class) đó mới có thể nhìn thấy được
Khi khai báo phạm vi truy cập là public, mọi thành phần như lớp, gói và lớp con đều có thể truy cập và nhìn thấy.
Nếu khai báo phạm vi truy cập là protected, lớp, gói và lớp con sẽ có khả năng truy cập lẫn nhau.
Trong Java, package là một tập hợp các lớp, interface và các package con có liên quan Các package được phân loại thành hai loại chính: package đã xây dựng sẵn và package do người dùng định nghĩa Một số package đã xây dựng sẵn phổ biến bao gồm java, lang, awt, javax, swing, net, io, util, sql Trong chương này, chúng ta sẽ khám phá cách tạo và sử dụng các package do người dùng tự định nghĩa.
Gói trong Java giúp ngăn chặn xung đột tên, kiểm soát quyền truy cập và cải thiện khả năng tìm kiếm, lưu trữ, cũng như sử dụng các lớp, interface, enumeration và annotation một cách hiệu quả hơn.
Một package là một tập hợp các kiểu liên quan, bao gồm lớp, interface, enumeration và annotation, nhằm cung cấp sự bảo vệ truy cập và quản lý tên hiệu quả.
Một vài package có sẵn trong Java như:
java.lang - Các lớp cơ bản
java.io - Các lớp input và output cơ bản
Lập trình viên có khả năng tạo ra gói riêng để nhóm lại các class và interface liên quan Việc này không chỉ giúp dễ dàng quản lý mà còn tăng cường khả năng xác định các thành phần như class, interface, enumeration và annotation có liên quan đến nhau.
Việc sử dụng gói (package) giúp tạo ra một không gian tên mới, tránh xung đột tên giữa các gói khác nhau Điều này không chỉ dễ dàng cung cấp khả năng truy cập mà còn thuận tiện để chứa các lớp (class) liên quan đến nhau.
2.2.3 Tạo một package trong Java
Khi tạo một package trong Java, bạn nên chọn tên cho package và đặt câu lệnh khai báo package ở trên cùng của source file
Lệnh package cần được đặt ở dòng đầu tiên của mã nguồn Bạn chỉ có thể khai báo lệnh package này một lần trong một tệp nguồn, và nó sẽ áp dụng cho tất cả các kiểu trong tệp đó.
Nếu một lệnh khai báo package không được sử dụng, kiểu class, interface, enumerations hoặc annotation sẽ được đặt vào package mặc định không có tên
Let's explore an example of creating a package called "animals." In programming practice, it's common to use lowercase names for packages to prevent conflicts with class and interface names Here, we will define an interface within the animals package.
/* Ten File : Animal.java */ package animals; interface Animal { public void eat(); public void travel();
Lợi thế của package trong Java
Java package được sử dụng để phân loại các lớp và các interface để mà chúng có thể được duy trì dễ dàng hơn
Java package cung cấp bảo vệ truy cập
Java package xóa bỏ các xung đột về đặt tên
Ví dụ khác về package trong Java
Từ khóa package được sử dụng để tạo một package trong Java
//Luu duoi dang Simple.java package mypack; public class Simple{ public static void main(String args[]){
System.out.println("Chao mung ban den voi package trong Java");
2.2.4 Cách biên dịch Java package
Nếu bạn không sử dụng bất cứ IDE nào, bạn cần theo cú pháp sau: javac -d thu_muc ten_javafile
Ví dụ: javac -d Simple.java
Tùy chọn -d xác định vị trí để lưu trữ class file đã tạo, cho phép bạn sử dụng bất kỳ tên thư mục nào như /home trên Linux hoặc d:/abc trên Windows Nếu bạn muốn giữ nguyên cấu trúc package trong cùng một thư mục, bạn có thể sử dụng dấu chấm (.).
Cách chạy chương trình Java package
Bạn cần sử dụng tên đầy đủ (ví dụ mypack.Simple) để chạy lớp đó
Để biên dịch: javac -d Simple.java
Để chạy: java mypack.Simple
–d là một switch mà nói cho trình biên dịch Compiler nơi để đặt class file (nó biểu diễn đích đến) Dấu chấm (.) biểu diễn folder hiện tại
2.2.5 Từ khóa import trong Java
Khi một lớp sử dụng lớp khác trong cùng một package, không cần phải chỉ định tên package Các lớp trong cùng package có thể nhận diện và tương tác với nhau mà không cần cú pháp đặc biệt nào.
Trong package payroll, một lớp Boss được bổ sung vào lớp Employee, cho phép lớp Boss tham chiếu đến lớp Employee mà không cần sử dụng tiền tố payroll Cấu trúc này được minh họa qua lớp Boss trong mã nguồn.
THỪA KẾ VÀ ĐA HÌNH
THỪA KẾ
Kế thừa trong Java là mối quan hệ giữa hai lớp, bao gồm lớp cha (superclass) và lớp con (subclass) Lớp con sẽ kế thừa tất cả các phương thức và thuộc tính từ lớp cha, nhưng chỉ có thể truy cập các thành viên public và protected Lớp con không được phép truy cập các thành viên private của lớp cha.
Kế thừa trong Java cho phép tạo ra một lớp mới dựa trên các lớp đã tồn tại, giúp tái sử dụng các phương thức và thuộc tính của lớp cha Ngoài ra, lập trình viên có thể khai báo thêm các phương thức và thuộc tính mới cho lớp con, nâng cao khả năng mở rộng và tổ chức mã nguồn.
3.1.2 Cài đặt quan hệ thừa kế
Dùng từ khóa extends để chỉ lớp dẫn xuất
extends {
// Các thuộc tính dữ liệu bổ sung // Các hàm thành phần bổ sung hay viết đè }
Tất cả các đối tượng thuộc lớp con đều là đối tượng của lớp cha, vì vậy việc gán một đối tượng lớp con cho biến tham chiếu của lớp cha được coi là sự mở rộng kiểu và không cần thực hiện ép kiểu.
Khi gán một đối tượng từ lớp cha cho biến tham chiếu của lớp con, việc ép kiểu là cần thiết và có thể dẫn đến tổn thất thông tin Ví dụ, trong cấu trúc lớp bao gồm SuperClass và SubClass, việc thực hiện ép kiểu cần được cân nhắc kỹ lưỡng để đảm bảo tính toàn vẹn của dữ liệu.
// public static main(String args[]){
SuperClass super1 = new SuperClass(); // Tạo ra đối tượng lớp cha
SubClass sub1 = super1; // Mở rộng kiểu
SuperClass super2 = (SubClass) sub1; // Thu hẹp kiểu nên phải ép kiểu
** Chú ý: Java cung cấp 3 tiền tố/từ khóa để hỗ trợ tính kế thừa của lớp:
- public: lớp có thể truy cập từ các gói, chương trình khác
- final: Lớp hằng, lớp không thể tạo dẫn xuất (không thể có con), hay đôi khi người ta gọi là lớp
Lớp trừu tượng không chứa khai báo về các thành phần và phương thức, trong khi lớp dẫn xuất sẽ đảm nhiệm việc khai báo và cài đặt cụ thể các thuộc tính và phương thức của lớp trừu tượng.
Tạo 3 class gồm các thuộc tính tương ứng:
Class "nhân sự" gồm: họ tên, năm sinh, quê quán
Class "học sinh" gồm: họ tên, năm sinh, quê quán, điểm trung bình
Class "giáo viên" gồm: họ tên, năm sinh, quê quán, lương hàng tháng
Việc không sử dụng tính kế thừa trong lập trình dẫn đến tình trạng lặp mã, ví dụ khi khai báo hai lớp "học sinh" và "giáo viên" sẽ phải định nghĩa lại các thuộc tính như họ tên, năm sinh, và quê quán Để khắc phục điều này, chúng ta có thể khai báo một lớp "nhân sự" và cho phép hai lớp còn lại kế thừa từ lớp này, giúp giảm thiểu sự trùng lặp và tối ưu hóa mã nguồn.
"nhân sự" , thiếu thuộc tính, phương thức nào thì sẽ bổ sung vào lớp đó
Trong bài viết này, chúng ta sẽ tạo các thuộc tính public cho lớp NhanSu, bao gồm hoTen, namSinh và queQuan, nhằm mục đích kế thừa và dễ hiểu hơn cho người đọc Dưới đây là mã nguồn cho lớp NhanSu trong package Test.
} class HocSinh extends NhanSu{ public float diemTb;
} public class Test { public static void main(String[] args) {
NhanSu ns = new NhanSu(); ns.hoTen = "Nhân Sự A"; ns.namSinh = 1990; ns.queQuan = "Hải Dương";
HocSinh hs = new HocSinh(); hs.hoTen = "Vũ Văn A";
// Đối tượng tạo ra từ lớp "học sinh" kế thừa, sử dụng các thuộc tính của lớp "nhân sự" hs.namSinh = 1992; hs.queQuan = "Hải Dương"; hs.diemTb = 9.5f;
GiaoVien gv = new GiaoVien(); gv.hoTen = "Nguyễn Văn B";
// Đối tượng tạo ra từ lớp "giáo viên" kế thừa, sử dụng các thuộc tính của lớ p "nhân sự" gv.namSinh = 1980; gv.queQuan = "Hà Nội"; gv.luong = 300000000;
} b, Tạo các thuộc tính để private, các phương thức cũng sẽ được kế thừa:
In the Test package, the NhanSu class defines private attributes including birth year (namSinh), hometown (queQuan), and full name (hoTen) It features a getter method, getHoTen(), which returns the value of the hoTen attribute.
} public void setHoTen(String hoTen) { this.hoTen = hoTen;
} public int getNamSinh() { return namSinh;
} public void setNamSinh(int namSinh) { this.namSinh = namSinh;
} public String getQueQuan() { return queQuan;
} public void setQueQuan(String queQuan) { this.queQuan = queQuan;
} class HocSinh extends NhanSu { private float diemTb; public float getDiemTb() { return diemTb;
} public void setDiemTb(float diemTb) { this.diemTb = diemTb;
47 public class Test { public static void main(String[] args) {
NhanSu ns = new NhanSu(); ns.setHoTen("Nhân Sự A"); ns.setNamSinh(1990); ns.setQueQuan("Hải Dương");
Đoạn mã trên tạo ra một đối tượng "Học Sinh" từ lớp kế thừa, cho phép sử dụng các thuộc tính và phương thức của lớp "Nhân Sự" Đối tượng này được thiết lập với tên "Vũ Văn A", năm sinh 1992, quê quán Hải Dương và điểm trung bình 9.5.
System.out.println("Thông tin học học sinh: ");
System.out.println("Họ tên: " + hs.getHoTen());
System.out.println("Năm sinh: " + hs.getNamSinh());
System.out.println("Quê quán: " + hs.getQueQuan());
System.out.println("Điểm trung bình: " + hs.getDiemTb());
Bài 1: Dùng tính kế thừa khai báo 3 class:
Class "Sinh Viên" gồm các thuộc tính: Họ tên, năm sinh, mã thẻ, tiền học phí còn nợ
Class "Giảng Viên" gồm các thuộc tính: Họ tên, năm sinh, mã thẻ, tiền lương hàng tháng
Class "Giám Đốc" gồm các thuộc tính: Họ tên, năm sinh, mã thẻ, tiền tiêu hàng tháng
Các thuộc tính để private, truy cập các thuộc tính sử dụng phương thức getter và setter
Tạo 1 đối tượng giảng viên, giá trị nhập vào từ bàn phím, in thông tin ra ngoài màn hình console 3.1.3 Các kiểu kế thừa trong Java
Có 3 kiểu kế thừa trong java đó là đơn kế thừa, kế thừa nhiều cấp, kế thừa thứ bậc
Đa kế thừa là khi một lớp (class) kế thừa từ nhiều lớp khác Trong Java, tính năng đa kế thừa chỉ được hỗ trợ thông qua interface, điều này được đề cập trong phần interface trong Java.
Chú ý: Đa kế thừa trong java không được support thông qua class
Ví dụ về đơn kế thừa
File: TestInheritance1.java class Animal { void eat() {
} class Dog extends Animal { void bark() {
} public class TestInheritance1 { public static void main(String args[]) {
Dog d = new Dog(); d.bark(); d.eat();
Ví dụ về kế thừa nhiều cấp
File: TestInheritance2.java class Animal { void eat() {
} class Dog extends Animal { void bark() {
} class BabyDog extends Dog { void weep() {
} public class TestInheritance2 { public static void main(String args[]) {
BabyDog d = new BabyDog(); d.weep(); d.bark(); d.eat();
Kết quả: weeping barking eating
Ví dụ về kế thừa thứ bậc
File: TestInheritance3.java class Animal { void eat() {
} class Dog extends Animal { void bark() {
} class Cat extends Animal { void meow() {
} public class TestInheritance3 { public static void main(String args[]) {
Cat c = new Cat(); c.meow(); c.eat();
Đa kế thừa không được hỗ trợ trong Java nhằm giảm thiểu sự phức tạp và đơn giản hóa ngôn ngữ lập trình Ví dụ, khi có ba lớp A, B và C, trong đó lớp C kế thừa từ cả A và B, nếu A và B có phương thức giống nhau, việc xác định phương thức nào được gọi từ đối tượng của lớp C sẽ trở nên khó khăn.
In Java, encountering a "compile time error" is preferable to a runtime error, as it indicates issues during the compilation process For instance, if you attempt to inherit from two classes simultaneously, the compiler will flag this as an error, preventing potential issues during program execution.
} public class C extends A,B { public static void main(String args[]) {
Từ khóa final trong Java đóng vai trò quan trọng trong việc giới hạn quyền truy cập và thay đổi của người dùng Nó có thể được áp dụng trong nhiều ngữ cảnh khác nhau, bao gồm biến, phương thức và lớp, giúp bảo vệ tính toàn vẹn của dữ liệu và cấu trúc chương trình.
Biến final trong lập trình có thể được áp dụng cho các biến, trong đó biến final không có giá trị được gọi là biến final trống hoặc biến final không được khởi tạo Biến này chỉ có thể được khởi tạo trong Constructor Ngoài ra, biến final trống cũng có thể là static và sẽ chỉ được khởi tạo trong khối static.
Nếu bạn tạo bất cứ biến nào là final, bạn không thể thay đổi giá trị của biến final (nó sẽ là hằng số)
Ví dụ của biến final trong Java
Trong lập trình, khi sử dụng biến final như speedlimit, giá trị của biến này không thể thay đổi sau khi đã được gán Ví dụ, trong lớp Bike9, biến speedlimit được khai báo là final, điều này có nghĩa là một khi giá trị được thiết lập, nó sẽ không bị thay đổi trong suốt quá trình thực thi của chương trình.
} public static void main(String args[]){
Bike9 obj=new Bike9(); obj.run();
Phương thức final trong Java
Nếu bạn tạo bất cứ phương thức nào là final, thì bạn không thể ghi đè nó
Ví dụ của phương thức final class Bike{ final void run(){System.out.println("running");}
} class Honda extends Bike{ void run(){System.out.println("Chay an toan voi 100kmph");} public static void main(String args[]){
Honda honda= new Honda(); honda.run();
ĐA HÌNH
Khi biến tham chiếu của lớp cha tham chiếu tới đối tượng của lớp con, thì đó là Upcasting
13 public class Upcasting { public static void main(String[] args) {
Animal animal1 = cat; // Chuyển kiểu không tường minh
Animal animal2 = (Animal) cat; // Chuyển kiểu tường minh cat.eat(); cat.meow(); animal1.eat(); animal2.eat();
// animal2.meow(); // Không thể gọi phương thức meow()
Kết quả thực thi chương trình trên:
Trong đoạn mã hàm main, chúng ta đã thực hiện upcasting bằng cách gán đối tượng cat thuộc lớp Cat cho các đối tượng animal1 và animal2 thuộc lớp Animal Việc upcasting cho phép sử dụng cả chuyển kiểu tường minh và không tường minh, và cả hai phương pháp này đều được chấp nhận.
Lưu ý rằng tất cả các phương thức của lớp Animal có thể được gọi thông qua đối tượng của lớp Cat, do mối quan hệ IS_A giữa chúng Tuy nhiên, nếu bạn ghi đè bất kỳ phương thức nào của lớp Animal trong lớp Cat, hàm được gọi trong quá trình runtime sẽ là hàm của lớp Cat.
Quay trở lại ví dụ phía trên Nếu trong lớp Cat, thực hiện override hàm eat như sau:
12 public class Cat extends Animal {
System.out.println("Eat meat");
Kết quả thực thi lại chương trình trên như sau:
Khác với upcasting, downcasting là quá trình chuyển đổi một đối tượng từ lớp cha thành một đối tượng của lớp con trong mối quan hệ kế thừa.
Thông thường, khi thực hiện dòng mã nguồn:
Trong lập trình hướng đối tượng, lớp Cat có thể gọi các phương thức đã được override từ lớp Animal thông qua đối tượng animal Tuy nhiên, khi cần truy cập tất cả các phương thức của lớp Cat thông qua đối tượng thuộc lớp Animal, chúng ta phải sử dụng kỹ thuật downcasting.
9 public class Downcating { public static void main(String[] args) {
Cat cat = (Cat) animal; // downcasting cat.meow();
Phương thức meow() chỉ có trong lớp Cat, nhưng chúng ta có thể gọi phương thức này thông qua đối tượng cat mà không cần khởi tạo mới lớp Cat Điều này có thể thực hiện được nhờ vào downcasting đối tượng animal kiểu Animal, mà không gặp phải vấn đề trong quá trình biên dịch và thực thi.
Khi thực hiện downcasting, có thể xảy ra lỗi ClassCastException nếu không thể thực hiện được Để đảm bảo an toàn, nên kiểm tra xem một đối tượng có phải là thể hiện của kiểu dữ liệu cụ thể trước khi tiến hành downcasting, có thể tham khảo toán tử instanceof trong Java Static Binding và Dynamic Binding là hai khái niệm quan trọng trong lập trình, giúp xác định cách mà phương thức được liên kết trong quá trình biên dịch hoặc thực thi.
Binding là quá trình kết nối một lời gọi phương thức với thân phương thức Có hai loại binding chính: Binding tĩnh (early binding) và Binding động (late binding).
Trước khi đi vào thảo luận về Binding, chúng ta cần làm rõ Type là gì:
1 Biến có một kiểu, nó có thể là kiểu gốc hoặc kiểu khác (không phải là kiểu gốc)
52 int data0; Ở đây, biến data là một kiểu int
2 Tham chiếu có một kiểu class Dog{ public static void main(String args[]){
Dog d1;//O day, d1 la kieu cua Dog
3 Đối tượng có một kiểu Đối tượng là một instance (sự thể hiện) của lớp Java cụ thể, nhưng nó cũng là một instance của lớp cha class Animal{} class Dog extends Animal{ public static void main(String args[]){
} Ở đây, d1 là một sự thể hiện của lớp Dog, nhưng nó cũng là một sự thể hiện của Animal
Gắn kết tĩnh (Static Binding) trong Java
Khi kiểu của đối tượng được xác định tại thời điểm biên dịch (compile time) bởi trình biên dịch, thì được gọi là gắn kết tĩnh (static binding) Nếu một lớp có bất kỳ phương thức nào là private, final hoặc static, thì cũng sẽ thuộc về gắn kết tĩnh Do đó, trong lập trình hướng đối tượng với gắn kết tĩnh, không thể xảy ra hiện tượng ghi đè (overloading).
Ví dụ về Static Binding class Dog{ private void eat(){System.out.println("dog dang an ");} public static void main(String args[]){
Gắn kết động (Dynamic Binding) trong Java
Khi kiểu của đối tượng được quyết định tại runtime thì đó là gắn kết động (Dynamic Binding)
Ví dụ về Dynamic Binding class Animal{ void eat(){System.out.println("animal dang an ");}
} class Dog extends Animal{ void eat(){System.out.println("dog dang an ");} public static void main(String args[]){
Trong ví dụ này, kiểu đối tượng không thể được xác định bởi Compiler do Dog là một sự thể hiện của Animal Do đó, Compiler chỉ nhận biết kiểu cơ sở mà không biết rõ kiểu cụ thể của đối tượng.
1, Khái niệm ghi đè – override:
- Đó là khi phương thức đã xuất hiện ở lớp cha và xuất hiện tiếp ở lớp con
- Khi đối tượng thuộc lớp con gọi phương thức thì sẽ chọn lựa và chạy theo phương thức trong lớp con
- Nếu lớp con không có phương thức đó thì mới lên kiếm ở lớp cha để chạy
- Phương thức ghi đè có cùng tên, cùng tham số truyền vào, cùng kiểu giá trị trả về với phương thức ở lớp cha!
- Ghi đè là hình thức đa hình (polymorphism) trong quá trình thực thi (Runtime)
Ghi đè (overriding) là quá trình mà lớp con kế thừa từ lớp cha và điều chỉnh một phương thức cụ thể để đáp ứng nhu cầu của mình.
Ví dụ 1: Sự khác nhau giữa Override và Overload
- Lớp "tên người Việt" gồm 2 thuộc tính: "họ" và "tên" và phương thức hiển thị tên đầy đủ theo thứ tự họ + tên
- Lớp "tên người nước ngoài" kế thừa từ lớp "tên người Việt" , phương thức hiển thị tên đầy đủ lại theo thứ tự tên + họ
- Như vậy, sau khi kế thừa ta chỉ cần ghi đè lại phương thức hiển thị là xong!
Code: package Test; class tenNguoiViet { public String ten, ho; public void show() {
System.out.println("Tên đầy đủ là: " + this.ho + " "+this.ten);
System.out.println("Full name: " + this.ten + " " + this.ho);
In the Java program, a class named `Test` is defined with a main method that creates instances of two classes: `tenNguoiViet` and `tenNguoiNuocNgoai` Both instances are assigned the surname "Nguyen" and the first name "Hung." The program then calls the `show()` method for each instance to display the information.
- Lớp HocSinh gồm các thuộc tính: hoTen, lop, toan, ly, hoa và phương thức điểm trung bình là trung bình cộng 3 môn
Lớp HocSinhChuyenToan áp dụng phương thức tính điểm trung bình từ lớp HocSinh, nhưng với điều chỉnh đặc biệt là nhân đôi hệ số môn Toán Cách tính điểm được thực hiện bằng cách cộng tất cả các điểm, sau đó chia cho 4 Do đó, cần ghi đè phương thức tính điểm trung bình trong lớp HocSinhChuyenToan để phản ánh đúng quy định này.
54 class HocSinh { private String hoTen; private String lop; private float toan, ly, hoa; public String getHoTen() { return hoTen;
} public void setHoTen(String hoTen) { this.hoTen = hoTen;
} public String getLop() { return lop;
} public void setLop(String lop) { this.lop = lop;
} public float getToan() { return toan;
} public void setToan(float toan) { this.toan = toan;
} public float getLy() { return ly;
} public void setLy(float ly) { this.ly = ly;
} public float getHoa() { return hoa;
} public void setHoa(float hoa) { this.hoa = hoa;
} public float diemTrungBinh() { return (float) (this.toan + this.ly + this.hoa) / 3;
@Override public float diemTrungBinh() { return (float) (this.getHoa() + this.getLy() + this.getToan() * 2) / 4;
} public class Test { public static void main(String[] args) {
HocSinh a = new HocSinh(); a.setHoTen("Vu Van Tuong"); a.setLop("CNTT K20B"); a.setToan(10.0f); a.setLy(9.0f); a.setHoa(8.0f);
System.out.println("Diem trung binh cua hoc sinh a la: " + a.diemTrungBinh());
HocSinhChuyenToan b = new HocSinhChuyenToan(); b.setHoTen("Nguyen Van B"); b.setToan(9.0f); b.setHoa(7.0f); b.setLy(8.0f);
System.out.println("Diem trung binh cua hoc sinh chuyen Toan b la: " + b.diemT rungBinh());
Class USB có các thuộc tính bao gồm mã hàng, giá và số lượng Phương thức tính tổng tiền được xác định bằng công thức giá nhân với số lượng Ngoài ra, class này còn có phương thức hiển thị toàn bộ thông tin của đơn hàng mua USB.
Class Mouse kế thừa từ class USB và ghi đè hai phương thức: tính tổng tiền với mức giảm giá 20% cho chuột, cũng như hiển thị toàn bộ thông tin đơn hàng khi khách hàng mua chuột máy tính.
- Tạo mỗi lớp 1 đối tượng, nhập thông tin từ bàn phím, in thông tin đơn hàng ra màn hình!
- Code hướng đối tượng, các thuộc tính để private!
Khi yêu cầu 3 con vật cùng 1 yêu cầu "speak", 3 con vật trả lại 3 kết quả khác nhau!
1, Khái niệm nạp chồng phương thức overloading method:
INTERFACE VÀ INNER CLASS
Trong Java, mỗi lớp chỉ có một siêu lớp trực tiếp, nghĩa là chỉ cho phép đơn thừa kế để giảm thiểu sự phức tạp của đa thừa kế Để khắc phục điều này, Java sử dụng interface, cho phép một lớp kế thừa từ nhiều interface khác nhau Nhờ đó, lớp có thể tiếp nhận thêm vùng dữ liệu và phương thức từ các interface, tạo ra sự linh hoạt trong lập trình hướng đối tượng.
* Định nghĩa interface interface định nghĩa mẫu giao diện thông qua việc phác thảo các hàm mẫu (prototype) và không cài đặt nội dung thực hiện interface {
thường chứa danh sách các hàm mẫu và các hằng
Giao diện (interface) là một kiểu lớp đặc biệt với tất cả các hàm thành phần đều là trừu tượng, không thể khởi tạo giá trị hay tạo đối tượng từ giao diện Các hàm trong giao diện sẽ được cài đặt trong các lớp xây dựng mới theo cú pháp: class implements [extends ].
// Bổ sung các thành phần;
// Cài đặt các hàm mẫu đã cho trong ;
Các hàm mẫu của interface được mặc định là abstract và không được khai báo static;
Một lớp có thể chọn một số hàm mẫu để cài đặt, nghĩa là có thể chỉ cài đặt một phần của giao diện interface;
Một giao diện có thể kế thừa từ nhiều hơn một giao diện;
Một giao diện có thể kế thừa từ nhiều hơn một giao diện và lớp;
Ví dụ: Code ví dụ của video bên blog StudyAndShare public class DemoJavaBasic { public static void main(String[] args) {
Manager mana = new Manager(); mana.show();
} interface Human{ public static final int AVG_AGE = 100; int AVG_WEIGHT = 60; public abstract void show(); void study();
System.out.println("Tuoi trung bình: "+ AVG_AGE);
// TODO Auto-generated method stub
Trong lập trình, một giao diện (interface) có thể kế thừa từ nhiều giao diện cha khác nhau, không chỉ giới hạn ở một giao diện cha trực tiếp Điều này cho phép giao diện con kế thừa tất cả các giá trị hằng và phương thức từ các giao diện cha Các giao diện cha được liệt kê theo chuỗi, cách nhau bằng dấu phẩy Cách khai báo giao diện đa kế thừa như sau: public interface InterfaceName extends interface1, interface2, interface3 {
Ví dụ: interface DienTich { public abstract void sHinhVuong(float a); public abstract void sHinhChuNhat(float a, float b);
} interface ChuVi { public abstract void cVHinhVuong(float a); public abstract void cVHinhChuNhat(float a, float b);
} interface CongThuc extends DienTich, ChuVi {
//Hoặc viết là class CongThucTinh implements DienTich,ChuVi cũng được!
@Override public void sHinhVuong(float a) {
System.out.println("Diện tích hình vuông là: " + a * a);
@Override public void sHinhChuNhat(float a, float b) {
System.out.println("Diện tích hình chữ nhật là: " + a * b);
@Override public void cVHinhVuong(float a) {
System.out.println("Chu vi hình vuông là: " + 4 * a);
@Override public void cVHinhChuNhat(float a, float b) {
System.out.println("Chu vi hình chữ nhật là: " + 2 * (a + b));
} public class Test { public static void main(String[] args) {
CongThucTinh x = new CongThucTinh(); x.sHinhVuong(5.0f); x.cVHinhVuong(5.0f); x.sHinhChuNhat(5.5f, 10.0f); x.cVHinhChuNhat(5.5f, 10.0f);
Inner class là một lớp nằm trong một lớp khác, được xem như thuộc tính của lớp chứa nó Điều này có nghĩa là bạn không thể khởi tạo đối tượng của lớp B nếu chưa khởi tạo đối tượng của lớp A.
Bởi class B nằm trong class A nên nó có thể truy cập tất cả các thuộc tính hay phương thức của class A class A { private int x = 8; class B { public void printInt() {
} Để sử dụng method vừa tạo, ta có thể viết: obj2.printInt();
Ví dụ 1: Ví dụ bên Blog StudyAndShare public class DemoTest { public static void main(String[] args) {
Outer out = new Outer(); out.show();
} class Outer { public void show() {
Inner in = new Inner(); in.display();
} class Inner { public void display() {
System.out.println("Đây là inner class.");
Outer.Inner in = new Outer().new Inner(); in.display();
Ví dụ 2: package test; class ThoiGian { public int ngay, thang, nam; class Time { public int gio, phut, giay; public void showTime() {
System.out.println("Ngày " + ngay + "/" + thang + "/" + nam);
System.out.println("Time: "+this.gio+": "+ this.phut + ": " + this.giay); }
} public class Test { public static void main(String[] args) {
ThoiGian.Time time = tg.new Time(); tg.ngay = 20; tg.thang = 7; tg.nam = 1996; time.gio = 20; time.phut = 22; time.giay = 01; time.showTime();
LỚP TRỪU TƯỢNG
Lớp trừu tượng không thể được khởi tạo bằng toán tử new, và phương thức abstract trong lớp này chỉ được khai báo mà không có phần thực thi Các phương thức này sẽ được ghi đè trong các lớp con kế thừa Nếu một lớp chứa phương thức abstract, thì lớp đó cũng phải được định nghĩa là abstract.
- Lớp trừu tượng là lớp không có khai báo các thuộc tính thành phần và các phương thức
Bất kỳ lớp nào kế thừa từ một lớp trừu tượng đều phải định nghĩa lại các phương thức trừu tượng của lớp đó, hoặc nếu không định nghĩa lại, thì phải ghi chú các phương thức trừu tượng đó.
- Abstract class là class có chứa các abstract methods
- Các methods trong abstract class phải khai báo với từ khóa abstract (không giống như interface vì interface mặc định gán abstract cho các methods)
- Một abstract class có thể chứa cả abstract methods và các methods thường
- Khi một class có chứa abstract mothod thì bắt buộc phải có từ khóa abstract đằng trước tên class đó abstract class A{ abstract void method_1();
// cài đặt chi tiết cho phương thức method_1
// cài đặt chi tiết cho phương thức method_1
Các phương thức được khai báo với tiền tố private và static không thể được định nghĩa là trừu tượng (abstract) Tiền tố private ngăn cản việc truy xuất từ các lớp dẫn xuất, trong khi tiền tố static chỉ có thể được gọi qua lớp chứa nó.
62 dùng riêng cho lớp khai báo mà thôi
* Phương thức trong abstract class có 2 cách để khai báo:
- Khai báo bình thường như class
- Khai báo giống như interface nhưng phải có thêm từ khóa abstract
Code demo: package java.demo.test; abstract class nhanSu { abstract void show();
} class hocSinh extends nhanSu { public String hoTen; public int namSinh; public void show() {
System.out.println("Hello " + hoTen + " Năm sinh: " + namSinh);
} public class Test { public static void main(String[] args) { hocSinh a = new hocSinh(); a.hoTen = "Vũ Văn A"; a.namSinh = 1992; a.show();
3.4.3 So sánh abstract class và interface
Mấy đoạn này là chúng ta sưu tầm trên mạng nhé:
- Một class chỉ có thể kế thừa từ một abstract class, nhưng có thể kế thừa nhiều interface
- Trong Interface chỉ có thể khai báo các fields, methods, mà không được hiện thực nó Còn đối với abstract thì dùng các biến, hiện thực các methods
In an interface, all fields and methods are public and must be implemented by inheriting classes, making them abstract In contrast, an abstract class can contain fields and methods that are private, internal, public, or protected, and these can be either abstract or non-abstract.
Interface được sử dụng để tập hợp các hành động và khả năng cần hiện thực hóa của một đối tượng, trong khi abstract class phục vụ cho các lớp kế thừa cùng loại với các tính chất hoặc trạng thái tương đồng.
- Abstract class có tốc độ thực thi nhanh hơn interface
- Thêm 1 tính năng mới vào interface sẽ phá vỡ toàn bộ các lớp hiện thực, còn abstract thì không
- Ví dụ về interface, các thành viên của interface phải được thực thi trong các lớp mà kế thừa từ
- Nhìn một cách nào đó, bạn có thể thấy rằng, Interface giống như những câu lạc bộ, và Abstract class là một ông bố trong gia đình
Mỗi câu lạc bộ đều có bộ quy tắc và nội quy mà tất cả thành viên phải tuân thủ Khác với một ông bố trong gia đình, người có tài sản và quy định trong di chúc về việc ai sẽ kế thừa tài sản của mình Bạn có thể tham gia nhiều câu lạc bộ cùng lúc, tương tự như việc một lớp "kế thừa" (Implement) nhiều Interface trong lập trình.
Khi tham gia vào một câu lạc bộ, bạn cần tuân thủ tất cả các quy tắc của câu lạc bộ đó, bất kể bạn là thành viên của bao nhiêu câu lạc bộ khác Điều này nhấn mạnh tầm quan trọng của việc thực hiện đầy đủ các giao diện thành viên.
CLB không để lại tài sản cho bạn, nhưng cha bạn có thể làm điều đó, miễn là ông ấy đồng ý Ông cũng yêu cầu bạn tuân thủ các quy tắc gia đình Khi đã là con của một người, bạn không thể trở thành con của người khác, giống như không thể kế thừa từ nhiều lớp trừu tượng.
Lớp "vô sinh" là lớp không thể có lớp dẫn xuất từ nó, tức là không có lớp con nào kế thừa từ lớp này Mục đích của lớp "vô sinh" là hạn chế và ngăn chặn việc tạo ra các lớp khác dẫn xuất từ nó Để khai báo một lớp là lớp "vô sinh", chúng ta sử dụng từ khóa "final class".
Tất cả các phương thức của lớp vô sinh đều vô sinh, nhưng các thuộc tính của lớp vô sinh thì có thể không vô sinh
- Xem thêm video blog StudyAndShare
Ví dụ: public final class A { public final int x; private int y; public final void method_1(){
MỘT SỐ LỚP CƠ BẢN TRONG JAVA
3.6.1 Các lớp Wapper (Integer, Double, Float, ) và Array
Các lớp xử lý như Byte, Integer, Float, và Double cung cấp các phương thức tiện ích để làm việc với byte, số nguyên và số thực Những phương thức này bao gồm compareTo, doubleValue, floatValue, longValue, notify và toString, giúp xử lý và chuyển đổi dữ liệu một cách hiệu quả.
System.out.println(d1 + " not equal " + d2);
Kết quả sẽ in ra màn hình: 1.0 not equal 1.0002
Array (java.lang.reflect.Array) là lớp chứa các phương thức static giúp tạo và quản lý mảng dễ dàng hơn Một số phương thức trong lớp Array như:
• get lấy một đối tượng từ mảng
• getLength trả về kích thước mảng
• getInt lấy về một số nguyên trong mảng các số nguyên
• getBoolean lấy về giá trị boolean trong mảng các giá trị boolean
• set đưa một đối tượng vào mảng
• setInt đưa một số nguyên vào mảng
String iObject = Array.get(a, 1).toString();
System.out.println("Length:" + Array.getLength(a) + " objecAt1:" + iObject); //In ra Length:2 objecAt1:Anh boolean[] b = { true, false, true }; boolean bObject = Array.getBoolean(b, 2);
System.out.println(bObject); //In ra true
3.6.2 Các lớp Collection (Collection, Set, List, Map, ArrayList, Vector, Hashtable, Hashset, HashMap)
Các lớp về collection được đặt trong gói java.util Đây là mối quan hệ giữa các lớp collection:
Hash table Resizable Array Balanced Tree LinkedList
Interfaces Collection HashSet ArrayList TreeSet LinkedList
List ArrayList LinkedList Vector, Stack
Map HashMap TreeMap HashTable, Properties
Chúng ta sẽ đi vào chi tiết từng lớp:
Collection là một interface với các phương thức phổ biến nhất để gom nhóm các đối tượng như:
• add thêm phần tử vào mảng
• isEmpty kiểm tra mảng có rỗng không
• remove xóa phần tử khỏi mảng
• size lấy kích thước mảng
• clear xóa tẩt cả các phần tử trong mảng
• toArray trả về mảng các đối tượng
A List is an interface designed to hold a continuous sequence of elements It inherits from the Collection interface, which means it includes all the methods from Collection, along with additional specific methods unique to List.
• get lấy phần tử ra khỏi mảng
• indexOf tìm phần tử trong mảng
• set thay thế phần tử trong mảng
ArrayList là một lớp kế thừa từ AbstractList và implement List interface và một vài interface khác
ArrayList a = new ArrayList(); a.add("Kien"); a.add(0, "Anh"); for(String s : a) {
System.out.print(s + "\t"); //In ra Anh Kien
Set, SortedSet, HashSet và TreeSet là những lớp trong Java xử lý collection, khác với List, các phần tử trong Set là duy nhất.
Set set = new HashSet(); set.add("Bernadine"); set.add("Elizabeth"); set.add("Gene"); set.add("Elizabeth"); set.add("Clara");
Chúng ta sẽ nhận được chuỗi in ra là: [Bernadine, Gene, Elizabeth, Clara]
The Set interface includes SortedSet, while HashSet and TreeSet are classes that implement the Set interface and inherit from AbstractSet TreeSet is a sorted collection, meaning its elements are automatically arranged in the correct order upon insertion, whereas HashSet does not maintain any specific order.
Set set = new TreeSet(); set.add("Bernadine"); set.add("Elizabeth"); set.add("Gene"); set.add("Elizabeth"); set.add("Clara");
Ta sẽ nhận được kết quả: [Bernadine, Clara, Elizabeth, Gene]
Map, SortedMap, HashMap, and TreeMap are classes used for managing collections in a key-value format While Map and SortedMap are interfaces that do not inherit from the Collection interface, HashMap and TreeMap are concrete classes that implement the Map interface and extend AbstractMap.
Map map = new HashMap(); map.put(3, "Kien"); map.put(1, "Nguyen"); map.put(2, "Anh");
Map sortedMap = new TreeMap(map);
Kết quả in ra màn hình sẽ là:
Chúng ta có một vài lớp ra đời trước như như Vector, HashTable:
Vector là một lớp tương tự như ArrayList nhưng có sự khác biệt lớn ở chỗ Vector được đồng bộ (Synchronized), điều này khiến nó trở thành lựa chọn phổ biến trong các ứng dụng đa luồng (multithreading) Tuy nhiên, do tính đồng bộ, tốc độ xử lý của Vector thường chậm hơn so với ArrayList.
HashTable và HashMap đều là các lớp lưu trữ dữ liệu theo kiểu ánh xạ, nhưng HashTable là đồng bộ, phù hợp cho ứng dụng đa luồng, trong khi HashMap không đồng bộ Mặc dù HashMap có thể được bổ sung tính năng đồng bộ, nhưng mặc định nó không có Ra đời sau HashTable, HashMap có thiết kế hiện đại và được ưa chuộng hơn trong thực tế.
Hashtable map = new Hashtable(); map.put(3, "Kien"); map.put(1, "Nguyen"); map.put(2, "Anh");
Kết quả in ra sẽ là: {3=Kien, 2=Anh, 1=Nguyen}
3.6.3 Nhóm lớp về String (String, StringBuffer, StringBuilder)
Trong Java, String là một đối tượng được sử dụng để xử lý chuỗi ký tự Giá trị của đối tượng String không thể thay đổi sau khi đã được khởi tạo.
String str = "Luvina"; str += "Company";
Trong ví dụ trên, chuỗi "LuvinaCompany" được hiển thị trên màn hình, nhưng đối tượng str không thay đổi Thay vào đó, một đối tượng mới với giá trị chuỗi "LuvinaCompany" được tạo ra và sau đó gán cho đối tượng str.
Một số phương thức thông dụng trong lớp String như:
• length lấy chiều dài hiện tại của chuỗi
• substring lấy ra một chuỗi trong chuỗi hiện hành
• compareTo so sánh 2 chuỗi có xét ký tự hoa, thường
• compareToIgnoreCase so sánh 2 chuỗi bỏ qua ký tự hoa thường
System.out.println("Length: " + str.length() + " substring(1,2): " + str.substring(1, 2));
Ta nhận được kết quả trên màn hình như sau: Length: 6 substring(1,2): u
StringBuffer và StringBuilder là hai đối tượng trong Java dùng để xử lý chuỗi, khác với String ở chỗ giá trị của chúng có thể thay đổi mà không cần tạo đối tượng mới trong bộ nhớ Khi thay đổi giá trị của String, JVM thực chất sử dụng StringBuffer và StringBuilder để xử lý và tạo ra các đối tượng String mới.
StringBuffer str = new StringBuffer("Luvina"); str.append("Company");
System.out.println(str); //In ra LuvinaCompany
Từ đặc điểm trên ta có thể thấy khi xử lý chuỗi mà giá trị sẽ hay thay đổi (cộng chuỗi, cắt chuỗi,
…) thì nên dùng StringBuffer hoặc StringBuilder sẽ cho tốc độ nhanh hơn so với sử dụng String
Một số phương thức thông dụng trong StringBuffer và StringBuilder:
• append thêm một chuỗi vào cuối chuỗi đang có
• insert chèn một chuỗi vào chuỗi đang có
• replace thay thế một chuỗi
• delete xóa một chuỗi trong chuỗi hiện hành
• reverse đảo ngược một chuỗi
StringBuffer str = new StringBuffer("Luvina");
System.out.println("Length: " + str.length()
+ " str.append(\" Company\"): " + str.append(" Company")
Ta thu được kết quả trên màn hình từ đoạn code trên như sau:
Length: 6 substring(1,2): u str.append(" Company"): Luvina Company str.lastIndexOf("n"): 12 str.reverse: ynapmoC anivuL
3.6.4 Các lớp tiện ích khác (StringTokenizer, Date, Calendar, SimpleDateFormat, Arrays)
StringTokenizer (java.util.StringTokenizer) là một lớp tiện ích giúp tách các chuỗi (string) được ngăn cách bởi các ký tự
StringTokenizer st = new StringTokenizer(s, ":"); while(st.hasMoreTokens()) {
System.out.println(key + ":" + val);//In subject:Demo StringTokenizer
Date (java.util.Date) là một lớp tiện ích xử lý về ngày tháng
System.out.println(dObject.toString()); //In Sat Jan 22 00:00:00 ICT 3910
System.out.println(dObject.getDate()); //In 22
Calendar là một lớp trừu tượng giúp hỗ trợ trong việc biến đổi thông tin ngày tháng
Calendar cal = Calendar.getInstance(); cal.add(Calendar.DATE, 0);
System.out.println(dObject.toString()); //In Tue Dec 22 17:28:46 ICT 2009
SimpleDateFormat (java.text SimpleDateFormat) là một lớp tiện ích giúp cho việc định dạng và trích thông tin thời gian
SimpleDateFormat sdf; sdf = new SimpleDateFormat("hh:mm:ss");
System.out.println(sdf.format(date)); //In 05:35:38 sdf = new SimpleDateFormat("dd MMM yyyy hh:mm:ss zzz");
System.out.println(sdf.format(date)); //In 22 Dec 2009 05:35:38 ICT
Lớp java.util.Arrays cung cấp các phương thức static giúp việc thao tác với mảng trở nên đơn giản hơn, bao gồm các chức năng như sắp xếp, tìm kiếm và thay thế Đây là một bổ sung hữu ích cho lớp Array, với nhiều phương thức hỗ trợ cho việc quản lý mảng hiệu quả.
• binarySearch tìm kiếm phần tử trong mảng dùng thuật toán tìm kiếm nhị phân
• copyOf sao chép một mảng với chiều dài
• copyOfRange sao chép một mảng với một vùng từ vị trí đầu đến vị trí cuối
String[] b = Arrays.copyOf(a, 2); int indexOfAnh = Arrays.binarySearch(a, "Anh");
System.out.println(indexOfAnh); //In ra 1 because find Anh before sorting a array
System.out.println(Arrays.toString(a)); //In ra [Anh, Kien, Nguyen]
System.out.println(Arrays.toString(b)); //In ra [Nguyen, Anh]
Biểu thức chính quy, hay còn gọi là Regular Expression, là công cụ giúp chúng ta xác định và kiểm tra xem một chuỗi có tuân thủ định dạng mà chúng ta mong muốn hay không.
Trong java hiện nay có 2 lớp là Pattern và Matcher (java.util.regex) giúp ta xử lý các vấn đến liên quan đến regular expression
Pattern là lớp dùng để biên dịch biểu thức, và kết quả của quá trình biên dịch này tạo ra một đối tượng thuộc lớp Matcher Đối tượng Matcher được sử dụng để kiểm tra xem chuỗi ký tự có khớp với biểu thức đã biên dịch hay không.
Một vài phương thức của lớp Pattern như:
• compile biên dịch biểu thức trong pattern
• matcher tạo ra đối tượng thuộc lớp Matcher từ pattern
• matches kiểm tra chuỗi có đúng với biểu thức trong pattern
Matcher là lớp được tạo ra từ Pattern và dùng để thực hiện những việc như match, tìm kiếm
Một vài phương thức trong lớp Matcher như:
• find tìm kiếm chuỗi mà match với mẫu biểu thức
• matches kiểm tra chuỗi có match với mẫu biểu thức
Một vài pattern đơn giản:
• [^abc] bất kỳ ký tự nào trừ a, b, c
• [a-z0-9] các ký tự a đến z và 0 đến 9
Ví dụ kiểm tra một chuỗi với các ký tự bắt buộc:
Pattern pat = Pattern.compile("Nguyen.*Kien");
Matcher matcher = pat.matcher("Nguyen Anh Kien"); boolean flag = matcher.matches();
System.out.println(flag); //In ra true
Ví dụ bạn cần kiểm tra điện thoại đúng định dạng XXX-XXXX-XXXX, mỗi khối X có ít nhất 1 ký tự và tối đa 3-4-4 ký tự:
String phoneNumberPattern = "^[0-9]{1,3}-[0-9]{1,4}-[0-9]{1,4}$"; boolean isMatch = Pattern.matches(phoneNumberPattern, phone);
System.out.println(isMatch); //In ra true
Ví dụ về kiểm tra địa chỉ email
String email = "nakien2a@yahoo.com";
String emailPattern = "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-z]{2,7}$"; boolean isMatch = Pattern.matches(emailPattern, email);
System.out.println(isMatch); //In ra true