Chào mừng các bạn đến với bài viết về Lập trình hướng đối tượng (OOP) trong Java! Trong phần đầu tiên này, chúng ta sẽ cùng nhau khám phá những khái niệm cơ bản nhất, đặt nền móng cho việc xây dựng các ứng dụng Java mạnh mẽ và dễ bảo trì.
Lập trình hướng đối tượng (OOP) là gì?
Lập trình hướng đối tượng (OOP) là một mô hình lập trình tổ chức mã thành các "đối tượng" (objects). Mỗi đối tượng đại diện cho một thực thể trong thế giới thực, chứa đựng cả dữ liệu (attributes - thuộc tính) và hành vi (methods - phương thức). Ví dụ, một đối tượng ÔTô
có thể có thuộc tính màu sắc, tốc độ và hành vi khởi động, tăng tốc.
Class và Object: Khuôn mẫu và Thực thể
Class (Lớp) là một bản thiết kế hoặc khuôn mẫu định nghĩa dữ liệu và hành vi chung cho tất cả các đối tượng cùng loại. Nó giống như bản vẽ kỹ thuật của một ngôi nhà.
Object (Đối tượng) là một thể hiện (instance) cụ thể của một class, chứa các giá trị dữ liệu độc đáo. Từ bản vẽ ngôi nhà, bạn có thể xây dựng nhiều ngôi nhà thực tế, mỗi ngôi nhà là một đối tượng.
Ví dụ:
// Định nghĩa Class OTo
class OTo { // Thuộc tính (Attributes) String mauSac; String hangSanXuat; int tocDoHienTai; // Phương thức (Methods) void khoiDong() { System.out.println("Xe đang khởi động..."); } void tangToc(int luongTocDo) { tocDoHienTai += luongTocDo; System.out.println("Tốc độ hiện tại: " + tocDoHienTai + " km/h"); }
} public class ViDuClassVaObject { public static void main(String[] args) {https://viblo.asia/posts/y37Ldd2NLov/settings/seo // Tạo đối tượng (Object) của Class OTo OTo xeCuaToi = new OTo(); // Sử dụng từ khóa 'new' để tạo đối tượng // Truy cập và gán giá trị cho thuộc tính xeCuaToi.mauSac = "Đỏ"; xeCuaToi.hangSanXuat = "Toyota"; xeCuaToi.tocDoHienTai = 0; System.out.println("Xe của tôi màu: " + xeCuaToi.mauSac); System.out.println("Hãng sản xuất: " + xeCuaToi.hangSanXuat); // Gọi phương thức xeCuaToi.khoiDong(); xeCuaToi.tangToc(50); xeCuaToi.tangToc(20); }
}
Vòng đời của một đối tượng trong Java
Vòng đời của một đối tượng trong Java trải qua ba giai đoạn chính:
- Creation (Tạo lập): Đối tượng được tạo ra trong bộ nhớ heap bằng cách sử dụng từ khóa new.
- Accessing (Truy cập): Đối tượng được truy cập thông qua các biến tham chiếu (reference variables).
- Cleanup (Dọn dẹp): Khi không còn tham chiếu nào đến đối tượng, Java’s Garbage Collector (bộ thu gom rác) sẽ tự động giải phóng bộ nhớ mà đối tượng đó chiếm giữ.
Keywords (Từ khóa)
Keywords là những từ dành riêng (reserved words) trong Java, chúng có ý nghĩa đặc biệt đối với trình biên dịch và không thể được sử dụng làm tên cho các biến, phương thức, hoặc class của bạn.
Ví dụ: class
, public
, static
, void
, new
, package
, v.v.
Comments (Chú thích)
Comments là các chú thích trong mã, được trình biên dịch bỏ qua. Chúng được sử dụng để mô tả hoặc giải thích mã, giúp người đọc hiểu rõ hơn về logic và mục đích của từng phần code. Java hỗ trợ ba loại comment:
- Single-line comments: Bắt đầu bằng
//
. Ví dụ:// Đây là một comment một dòng
- Multi-line comments: Bắt đầu bằng
/*
và kết thúc bằng*/
. Ví dụ:
/* * Đây là một comment * nhiều dòng */
- Documentation comments: Bắt đầu bằng /** và kết thúc bằng */. Được sử dụng để tạo tài liệu Javadoc. Ví dụ:
/** * Phương thức này dùng để tính tổng hai số nguyên. * @param a Số thứ nhất * @param b Số thứ hai * @return Tổng của a và b */
public int tong(int a, int b) { return a + b;
}
Packages (Gói)
Packages là cơ chế để tổ chức các class, interface và sub-package liên quan vào một đơn vị duy nhất. Chúng giúp tránh xung đột tên (name collision) và cung cấp một mức độ kiểm soát truy cập. Từ khóa package được sử dụng để tạo một package
.
Ví dụ:
// Trong file src/com/example/model/NhanVien.java
package com.example.model; public class NhanVien { String ten; // ...
} // Trong file src/com/example/app/UngDung.java
package com.example.app; import com.example.model.NhanVien; // Nhập class NhanVien từ package khác public class UngDung { public static void main(String[] args) { NhanVien nv = new NhanVien(); nv.ten = "Nguyen Van A"; }
}
Access Modifiers (Bộ điều chỉnh truy cập)
Access modifiers kiểm soát khả năng hiển thị và truy cập của các class, phương thức và biến từ các phần khác của ứng dụng Java. Có bốn loại chính:
public
: Có thể truy cập từ mọi nơi.protected
: Có thể truy cập trong cùng package và bởi các lớp con (subclasses), ngay cả khi chúng ở các package khác.default
(package-private): Chỉ có thể truy cập trong cùng package. (Khi không có access modifier nào được chỉ định).private
: Chỉ có thể truy cập trong class mà nó được khai báo.
Khai báo Class và Fields (Trường)
Một class được khai báo bằng từ khóa class theo sau là tên class
. Nó có thể tùy chọn mở rộng một lớp cha (superclass) bằng extends
và triển khai các interface bằng implements
.
Fields là các biến được khai báo ở cấp độ class để lưu trữ trạng thái của một đối tượng. Chúng có thể có access modifiers, specifiers (static
, final
), một kiểu dữ liệu, và một giá trị khởi tạo tùy chọn.
Ví dụ:
public class SinhVien { // class được khai báo public private String maSinhVien; // field private public String hoTen; // field public protected int tuoi; // field protected static String truongHoc = "Đại học ABC"; // field static public SinhVien(String ma, String ten, int age) { this.maSinhVien = ma; this.hoTen = ten; this.tuoi = age; } public void hienThiThongTin() { System.out.println("MSSV: " + maSinhVien); System.out.println("Họ tên: " + hoTen); System.out.println("Tuổi: " + tuoi); System.out.println("Trường: " + truongHoc); }
}
Methods (Phương thức)
Methods là các khối mã thực hiện các tác vụ cụ thể và tùy chọn trả về giá trị. Chúng được khai báo với một access modifier tùy chọn, specifiers, kiểu trả về, tên, các tham số và thân phương thức.
Ví dụ:
public class MayTinh { public int cong(int a, int b) { // Phương thức public, trả về int, nhận 2 tham số int return a + b; } private double tru(double a, double b) { // Phương thức private, trả về double return a - b; } public static void chaoMung() { // Phương thức static, không trả về giá trị System.out.println("Chào mừng đến với Máy tính."); }
}
Method Overloading (Nạp chồng phương thức)
Method overloading là việc định nghĩa nhiều phương thức có cùng tên nhưng danh sách tham số khác nhau trong cùng một class. Trình biên dịch Java sẽ xác định phương thức quá tải nào sẽ được gọi dựa trên số lượng, kiểu và thứ tự của các đối số được truyền trong quá trình gọi phương thức.
Java chỉ có thể chọn một phương thức được nạp chồng nếu nó tìm thấy một đối số khớp chính xác hoặc nếu nó có thể tìm thấy một phiên bản cụ thể hơn thông qua các chuyển đổi mở rộng (widening conversions
) (ví dụ: int
sang long
, int
sang double
, v.v.).
Ví dụ:
public class PhepToan { // Phương thức cong với hai số nguyên public int cong(int a, int b) { System.out.println("Gọi phương thức cong(int, int)"); return a + b; } // Phương thức cong với ba số nguyên public int cong(int a, int b, int c) { System.out.println("Gọi phương thức cong(int, int, int)"); return a + b + c; } // Phương thức cong với hai số double public double cong(double a, double b) { System.out.println("Gọi phương thức cong(double, double)"); return a + b; } public static void main(String[] args) { PhepToan pt = new PhepToan(); System.out.println(pt.cong(5, 10)); // Gọi cong(int, int) System.out.println(pt.cong(1, 2, 3)); // Gọi cong(int, int, int) System.out.println(pt.cong(2.5, 3.5)); // Gọi cong(double, double) System.out.println(pt.cong(5, 10.0)); // int to double widening conversion, gọi cong(double, double) }
}
Varargs (Tham số có độ dài thay đổi)
Varargs (variable-length arguments) cho phép các phương thức chấp nhận một số lượng đối số không xác định có cùng một kiểu dữ liệu. Để định nghĩa một phương thức với varargs, sử dụng ba dấu chấm (...
) sau kiểu dữ liệu của tham số cuối cùng trong khai báo phương thức.
Một tham số varargs phải là tham số cuối cùng trong danh sách tham số của một phương thức, và chỉ một tham số varargs được phép trên mỗi phương thức.
Các phương thức với varargs có thể được nạp chồng, nhưng bạn phải tránh sự mơ hồ bằng cách đảm bảo các chữ ký phương thức khác nhau.
Ví dụ:
public class MayTinhTong { // Phương thức tính tổng các số nguyên sử dụng varargs public int tinhTong(int... soNguyen) { int tong = 0; for (int so : soNguyen) { tong += so; } return tong; } // Ví dụ về nạp chồng varargs (cần tránh sự mơ hồ) // public int tinhTong(String ten, int... soNguyen) { ... } // Hợp lệ // public int tinhTong(int... soNguyen, String ten) { ... } // Lỗi biên dịch: varargs phải là cuối cùng public static void main(String[] args) { MayTinhTong mtt = new MayTinhTong(); System.out.println("Tổng của 1, 2: " + mtt.tinhTong(1, 2)); System.out.println("Tổng của 1, 2, 3, 4, 5: " + mtt.tinhTong(1, 2, 3, 4, 5)); System.out.println("Tổng không có số nào: " + mtt.tinhTong()); // Mảng rỗng }
}
Constructors (Hàm khởi tạo)
Constructors là các phương thức đặc biệt được sử dụng để khởi tạo đối tượng. Chúng được gọi khi một thể hiện của một class được tạo bằng từ khóa new
. Constructors có cùng tên với class và không có kiểu trả về (ngay cả void
).
Ví dụ:
class ConMeo { String ten; String mauLong; // Constructor mặc định (nếu không khai báo, Java sẽ tự động tạo) public ConMeo() { System.out.println("Một con mèo mới được tạo (constructor mặc định)."); } // Constructor với tham số public ConMeo(String tenMeo, String mauLongMeo) { this.ten = tenMeo; this.mauLong = mauLongMeo; System.out.println("Mèo " + tenMeo + " màu " + mauLongMeo + " đã được tạo."); } void hienThiThongTin() { System.out.println("Tên: " + ten + ", Màu lông: " + mauLong); }
} public class ViDuConstructor { public static void main(String[] args) { ConMeo tom = new ConMeo("Tom", "Xám"); // Gọi constructor với tham số tom.hienThiThongTin(); ConMeo jerry = new ConMeo(); // Gọi constructor mặc định jerry.ten = "Jerry"; jerry.mauLong = "Nâu"; jerry.hienThiThongTin(); }
}
Instance Initializers (Khối khởi tạo thể hiện)
Instance initializers là các khối mã được thực thi khi một đối tượng được tạo, tương tự như constructors nhưng không có tham số. Chúng được đặt trong cặp ngoặc nhọn {}
bên trong thân class. Chúng sẽ được thực thi trước constructor.
Ví dụ:
class SinhVienMoi { String maSV; String khoa; // Instance Initializer Block { System.out.println("Khối khởi tạo thể hiện đang được thực thi."); this.khoa = "Công nghệ thông tin"; // Gán giá trị mặc định cho khoa } public SinhVienMoi(String maSV) { System.out.println("Constructor đang được thực thi."); this.maSV = maSV; } public static void main(String[] args) { SinhVienMoi sv1 = new SinhVienMoi("SV001"); System.out.println("Mã SV: " + sv1.maSV + ", Khoa: " + sv1.khoa); }
}
Static Initializers (Khối khởi tạo tĩnh)
Static initializers là các khối mã được thực thi khi một class được nạp vào bộ nhớ, trước khi bất kỳ thể hiện nào của class được tạo. Chúng được định nghĩa bằng cách sử dụng từ khóa static
theo sau là {}
.
Ví dụ:
class CauHinh { static String tenUngDung; static int phienBan; // Static Initializer Block static { System.out.println("Khối khởi tạo tĩnh đang được thực thi."); tenUngDung = "Hệ thống Quản lý"; phienBan = 1; } public static void main(String[] args) { System.out.println("Tên ứng dụng: " + CauHinh.tenUngDung); System.out.println("Phiên bản: " + CauHinh.phienBan); // Khi chạy main, class CauHinh được nạp, static initializer block sẽ chạy. }
}
java.lang.Object Class
Mỗi class trong Java đều ngầm định (implicitly) kế thừa từ class java.lang.Object
. Điều này có nghĩa là tất cả các class đều có quyền truy cập vào các phương thức cơ bản được định nghĩa trong Object
class, như toString()
, equals()
, và hashCode()
.
Ví dụ:
class Person { String name; public Person(String name) { this.name = name; } @Override // Ghi đè phương thức toString() từ Object class public String toString() { return "Tên của tôi là: " + this.name; }
} public class ViDuObjectClass { public static void main(String[] args) { Person p = new Person("An"); System.out.println(p.toString()); // Hoặc đơn giản là System.out.println(p); }
}
Nested Classes (Các lớp lồng nhau)
Nested classes là các class được định nghĩa bên trong một class khác. Chúng có thể là:
- Static Nested Classes: Liên kết với class bên ngoài (outer class) và có thể truy cập trực tiếp các thành viên tĩnh của nó. Chúng có thể được khởi tạo độc lập, không cần một thể hiện của outer class.
- Inner Classes (Non-static Nested Classes): Liên kết với một thể hiện của outer class và có quyền truy cập vào cả thành viên tĩnh và không tĩnh của outer class. Chúng yêu cầu một thể hiện của outer class để được khởi tạo.
- Local Classes: Được định nghĩa bên trong một khối, thường là một phương thức, và có quyền truy cập vào các biến final hoặc effectively final từ phạm vi bao quanh. Chúng không thể có access modifiers và chỉ hiển thị trong khối định nghĩa.
- Anonymous Classes: Được định nghĩa trong một biểu thức và được sử dụng để tạo các triển khai một lần của các interface hoặc abstract class. Chúng không có tên và được khởi tạo tại điểm khai báo.
Ví dụ (Static Nested Class và Inner Class):
class OuterClass { private static String msgStatic = "Hello from static outer."; private String msgNonStatic = "Hello from non-static outer."; // Static Nested Class static class StaticNestedClass { public void display() { System.out.println(msgStatic); // Có thể truy cập thành viên static của OuterClass // System.out.println(msgNonStatic); // Lỗi: không thể truy cập thành viên non-static } } // Inner Class (Non-static Nested Class) class InnerClass { public void display() { System.out.println(msgStatic); // Có thể truy cập thành viên static System.out.println(msgNonStatic); // Có thể truy cập thành viên non-static } }
} public class ViDuNestedClass { public static void main(String[] args) { // Tạo thể hiện của Static Nested Class OuterClass.StaticNestedClass staticNested = new OuterClass.StaticNestedClass(); staticNested.display(); // Tạo thể hiện của Inner Class OuterClass outer = new OuterClass(); OuterClass.InnerClass inner = outer.new InnerClass(); inner.display(); }
}
Quy tắc đặt tên file Java Source
- Nếu một class được khai báo
public
, tên của file mã nguồn Java phải khớp chính xác với tên của class public, bao gồm cả chữ hoa chữ thường, với phần mở rộng.java
. - Một file mã nguồn Java có thể chứa nhiều định nghĩa class, nhưng chỉ một trong số chúng có thể được khai báo public.
- Nếu không có class public nào, tên file có thể khác với tên các class.
Đây là phần đầu tiên trong series về Lập trình hướng đối tượng trong Java. Hy vọng những kiến thức cơ bản này sẽ giúp các bạn có cái nhìn tổng quan và vững chắc để tiếp tục khám phá những phần tiếp theo. Hãy theo dõi để đón đọc Phần 2 nhé!