Dù công nghệ liên tục thay đổi, Java vẫn giữ vị thế hàng đầu trong các lĩnh vực Enterprise, Mobile và Big Data. Tuy nhiên, sai lầm phổ biến của nhiều lập trình viên là lạm dụng Framework (Spring, Java EE) khi chưa vững nền tảng. Hậu quả là code thiếu tối ưu, dễ gặp các lỗi khó chịu về hiệu suất (performance) hay rò rỉ bộ nhớ (memory leak).
Để giúp bạn xây dựng bàn đạp vững chắc trước khi tiến tới các công nghệ nâng cao, bài viết này sẽ hệ thống lại 5 trụ cột cốt lõi nhất của Java (Java Core):
- Cú pháp và các khái niệm cơ bản
- Lập trình hướng đối tương OOP
- Collections: Quản lý cấu trúc dữ liệu hiệu quả
- Multithreading: Xử lý đa luồng và tối ưu hiệu năng
- I/O: Cơ chế đọc/ghi dữ liệu.
Đọc chi tiết: Java là gì? Tất cả những điều bạn cần biết về ngôn ngữ Java
Kiến trúc và môi trường thực thi
JDK, JRE và JVM
Trước khi viết dòng code đầu tiên, chúng ta cần hiểu code đó sẽ chạy ở đâu và như thế nào. Rất nhiều lỗi môi trường (environment setup) xuất phát từ việc không phân biệt được bộ ba khái niệm JDK, JRE và JVM.
Hãy tưởng tượng mối quan hệ này như cấu trúc bao hàm: JDK chứa JRE, và JRE chứa JVM.
- JVM (Java Virtual Machine – Máy ảo Java): Đây là “trái tim” của Java. Nó không hiểu code bạn viết (file
.java), nhưng nó hiểu Bytecode (file.class). JVM chịu trách nhiệm chuyển đổi Bytecode thành mã máy (machine code) tương ứng với từng hệ điều hành (Windows, Linux, macOS). Đây chính là lý do Java có slogan: “Write Once, Run Anywhere”. - JRE (Java Runtime Environment): Nếu bạn chỉ là người dùng muốn chạy ứng dụng Java, bạn chỉ cần JRE. Nó bao gồm JVM + các thư viện chuẩn (Class Libraries) cần thiết để chương trình hoạt động.
- JDK (Java Development Kit): Nếu bạn là lập trình viên muốn viết và biên dịch code, bạn cần JDK. Nó bao gồm JRE + các công cụ phát triển (Development Tools) như trình biên dịch (
javac), trình gỡ lỗi (jdb), v.v.
Bytecode & Quy trình thực thi
Khác với C++ (biên dịch thẳng ra mã máy), quy trình của Java đi qua một bước trung gian:
- Biên dịch (Compile): Trình biên dịch
javacchuyển đổi mã nguồn (.java) thành Bytecode (.class). Bytecode không phải là mã máy, mà là tập lệnh cho máy ảo. - Thực thi (Run): Khi chạy chương trình, JVM sẽ đọc Bytecode này và dùng trình biên dịch JIT (Just-In-Time) để dịch nó sang mã máy thật sự của thiết bị đang chạy.
Cấu trúc chương trình Java cơ bản
Mọi ứng dụng Java, dù lớn hay nhỏ, đều bắt đầu từ những cấu trúc nền tảng sau. Hãy cùng tìm hiểu qua một đoạn code cơ bản:
package com.example.helloworld; // 1. Khai báo Package
import java.util.Date; // 2. Import thư viện
public class HelloWorld { // 3. Khai báo Class
// 4. Main method - Điểm khởi đầu của chương trình
public static void main(String[] args) {
System.out.println("Xin chào Java Core!");
}
}
- Package (Gói): Giống như các thư mục trong máy tính, Package dùng để gom nhóm các Class liên quan lại với nhau, giúp code gọn gàng và tránh xung đột tên.
- Import: Dùng để import các Class từ package khác (ví dụ: muốn dùng ngày tháng phải import
java.util.Date). - Class (Lớp): Đơn vị cấu tạo cơ bản nhất của Java. Mọi đoạn code Java đều phải nằm trong một Class. Tên Class phải trùng với tên file (ví dụ file
HelloWorld.javathì class phải làHelloWorld). - Main Method:
public static void main(String[] args)- Đây là “cánh cổng” đầu tiên mà JVM tìm đến để bắt đầu chạy chương trình.
- Nếu thiếu hàm này, chương trình sẽ không thể thực thi (mặc dù vẫn có thể biên dịch).
Cú pháp và các khái niệm cơ bản
Nếu JVM là trung tâm xử lý, thì cú pháp (syntax) chính là cách chúng ta ra lệnh cho bộ máy đó hoạt động. Trong phần này, chúng ta sẽ đi qua các quy tắc cốt lõi để xây dựng logic chương trình.
Kiểu dữ liệu (Data Types)
Java là ngôn ngữ định kiểu mạnh (strongly typed), nghĩa là mọi biến đều phải được khai báo kiểu dữ liệu cụ thể trước khi sử dụng. Java chia dữ liệu thành 2 nhóm chính:
- Primitive Types: Đây là 8 kiểu dữ liệu cơ bản nhất, lưu trữ giá trị trực tiếp trong bộ nhớ (Stack) để tối ưu hiệu suất.
- Số nguyên:
byte,short,int,long. - Số thực:
float,double. - Ký tự:
char(lưu mã Unicode). - Luận lý:
boolean(true/false).
Điểm khác biệt của Java: Trong C/C++, kích thước của int có thể thay đổi tùy theo hệ điều hành (16-bit hoặc 32-bit). Nhưng trong Java, kích thước này là cố định (ví dụ: int luôn là 32-bit) bất kể bạn chạy trên Windows hay Linux. Điều này đảm bảo tính nhất quán của slogan “Write Once, Run Anywhere”.
- Wrapper Classes (Lớp bao đóng) & Autoboxing
Tại sao chúng ta có int rồi mà vẫn cần Integer? Vì các cấu trúc dữ liệu trong Java (như ArrayList, Vector) chỉ làm việc với Object, không làm việc với Primitive. Bạn không thể viết ArrayList<int>, mà bắt buộc phải là ArrayList<Integer>.
- Autoboxing: Java tự động chuyển đổi từ Primitive sang Wrapper (VD:
int->Integer). - Unboxing: Ngược lại, chuyển từ Wrapper sang Primitive.
Biến, Hằng và Phạm vi (Variables, Constants, Scope)
Việc hiểu vòng đời của biến giúp bạn quản lý bộ nhớ tốt hơn và tránh các lỗi logic.
Biến cục bộ (Local Variable)
- Khai báo trong phương thức (method) hoặc khối lệnh (block).
- Chỉ tồn tại khi phương thức đang chạy. Bắt buộc phải khởi tạo giá trị trước khi dùng.
Biến thể hiện (Instance Variable)
- Khai báo trong class nhưng ngoài method.
- Gắn liền với một đối tượng (Object) cụ thể. Mỗi object có một bản sao riêng.
Biến tĩnh (Static Variable)
- Khai báo với từ khóa
static. - Gắn liền với Class, không phụ thuộc vào Object. Tất cả các object dùng chung một biến này (thường dùng để đếm số lượng đối tượng hoặc làm hằng số chung).
Từ khóa final
Dùng để khai báo hằng số hoặc ngăn chặn sự thay đổi.
- Với biến: Giá trị không thể thay đổi sau khi khởi tạo (immutable).
- Với method: Không thể bị ghi đè (override).
- Với class: Không thể kế thừa (inherit).
Các toán tử và Điều khiển luồng
Logic của chương trình được điều hướng thông qua các cấu trúc này.
Toán tử: Ngoài các toán tử số học (+, -, *, /) và logic (&&, ||, !) quen thuộc, Java cung cấp các toán tử thao tác bit (bitwise) cho các tác vụ cấp thấp.
Cấu trúc điều kiện:
if-else: Dùng cho các điều kiện phức tạp.switch-case: Tối ưu hơnif-elsekhi kiểm tra một biến với nhiều giá trị cố định (từ Java 14 trở lên đã hỗ trợswitchexpression gọn gàng hơn).
Vòng lặp (Loops):
for: Khi biết trước số lần lặp.while/do-while: Khi lặp dựa trên điều kiện (kiểm tra trước hoặc sau).for-each(Enhanced for loop): Cú pháp cực kỳ quan trọng khi làm việc với Mảng hoặc Collections vì tính ngắn gọn và dễ đọc.
// Ví dụ for-each
String[] names = {"Java", "Spring", "Android"};
for (String name : names) {
System.out.println(name);
}
Lập trình hướng đối tượng (OOP)
Đây là phần quan trọng nhất của bài viết, vì Java là ngôn ngữ thuần hướng đối tượng. Java được thiết kế hoàn toàn dựa trên mô hình đối tượng. Việc nắm vững OOP là bắt buộc để tổ chức code, tái sử dụng mã nguồn và đảm bảo tính bảo trì cho hệ thống.
Mình đã triển khai phần này với các ví dụ thực tế và giải thích cô đọng để làm rõ sự khác biệt giữa các khái niệm dễ nhầm lẫn (như Overloading vs Overriding, Abstract Class vs Interface).

Class và Object
Đây là cặp khái niệm nền tảng định hình nên chương trình Java:
- Class (Lớp): Là khuôn mẫu (blueprint) định nghĩa các thuộc tính (dữ liệu) và phương thức (hành vi) chung. Ví dụ: Bản vẽ thiết kế xe hơi.
- Object (Đối tượng): Là thể hiện cụ thể (instance) được khởi tạo từ Class, chiếm ô nhớ trong Heap. Ví dụ: Một chiếc xe cụ thể được sản xuất từ bản vẽ trên.
Các thành phần bổ trợ
- Constructor (Hàm khởi tạo): Phương thức đặc biệt chạy ngay khi khởi tạo đối tượng bằng từ khóa
new. Mục đích chính là thiết lập giá trị ban đầu cho các thuộc tính. - Từ khóa
thisvàsuper:this: Tham chiếu đến chính đối tượng hiện tại (thường dùng để phân biệt biến instance và tham số truyền vào).super: Tham chiếu đến đối tượng của lớp cha trực tiếp (dùng để gọi constructor hoặc phương thức của lớp cha).
4 trụ cột của OOP
Sức mạnh của Java nằm ở việc vận dụng linh hoạt 4 nguyên lý sau:
Tính Đóng gói (Encapsulation)
- Cơ chế: Che giấu dữ liệu bên trong của đối tượng, chỉ cho phép truy cập thông qua các phương thức được kiểm soát.
- Thực thi: Khai báo thuộc tính là
privatevà cung cấp các phương thứcpublic(Getter/Setter) để truy xuất/cập nhật dữ liệu. - Access Modifiers: Quy định phạm vi truy cập, xếp theo mức độ mở dần:
private->default(mặc định) ->protected->public.
Tính Kế thừa (Inheritance)
- Cơ chế: Cho phép lớp con (Child) thừa hưởng các thuộc tính và phương thức của lớp cha (Parent), giúp tái sử dụng mã nguồn.
- Cú pháp: Sử dụng từ khóa
extends. - Lưu ý: Java không hỗ trợ đa kế thừa (một lớp con không thể có nhiều lớp cha) để tránh xung đột logic (Diamond Problem), nhưng có thể cài đặt (implement) nhiều Interface.
Tính Đa hình (Polymorphism)
Cho phép một hành động có thể thực hiện theo nhiều cách khác nhau tùy thuộc vào đối tượng gọi nó.
- Nạp chồng (Method Overloading) – Compile-time: Các phương thức trong cùng một class có cùng tên nhưng khác tham số (số lượng hoặc kiểu dữ liệu).
- Ghi đè (Method Overriding) – Runtime: Lớp con định nghĩa lại nội dung phương thức đã tồn tại ở lớp cha để phù hợp với hành vi riêng của nó.
Tính Trừu tượng (Abstraction)
Tập trung vào tính năng của đối tượng thay vì chi tiết thực thi bên trong.
- Abstract Class: Lớp dùng từ khóa
abstract, có thể chứa cả phương thức trừu tượng (chưa có thân hàm) và phương thức thường. Dùng cho quan hệ “is-a” (Là một). - Interface: Bản thiết kế hành vi thuần túy. Trước Java 8, Interface chỉ chứa phương thức trừu tượng và hằng số. Từ Java 8 trở đi, Interface hỗ trợ
default methodvàstatic method. Dùng cho quan hệ “can-do” (Có khả năng làm gì).
Đọc chi tiết: OOP là gì? 4 đặc tính cơ bản của OOP
Java Collections Framework (JCF)
JCF cung cấp kiến trúc thống nhất để lưu trữ và thao tác với nhóm các đối tượng.
Các Interface chính
- List: Chứa các phần tử có thứ tự, cho phép trùng lặp. Truy cập phần tử theo chỉ số (index).
- Set: Chứa các phần tử không trùng lặp (unique).
- Map: Lưu trữ dữ liệu dưới dạng cặp khóa-giá trị (Key-Value). Key là duy nhất. (Lưu ý: Map không kế thừa từ Collection interface).
So sánh các triển khai phổ biến (Implementations)
ArrayList vs LinkedList:
ArrayList: Dùng mảng động. Truy xuất phần tử (get) nhanh, chèn/xóa chậm (do phải dịch chuyển mảng).LinkedList: Dùng danh sách liên kết đôi. Chèn/xóa nhanh, truy xuất chậm.
HashSet vs TreeSet:
HashSet: Không đảm bảo thứ tự, hiệu năng cao nhờ cơ chế băm (hashing).TreeSet: Sắp xếp các phần tử tăng dần, hiệu năng thấp hơn.
HashMap vs TreeMap:
HashMap: Lưu trữ Key-Value không theo thứ tự.TreeMap: Lưu trữ Key-Value với Key được sắp xếp.
Generics
Cơ chế cho phép chỉ định kiểu dữ liệu cụ thể mà Collection sẽ chứa (ví dụ: List<String>).
Mục đích là đảm bảo an toàn kiểu (Type-safety) tại thời điểm biên dịch và loại bỏ việc phải ép kiểu (casting) thủ công khi lấy dữ liệu ra, giảm thiểu ClassCastException.
Đa luồng cơ bản (Basic Multithreading)
Cho phép ứng dụng thực hiện nhiều tác vụ đồng thời, tối ưu hóa thời gian chờ của CPU.
Khái niệm Thread và Process
- Process (Tiến trình): Một chương trình đang chạy, có không gian bộ nhớ riêng biệt.
- Thread (Luồng): Đơn vị xử lý nhỏ nhất trong một tiến trình. Các thread trong cùng một process chia sẻ chung vùng nhớ (Heap), giúp giao tiếp nhanh nhưng dễ gây xung đột dữ liệu.
Cách tạo Thread
Có 2 cách chính để khởi tạo một luồng:
- Kế thừa lớp Thread: Viết lại phương thức
run(). Hạn chế là class không thể kế thừa class khác. - Triển khai interface Runnable: (Khuyên dùng) Triển khai phương thức run() và truyền vào đối tượng
Thread. Linh hoạt hơn vì Java cho phép implement nhiều interface.
Vòng đời của Thread (Lifecycle)
Một thread trải qua các trạng thái:
- New: Đã khởi tạo nhưng chưa chạy (chưa gọi
start()). - Runnable: Đang chạy hoặc sẵn sàng chạy.
- Blocked/Waiting: Tạm dừng để chờ tài nguyên hoặc chờ thread khác.
- Terminated: Hoàn thành công việc hoặc bị ngắt do lỗi.
Đồng bộ hóa (Synchronization)
Do các thread chia sẻ bộ nhớ, việc nhiều thread cùng thay đổi một dữ liệu sẽ gây ra sai lệch (Race Condition).
Giải pháp: Sử dụng từ khóa synchronized trên phương thức hoặc khối lệnh để khóa (lock) tài nguyên. Tại một thời điểm, chỉ một thread được phép truy cập vào vùng được đánh dấu synchronized.
I/O Cơ bản (Input/Output)
Java I/O (gói java.io) xử lý việc đọc và ghi dữ liệu thông qua khái niệm Stream (Luồng).
Phân loại theo đơn vị dữ liệu
- Byte Stream (Luồng Byte): Xử lý dữ liệu nhị phân thô (8-bit). Dùng cho hình ảnh, video, file âm thanh.: Các lớp chính:
FileInputStream,FileOutputStream. - Character Stream (Luồng Ký tự): Xử lý dữ liệu văn bản (16-bit Unicode). Tự động xử lý các vấn đề về mã hóa ký tự (encoding).: Các lớp chính:
FileReader,FileWriter,PrintWriter.
Các lớp hỗ trợ hiệu năng và thao tác
- File: Đại diện cho đường dẫn tệp hoặc thư mục, dùng để lấy thông tin metadata (kích thước, quyền truy cập) chứ không dùng để đọc/ghi nội dung.
- BufferedReader / BufferedWriter: Sử dụng bộ đệm (buffer) để giảm số lần truy cập trực tiếp vào ổ cứng, giúp tăng tốc độ đọc/ghi văn bản đáng kể.
Xử lý Ngoại lệ (Exception Handling)
Ngoài các trụ cột cơ bản trong java được trình bày kể trên thì cơ chế xử lý ngoại lệ (Exception Handling) này giúp ứng dụng duy trì luồng hoạt động bình thường ngay cả khi xảy ra lỗi ngoài ý muốn.
Hệ thống cấp bậc (Exception Hierarchy)
Tất cả các lỗi trong Java đều kế thừa từ lớp cha Throwable.
- Error: Các lỗi nghiêm trọng liên quan đến hệ thống hoặc JVM (ví dụ:
OutOfMemoryError,StackOverflowError). Ứng dụng thường không thể và không nên bắt (catch) các lỗi này. - Exception: Các lỗi liên quan đến logic chương trình hoặc I/O mà ứng dụng có thể xử lý được. Các loại Exception gồm:
- Checked Exceptions: Các ngoại lệ được kiểm tra tại thời điểm biên dịch (Compile-time). Bắt buộc phải xử lý bằng
try-catchhoặc khai báothrows. Ví dụ:IOException,SQLException. - Unchecked Exceptions (Runtime Exceptions): Các ngoại lệ xảy ra tại thời điểm chạy (Runtime), thường do lỗi logic của lập trình viên. Không bắt buộc phải xử lý tường minh. Ví dụ:
NullPointerException,ArrayIndexOutOfBoundsException.
- Checked Exceptions: Các ngoại lệ được kiểm tra tại thời điểm biên dịch (Compile-time). Bắt buộc phải xử lý bằng
Các khối xử lý và Custom Exception
- try-catch-finally:
try: Chứa đoạn code có nguy cơ gây lỗi.catch: Bắt và xử lý lỗi cụ thể.finally: Khối lệnh luôn luôn được thực thi (dù có lỗi hay không), thường dùng để đóng tài nguyên (close file, close connection).
- throws: Khai báo trên chữ ký phương thức, báo hiệu phương thức này có thể ném ra ngoại lệ và đẩy trách nhiệm xử lý cho phương thức gọi nó.
- Custom Exceptions: Tạo ngoại lệ riêng bằng cách kế thừa lớp
Exception(cho Checked) hoặc RuntimeException (cho Unchecked) để mô tả rõ ràng các lỗi nghiệp vụ cụ thể.
Các câu hỏi thường gặp về Java core
Sự khác biệt giữa Core Java và Advanced Java là gì?
Thực tế, “Core Java” và “Advanced Java” không phải là thuật ngữ chính thức trong tài liệu kỹ thuật của Oracle, mà là cách gọi thông dụng trong cộng đồng lập trình để phân chia lộ trình học.
- Core Java: Tương đương với phiên bản Java SE (Standard Edition). Nó bao gồm các API cơ bản và cốt lõi nhất của ngôn ngữ.
- Advanced Java: Thường ám chỉ các công nghệ thuộc Java EE (Enterprise Edition) hoặc Jakarta EE, dùng để xây dựng các ứng dụng Web và Doanh nghiệp quy mô lớn.
Bảng so sánh chi tiết:
| Đặc điểm | Java Core | Advanced Java |
| Phạm vi | Các khái niệm cơ bản: Cú pháp, OOP, Collections, Multithreading, I/O. | Các công nghệ chuyên sâu: Web Services, Database connectivity, Servlets, JSP. |
| Mục đích | Xây dựng nền tảng tư duy và phát triển ứng dụng Desktop độc lập. | Xây dựng ứng dụng Web, hệ thống phân tán và ứng dụng doanh nghiệp. |
| Kiến trúc | Đơn tầng (Single-tier architecture). | Đa tầng (Multi-tier architecture) như Client-Server, MVC. |
| Gói (Package) | java.lang, java.util, java.io, java.awt… | javax.servlet, javax.persistence, java.sql… |
Java truyền tham trị (Pass-by-value) hay tham chiếu (Pass-by-reference)?
Một câu hỏi gây tranh cãi nhưng câu trả lời chính xác là: Java chỉ hỗ trợ Pass-by-value (Truyền tham trị).
- Với kiểu nguyên thủy: Giá trị thực sự được copy và truyền vào hàm.
- Với kiểu đối tượng: Giá trị của biến tham chiếu (địa chỉ bộ nhớ) được copy và truyền vào hàm. Do đó, nếu bạn thay đổi thuộc tính của object bên trong hàm, object bên ngoài bị ảnh hưởng. Nhưng nếu bạn gán biến đó cho một object mới (
obj=new Object()), object bên ngoài không đổi.
Java có phải là ngôn ngữ thuần hướng đối tượng 100% không?
Không. Mặc dù Java tuân thủ chặt chẽ mô hình OOP, nhưng nó không thuần khiết 100% (như Smalltalk). Lý do chính là sự tồn tại của Primitive Types (kiểu nguyên thủy: int, char, boolean…). Các kiểu dữ liệu này không phải là Object để đảm bảo hiệu suất (performance) cho hệ thống.
Kết luận
Có thể khẳng định, Java Core chính là ‘trái tim’ của toàn bộ hệ sinh thái Java rộng lớn. Dù công nghệ có thay đổi, các Framework mới có ra đời, thì những giá trị nền tảng của Java Core vẫn luôn giữ nguyên tính thời sự và quan trọng. Việc đầu tư thời gian nghiêm túc cho phần kiến thức này sẽ mang lại lợi tức to lớn cho con đường phát triển kỹ năng phần mềm của bạn về lâu dài.

