Chào bạn, bạn đang muốn
tự học lập trình Java?
Tự học Java tại nhà để chuẩn bị học lập trình web, lập trình Android?
Vậy thì, đây là bài hướng dẫn chi tiết bạn cần tìm. Hướng dẫn này giúp bạn
tự học lập trình Java từ cơ bản đến nâng cao (Được thiết kế dành riêng cho những người mới bắt đầu).
Hướng dẫn Tự học Lập trình Java (One for All)
Cũng đừng lo lắng nếu bạn chưa biết gì về lập trình. Bạn sẽ học được tất cả trong bài hướng dẫn này (One for All - Một cho tất cả).
Giới thiệu Series Tự học Java | NIIT - ICT Hà Nội
Việc của bạn là ngồi vào máy tính và bắt đầu đọc và làm theo hướng dẫn, đọc từ trên xuống dưới và code lại từng ví dụ (nhiều lần).
Bài viết
hướng dẫn tự học lập trình Java bao gồm:
Mục tiêu của bài viết này là giúp bạn có kiến thức vững chắc về ngôn ngữ lập trình Java trước khi bạn chuyển trang.
> Chú ý: Nếu bạn muốn học JAVA nhanh hơn, học trực tiếp với chuyên gia doanh nghiệp thì đăng ký ngay: KHÓA HỌC JAVA
Một điều cần nhớ trước khi bạn bắt đầu tự học lập trình Java:
"MỖI MỘT VÍ DỤ ĐỀU SẼ GIÚP BẠN HIỂU VÀ QUEN VỚI LẬP TRÌNH JAVA. VÌ THẾ HÃY TỰ CODE LẠI TẤT CẢ CÁC VÍ DỤ JAVA ÍT NHẤT 1 - 2 LẦN"
Còn nếu bạn thấy lười làm lại các ví dụ, thì đừng lãng phí thời gian nữa! Bạn sẽ không bao giờ học lập trình thành công đâu.
Thế nên, nếu đã chọn học lập trình. Hãy quyết tâm cao độ và bắt đầu tự học Java theo hướng dẫn này.
> Ngoài ra, mình khuyên bạn nên đọc thêm bài viết: 6 BƯỚC TỰ HỌC LẬP TRÌNH (như người có kinh nghiệm) để biết có thêm kinh nghiệm khi tự học.
Nào, giờ nếu bạn đã chắc chắn.
Hãy bắt đầu chinh phục ngôn ngữ lập trình Java.
CHƯƠNG I: BẮT ĐẦU TỰ HỌC LẬP TRÌNH JAVA
Để bắt đầu, như mọi lập trình viên khác.
Chúng ta sẽ thử viết chương trình "Hello Word"
với Java.
Chương I. Phần 1. Viết chương trình Hello World bằng Java
"Hello, World!" là một chương trình đơn giản để xuất ra dòng chữ Hello, World!
trên màn hình.
Vì đây là một chương trình rất đơn giản, nên nó thường được sử dụng để giới thiệu một ngôn ngữ lập trình mới cho người mới học.
Hãy cùng mình xem cách chương trình "Hello, World!"
hoạt động.
Nếu bạn muốn chạy chương trình này trong máy tính của mình, bạn phải cài đặt Java đúng cách.
Ngoài ra, bạn cần một phần mềm lập trình Java (gọi tắt là IDE hoặc trình soạn thảo văn bản) để viết và chỉnh sửa code Java. (Phát hiện lỗi tốt hơn)
* Bạn có thể tải bộ cài nhanh VSCode for Java với package đóng gói sẵn (chỉ khoảng 38MB) -> Và install (Học Java Core thì VSCode là đủ)
Chương I. Phần 1.1. Chương trình Hello World trong Java
Video tự học Java 01: Viết chương trình Java đầu tiên: Hello World!
Đây là code của chương trình Hello World bằng ngôn ngữ Java:
// Chương trình "Hello World!"
class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
> Lưu ý: Nếu bạn đã copy code chính xác, bạn cần lưu tên tệp là HelloWorld.java
. Đó là vì Java yêu cầu tên của class (lớp) phải giống tên tệp (class chứa hàm main).
Khi bạn chạy chương trình, kết quả sẽ là:
Hello World!
Chương I. Phần 1.2. Chương trình "Hello World!" bằng Java hoạt động thế nào?
Bây giờ, mình sẽ giải thích cách chương trình "Hello World!" hoạt động, từng phần, từng phần 1.
// Your First Program
Trong Java, bất kỳ dòng nào bắt đầu bằng //
là một comment (nhận xét).
Comment chỉ dành cho người đọc code để hiểu rõ hơn về ý định và chức năng của chương trình.
Nó hoàn toàn bị bỏ qua trình biên dịch Java.
Máy ảo Java (JVM) dịch chương trình Java sang Java bytecode mà máy tính có thể thực thi.
Để tìm hiểu thêm về comment, hãy đọc bài viết comment trong Java.
class HelloWorld { ... }
Trong Java, mọi ứng dụng bắt đầu với một định nghĩa class.
Trong chương trình trên, HelloWorld
là tên của class.
Bây giờ,
Bạn chỉ cần nhớ rằng, mọi ứng dụng Java đều phải định nghĩa class.
Tên của class phải khớp với tên của tệp.
public static void main(String[] args) { ... }
Đây là phương thức main
Mọi ứng dụng trong Java phải chứa một phương thức main
Và, Trình biên dịch Java sẽ bắt đầu thực thi chương trình từ phương thức main
này.
Vì đây là là một chương trình cơ bản để giới thiệu ngôn ngữ lập trình Java cho người mới học.
Nên bạn chưa cần hiểu cách main hoạt động.
Chúng ta sẽ tìm hiểu về public
, static
, void
và cách một phương thức Java hoạt động trong phần sau.
Bây giờ, chỉ cần nhớ rằng máy sẽ đi vào phương thức main
đầu tiên.
Và,
Bắt buộc phải có phương thức main
trong một chương trình Java.
Tiếp theo,
System.out.println("Hello World!");
Đoạn code trên in chuỗi bên trong dấu ngoặc kép Hello, World!
đến đầu ra tiêu chuẩn (màn hình của bạn).
Lưu ý, câu lệnh này nằm trong hàm main, hàm main nằm trong class HelloWorld
Chương I. Phần 1.3. Bạn học được gì từ chương trình Hello, World với Java?
Ok,
Bây giờ bạn đã đi hết phần viết chương trình "Hello, World!"
bằng Java.
Và đây là những thứ bạn cần phải nhớ:
-
Mỗi ứng dụng Java hợp lệ phải có định nghĩa class (tên class khớp với tên tệp)
-
Phương thức
main
(bắt buộc phải có) được đặt trong class
-
Trình biên dịch thực thi code từ hàm
main
Đây là một chương trình Java hợp lệ mà không thực hiện gì cả.
// Ví dụ một chương trình Java hợp lệ
public class HelloWorld {
public static void main(String[] args) {
// Viết code gì đó tại đây
}
}
Đừng lo lắng nếu bạn không hiểu ý nghĩa của class
, static
, phương thức (method
), v.v.
Bạn sẽ được học chi tiết trong các phần sau.
> Chú ý: Phần 2.1, 2.2, 2.3 ngay bên dưới đây là một phần khá là lý thuyết để giúp bạn hiểu về cấu trúc của ngôn ngữ Java và JVM. Bạn cũng có thể lướt qua nhanh hoặc đi ngay vào phần 3.
Chương I. Phần 2. Tìm hiểu về JDK, JRE và JVM
Trong phần này, bạn sẽ được tìm hiểu về sự khác biệt chính giữa JDK, JRE và JVM.
Chương I. Phần 2.1. JVM là cái gì? Có gì ảo diệu?
JVM (Máy ảo Java) là một máy trừu tượng cho phép máy tính của bạn chạy chương trình Java.
Khi bạn chạy chương trình Java,
Trước tiên, trình biên dịch Java sẽ biên dịch mã Java của bạn thành bytecode.
Sau đó, JVM dịch bytecode thành mã máy gốc (tập hợp các hướng dẫn mà CPU của máy tính thực thi trực tiếp).
Java là một ngôn ngữ độc lập với nền tảng.
Đó là bởi vì khi bạn viết mã Java, nó được viết cho JVM chứ không phải máy tính vật lý (máy tính) của bạn.
Do đó, chỉ cần có JVM là code Java của bạn chạy mọi nơi (Độc lập với nền tảng)
Cách chương trình Java hoạt động, thực thi
Nếu bạn quan tâm đến việc tìm hiểu về Kiến trúc JVM, hãy đọc bài viết Máy ảo Java.
Chương I. Phần 2.2. JRE là cái gì?
JRE (Java Runtime Environment) có nghĩa là môi trường thực thi Java.
JRE là gói phần mềm cung cấp các thư viện Java class, cùng với Máy ảo Java (JVM) và các thành phần khác để chạy các ứng dụng được viết bằng Java.
Hiểu đơn giản JRE là:
JRE = Java class + JVM
Nếu bạn cần chạy các chương trình Java, nhưng không phát triển ứng dụng nào cả, JRE là thứ bạn cần.
Bạn có thể tải xuống JRE từ trang chủ của Oracle (Ông chú phát hành Java)
Chương I. Phần 2.3. JDK là gì?
JDK (Java Development Kit) là một bộ công cụ phát triển phần mềm để phát triển các ứng dụng trong Java.
Khi bạn tải xuống JDK, JRE cũng được tải xuống và không cần tải xuống riêng.
Ngoài JRE, JDK cũng chứa số lượng công cụ phát triển (trình biên dịch, JavaDoc, Trình gỡ lỗi Java, v.v.).
JDK = JRE + Compiler + Debugger + ...
Nếu bạn muốn lập trình ứng dụng Java, hãy tải xuống JDK.
Đây là mối quan hệ giữa JVM, JRE và JDK.
Mối liên hệ giữa JVM, JRE và JDK
Chương I. Phần 3. Biến và Kiểu dữ liệu Nguyên thủy
Trong phần này, bạn sẽ tìm hiểu về các biến, cách tạo ra biến trong Java.
Và các kiểu dữ liệu khác nhau mà ngôn ngữ lập trình Java hỗ trợ để tạo các biến.
Chương I. Phần 3.1. Biến trong Java
Video Tự học Java 02: Tìm hiểu về Biến trong Java
Một biến là một vị trí trong bộ nhớ (vùng lưu trữ) để giữ dữ liệu.
Để chỉ ra vùng lưu trữ, mỗi biến phải được đặt một tên duy nhất (mã định danh). Tìm hiểu thêm về cách đặt tên trong Java.
Chương I. Phần 3.2. Cách khai báo biến trong Java
Đây là một ví dụ để khai báo một biến trong Java.
int tocDoToiDa = 80;
Ở đây,
tocDoToiDa
là một biến có kiểu dữ liệu int
và được gán giá trị 80
. Có nghĩa là biến tocDoToiDa
có thể lưu trữ các giá trị nguyên.
> Bạn sẽ tìm hiểu về các kiểu dữ liệu Java một cách chi tiết sau trong bài viết này.
Trong ví dụ này, chúng ta đã gán giá trị cho biến trong quá trình khai báo.
Tuy nhiên, nó không bắt buộc.
Bạn có thể khai báo các biến mà không cần gán giá trị, và sau đó bạn có thể lưu trữ giá trị theo ý muốn. Ví dụ:
int tocDoToiDa;
tocDoToiDa = 80;
Giá trị của biến này có thể thay đổi trong chương trình, ví dụ:
int tocDoToiDa;
...
tocDoToiDa = 90;
Java là một ngôn ngữ chặt chẽ static-typed.
Có nghĩa là tất cả các biến phải được khai báo trước khi chúng có thể được sử dụng.
Ngoài ra, trong Java, bạn không thể khai báo 2 biến trùng tên trong cùng phạm vi biến (Variable Scope).
Phạm vi biến là gì?
Đừng vội quan tâm về nó bây giờ.
Bây giờ, bạn chỉ cần nhớ, bạn không thể làm như thế này.
int speedLimit = 80;
... .. ...
int speedLimit;
Chương I. Phần 3.3. Quy tắc đặt tên biến trong Java
Ngôn ngữ lập trình Java có bộ quy tắc và quy ước riêng để đặt tên biến.
Đây là những gì bạn cần biết:
Biến trong Java phân biệt chữ HOA - chữ thường
Tên của biến là một chuỗi các chữ cái và chữ số Unicode. Nó có thể bắt đầu bằng một chữ cái, $
hoặc _
Tuy nhiên, tốt hơn hết là sử dụng chữ cái để bắt đầu tên của một biến.
Ngoài ra, tên biến không thể sử dụng khoảng trắng.
Khi tạo các biến, nên chọn một tên có ý nghĩa. Ví dụ: diemSo
, soChan
, capDo
tốt hơn là d
, s
, c
.
Nếu bạn chọn biến là một từ, nên sử dụng chữ cái viết thường. Ví dụ: tốt hơn là sử dụng diem
thay vì dIEM
hay là DIEM
.
Còn nếu bạn muốn đặt tên biến có từ 2 từ trở lên?
Hãy viết thường cho từ đầu tiên, các từ sau viết Hoa chữ cái đầu, ví dụ: tocDoToiDa
Có 4 loại biến trong ngôn ngữ lập trình Java:
-
Instance Variables (Biến thể hiện / Biến đối tượng)
-
Class Variables (Biến class) (Static Fields)
-
Local Variables (Biến cục bộ)
-
Parameters (Tham số)
Bạn sẽ tìm hiểu về 4 loại biến này ở các chương sau.
Như đã đề cập ở trên, Java là một ngôn ngữ rất chặt chẽ.
Điều này có nghĩa là, tất cả các biến phải được khai báo trước khi chúng có thể được sử dụng.
int tocDo;
Ở đây, tocDo
là một biến và kiểu dữ liệu của nó là int
.
Kiểu dữ liệu int
xác định rằng biến tocDo
chỉ có thể chứa số nguyên (integer).
Nói một cách đơn giản, kiểu dữ liệu của biến xác định các giá trị mà biến có thể lưu trữ.
Có 8 kiểu dữ liệu được xác định trước trong ngôn ngữ lập trình Java, được gọi là kiểu dữ liệu nguyên thủy.
Ngoài các kiểu dữ liệu nguyên thủy, còn có các kiểu dữ liệu được tham chiếu trong Java (bạn sẽ được học về chúng trong các chương sau).
Chương I. Phần 3.4. 8 Kiểu dữ liệu nguyên thủy trong Java
Kiểu dữ liệu #1. Boolean
Video Tự học Java 03: Kiểu dữ liệu Boolean trong Java
Kiểu dữ liệu boolean
có hai giá trị có thể là true
hoặc false
.
Giá trị mặc định: false
.
Chúng thường được sử dụng cho các điều kiện đúng / sai.
Ví dụ:
class ViDuKieuBoolean {
public static void main(String[] args) {
boolean dapAn = true;
System.out.println(dapAn);
}
}
Khi bạn chạy chương trình, kết quả sẽ là:
true
Kiểu dữ liệu #2. byte
Video Tự học Java 04: Kiểu dữ liệu Byte trong Java
Kiểu dữ liệu byte
có thể có giá trị từ -128 đến 127.
Kiểu byte
được sử dụng để thay thế kiểu int
để tiết kiệm bộ nhớ nếu dữ liệu nằm trong khoảng từ [-121, 127].
Giá trị mặc định: 0
Ví dụ:
class ViDuKieuByte {
public static void main(String[] args) {
byte testSo;
testSo = 124;
System.out.println(testSo);
}
}
Khi bạn chạy chương trình. Kết quả nhận được là:
124
Kiểu dữ liệu #3. short
Video Tự học Java 05: Kiểu dữ liệu Short trong Java
Kiểu dữ liệu short có thể chứa giá trị từ -32768 đến 32767.
Kiểu short
được sử dụng để thay thế kiểu int
để tiết kiệm bộ nhớ nếu dữ liệu nằm trong khoảng từ [-32768, 32767].
Giá trị mặc định: 0
Ví dụ:
class ViDuKieuShort {
public static void main(String[] args) {
short nhietDo;
nhietDo = -200;
System.out.println(nhietDo);
}
}
Khi bạn chạy chương trình. Kết quả nhận được là:
-200
Kiểu dữ liệu #4. int
Video Tự học Java 06: Kiểu dữ liệu Int trong Java
Kiểu dữ liệu int
có thể chứa giá trị từ -2^31 đến 2^31 - 1
Giá trị mặc định: 0
Ví dụ:
class ViDuKieuInt {
public static void main(String[] args) {
int soNguyenA = 5000000;
System.out.println(soNguyenA);
}
}
Khi bạn chạy chương trình. Kết quả nhận được là:
5000000
Kiểu dữ liệu #5. long
Kiểu dữ liệu long
có thể chứa giá trị từ -2^63 đến 2^63 - 1.
Giá trị mặc định: 0
Ví dụ:
class ViDuKieuLong {
public static void main(String[] args) {
long soB = 42332200000L;
System.out.println(soB);
}
}
Khi bạn chạy chương trình. Kết quả nhận được là:
42332200000
Lưu ý!
Chữ L
ở cuối số 42332200000
là không thể tách rời. Nó đại diện cho dữ liệu kiểu long
.
Kiểu dữ liệu #6. double
Video Tự học Java 07: Kiểu dữ liệu Double và Float trong Java
Kiểu dữ liệu double
là kiểu số thập phân 64-bit
Giá trị mặc định: 0.0
(0.0d
)
Ví dụ:
class ViDuKieuDouble {
public static void main(String[] args) {
float soDouble = -10.5d;
System.out.println(soDouble);
}
}
Khi bạn chạy chương trình. Kết quả nhận được là:
-10.5
Kiểu dữ liệu #7. float
Kiểu dữ liệu float
là kiểu số thập phân 32-bit.
Giá trị mặc định: 0.0
(0.0f
)
Ví dụ:
class ViDuKieuFloat {
public static void main(String[] args) {
float soFloat = -6.9f;
System.out.println(soFloat);
}
}
Khi bạn chạy chương trình. Kết quả nhận được là:
-6.9
Kiểu dữ liệu #8. char
Video Tự học Java 08: Kiểu dữ liệu char trong Java
Kiểu dữ liệu char
có thể chứa giá trị unicode 16-bit
Giá trị tối đa của kiểu dữ liệu char là '\u0000'
(0). Giá trị tối thiểu là '\uffff'
Giá trị mặc định: '\u0000'
Ví dụ:
class ViDuKieuChar {
public static void main(String[] args) {
char letter = '\u0051';
System.out.println(letter);
}
}
Khi bạn chạy chương trình. Kết quả nhận được là:
Q
Bạn nhận dược giá trị Q
bởi vì ký tự tương ứng với giá trị Unicode '\u0051'
là Q
.
Đây là một ví dụ khác về kiểu dữ liệu char:
class ViDuKieuChar2 {
public static void main(String[] args) {
char letter1 = '9';
System.out.println(letter1);
char letter2 = 65;
System.out.println(letter2);
}
}
Khi bạn chạy chương trình. Kết quả nhận được là:
Ngoài ra, Java cũng hỗ trợ cho chuỗi ký tự char thông qua class java.lang.String.
Bạn có thể tạo đối tượng chuỗi trong Java như sau:
chuoiCuaToi = "Tự học Lập trình Java";
Chuỗi trong Java là một chủ đề quan trọng.
Chúng ta sẽ tìm hiểu về chúng trong những phần tiếp theo của bài hướng dẫn này.
Chương I. Phần 3.5. Java literals
Để hiểu về literals, hãy lấy một ví dụ về gán giá trị cho biến.
Ở đây:
Theo nghĩa đen, literals là code đại diện cho một giá trị cố định.
Các giá trị 3
, 2.5
, true
, false
xuất hiện trực tiếp trong chương trình mà không đòi hỏi tính toán là literals.
Trong ví dụ trên dapAn
là một biến. boolean
là kiểu dữ liệu, nó cho phép biến dapAn
có thể lưu trữ giá trị true
hoặc false
.
Để trình biên dịch hiểu nó, nó đã yêu cầu phải tính toán.
Tuy nhiên, literals như là -5
, 'a'
, true
đại diện cho giá trị cố định.
Chương I. Phần 3.6. Integer Literals
Integer Literals được sử dụng để khởi tạo các biến dữ liệu kiểu nguyên như byte
, short
, int
và long
.
Nếu một số nguyên có kết thúc là l
hoặc L
, nó là kiểu dữ liệu long
.
Lưu ý!
Sử dụng L
thay vì l
// Lỗi! literal 42332200000 của kiểu type nằm ngoài phạm vi
long giaTriKieuLong1 = 42332200000;
// 42332200000L là kiểu long, nó không nằm ngoài phạm vi
long giaTriKieuLong2 = 42332200000L;
Integer literals có thể được thể hiện trong số thập phân và nhị phân số hệ thống.
Những con số bắt đầu với tố 0x
đại diện cho thập lục phân. Tương tự, số bắt đầu với tố 0b
đại diện cho nhị phân.
// Hệ Thập phân
int soThapPhan = 34;
// 0x Đại diện cho hệ thập lục phân
int soThapLucPhan = 0x2F;
// 0b Đại diện cho hệ nhị phân
int soNhiPhan = 0b10010;
Chương I. Phần 3.7. Floating-point Literals
Floating-point Literals được sử dụng để khởi tạo biến của dữ liệu loại float
và double
.
Nếu một thập phân số kết thúc với f
hoặc F
, nó là loại float
. Nếu không, nó là của double
.
Một số double
có thể tùy chọn kết thúc với D
hay d
. Tuy nhiên, nó không cần thiết.
Chúng cũng có thể biểu diễn trong ký hiệu khoa học sử dụng E
hoặc e
class ViDuKieuDouble2 {
public static void main(String[] args) {
double soDouble = 3.4;
float soFloat = 3.4F;
// 3.445*10^2
double soDoubleKieuKhoaHoc = 3.445e2;
System.out.println(soDouble);
System.out.println(myFsoFloatloat);
System.out.println(myDoubleScientific);
}
}
Khi bạn chạy chương trình. Kết quả nhận được là:
3.4
3.4
344.5
Chương I. Phần 3.8. Ký tự và Chuỗi literals
Chúng chứa các ký tự Unicode (UTF-16)
char
literals thì chúng ta sử dụng dấu nháy đơn ' '
. Ví dụ, 'a'
, '\u0000'
String
literals thì chúng ta sử dụng dấu nháy kép " "
. Ví dụ, "Tự học Lập trình Java"
, "Lập trình Java"
Bên cạnh đó, Java cũng hỗ trợ một vài ký tự đặc biệt:
-
Sử dụng ký tự
\
trước khi thêm ký tự đặc biệt. Ví dụ, \"
, \'
, \\
class ViDuVeLiterals {
public static void main(String[] args) {
char giaTriChar = 'g';
char themDongMoi = '\n';
String giaTriString = "Lập trình Java";
System.out.println(giaTriChar);
System.out.println(themDongMoi);
System.out.println(giaTriString);
}
}
Khi bạn chạy chương trình. Kết quả nhận được là:
g
Lập trình Java
Ok, đến đây bạn đã biết về một số kiểu dữ liệu trong Java.
Bây giờ, chúng ta sẽ tiếp tìm hiểu về toán tử và hiểu sâu hơn về các kiểu dữ liệu
Chương I. Phần 4. Toán tử trong Java
Trong phần hướng dẫn tự học Java này, bạn sẽ tìm hiểu các kiểu toán tử khác nhau trong ngôn ngữ Java.
Và tìm hiểu cách các toán tử chúng hoạt động thông qua các ví dụ.
Toán tử là gì?
Toán tử là biểu tượng đặc biệt (ký tự đặc biệt) thực hiện các hoạt động trên toán hạng (biến và giá trị).
Ví dụ, toán tử +
sẽ thực hiện cộng các toán hạng.
Trong phần 3 bạn đã được học cách khai báo biến và gán giá trị cho biến. Bây giờ, bạn sẽ học tiếp cách sử dụng toán tử để thao tác với các biến.
Chương I. Phần 4.1. Toán tử gán trong Java.
Video tự học Java 09: Toán tử gán trong Java
Toán tử gán trong Java được sử dụng để gán giá trị cho biến. Ví dụ:
Hành động này có nghĩa là, giá trị ở phía bên phải được gán cho biến nằm ở phía bên trái. Bạn không thể làm ngược lại.
Còn nhiều thứ về toán tử gán.
Tuy nhiên, để thật đơn giản, chúng ta sẽ tìm hiểu đến chúng sau.
Ví dụ về toán tử gán:
class ViDuToanTuGan {
public static void main(String[] args) {
int so1, so2;
// Gán 6 cho so1
so1 = 6;
System.out.println(so1);
// Gán giá trị của so1 cho so2
so2 = so1;
System.out.println(so2);
}
}
Khi bạn chạy chương trình. Kết quả nhận được là:
6
6
Chương I. Phần 4.2. Toán tử toán học trong Java
Toán tử toán học được sử dụng để tính toán như: Cộng, Trừ, Nhân, Chia
+
Cộng (Hoặc nối chuỗi)
-
Trừ
*
Nhân
/
Chia
%
Lấy phần dư
Ví dụ về toán tử toán học:
class ViDuToanTuToanHoc {
public static void main(String[] args) {
double so1 = 12.5, so2 = 3.5, ketQua;
// Cộng hai số
ketQua = so1 + so2;
System.out.println("so1 + so2 = " + ketQua);
// Trừ hai số
ketQua = so1 - so2;
System.out.println("so1 - so2 = " + ketQua);
// Nhân hai số
ketQua = so1 * so2;
System.out.println("so1 * so2 = " + ketQua);
// Chia hai số
ketQua = so1 / so2;
System.out.println("so1 / so2 = " + ketQua);
// lấy phần dư của so1 chia so2
ketQua = so1 % so2;
System.out.println("so1 % so2 = " + ketQua);
}
}
Khi bạn chạy chương trình. Kết quả nhận được là:
so1 + so2 = 16.0
so1 - so2 = 9.0
so1 * so2 = 43.75
so1 / so2 = 3.5714285714285716
so1 % so2 = 2.0
Trong ví dụ trên tất cả toán hạng được sử dụng biến.
Tuy nhiên, nó không bắt buộc. Bạn hoàn toàn có thể thực hiện giữa biến và số ...
Ví dụ:
ketQua = so1 + 5.2;
ketQua = 2.3 + 4.5;
so2 = so1 -2.9;
Ngoài ra, bạn cũng có thể sử dụng toán tử + để nối 2 hoặc nhiều chuỗi với nhau.
Ví dụ nối chuỗi bằng toán tử cộng:
class NoiChuoiVoiToanTuCong {
public static void main(String[] args) {
String chuoi1, chuoi2, chuoi3, ketQua;
chuoi1 = "Bài viết: ";
chuoi2 = "Tự học lập trình Java. ";
chuoi3 = "One for All";
ketQua = chuoi1 + chuoi2 + chuoi3;
System.out.println(ketQua);
}
}
Khi bạn chạy chương trình. Kết quả nhận được là:
Bài viết: Tự học lập trình Java. One for All
Chương I. Phần 4.3. Toán tử đơn phương
Video Tự học Java 12: Toán tử đơn phương trong Java
Toán tử đơn phương trong Java thực hiện hoạt động chỉ với một toán hạng.
+
Biểu diễn số dương (không cần thiết)
-
Đảo chiều thành số âm
++
Tăng lên 1 giá trị
--
Giảm đi 1 giá trị
!
Đảo ngược giá trị logic
Ví dụ về toán tử đơn phương:
class ViDuToanTuDonPhuong {
public static void main(String[] args) {
double so = 5.2, ketQua;
boolean giaTriSai = false;
System.out.println("Kết quả: +so = " + +so);
// Kết quả: +so = 5.2
System.out.println("Kết quả: -so = " + -so);
// Kết quả: -so = -5.2
// ++so tương đương với so = so + 1
System.out.println("Kết quả: so++ = " + ++so);
// Kết quả: so++ = 6.2
// --so tương đương với so = so - 1
System.out.println("Kết quả: --so = " + --so);
// Kết quả: --so = 5.2
System.out.println("Ket qua: !giaTriSai = " + !giaTriSai);
// Ket qua: !giaTriSai = true
}
}
Bạn cũng có thể sử dụng toán tử tăng ++
và giảm --
như là tiền tố hoặc hậu tố.
Ví dụ:
int a = 5;
++a // a = 6
a++ // a = 7
--a // a = 6
a-- // a = 5
Đến bây giờ, có thể bạn vẫn dễ hiểu bởi vì mọi thứ đang rất đơn giản.
Tuy nhiên, có một sự khác biệt giữa việc đặt toán tử tăng, giảm làm tiền tố hoặc hậu tố.
Ví dụ:
class ViDuToanTuDonPhuong2 {
public static void main(String[] args) {
int a = 5;
System.out.println(a++);
System.out.println(a);
System.out.println(++a);
System.out.println(a);
}
}
Khi bạn chạy chương trình. Kết quả nhận được là:
Các toán tử tăng giảm được đặt làm hậu tố như a++
sẽ được câu lệnh đánh giá giá trị trước sau đó mới tăng và lưu giá trị vào biến.
Chính vì thế, câu lệnh System.out.println(a++);
cho ra kết quả 5
.
Và câu lệnh sau đó, Sytem.out.println(a);
cho ra kết quả 6
.
Toán tử tăng giảm được đặt làm tiền tố có hành vi ngược lại.
Khi máy đọc câu lệnh, nó sẽ đọc từ trái sang phải.
Nó sẽ thấy và thực thi toán tử ++
, lưu giá trị mới vào biến a
trước. Sau đó câu mới đánh giá giá trị a
.
Chương I. Phần 4.5. Toán tử bằng và các toán tử quan hệ
Video tự học Java 13: Toán tử quan hệ trong Java
Toán tử bằng và toán tử quan hệ xác định mối quan hệ giữa hai toán hạng.
Nó sẽ kiểm tra nếu một toán hạng lớn hơn, nhỏ hơn, bằng nhau, không bằng nhau...
Tùy thuộc vào các mối quan hệ, nó sẽ cho kết quả hoặc là true
hoặc false
.
==
toán tử so sánh sự bằng nhau
!=
toán tử so sánh sự không bằng nhau
>
toán tử so sánh lớn hơn
<
toán tử so sánh nhỏ hơn
>=
toán tử so sánh lớn hơn hoặc bằng
<=
toán tử so sánh nhỏ hơn hoặc bằng
Cũng đơn giản như so sánh trong toán học thôi đúng không?
Chỉ khác một điều là toán tử so sánh bằng trong lập trình là ==
Bởi vì toán tử =
đã được sử dụng để làm toán tử gán, nên để so sánh sự bằng nhau chúng ta sử dụng ==
Ví dụ sử dụng toán tử quan hệ:
class ViDuToanTuQuanHe {
public static void main(String[] args) {
int so1 = 5, so2 = 6;
if (so1 > so2){
System.out.println("Số 1 lớn hơn Số 2.");
} else{
System.out.println("Số 2 lớn hơn hoặc bằng Số 1");
}
}
}
Khi bạn chạy chương trình. Kết quả nhận được là:
Số 2 lớn hơn hoặc bằng Số 1
Trong ví dụ trên, bởi vì if (so1 > so2)
đúng khi và chỉ khi so1
lớn hơn so2
.
Nếu so2
lớn hơn hoặc bằng so1
thì điều kiện trên sẽ bị đánh giá là false
.
Do đó, chương trình đã thực thi câu lệnh trong khối else
.
Nếu bạn chưa hiểu chương trình trên thì cũng không sao. Chúng ta sẽ cùng tìm hiểu kỹ hơn trong phần if...else
.
Còn bây giờ bạn chỉ cần hiểu là việc sử dụng toán tử quan hệ là để mong muốn kiểm tra xem biểu thức kiểm tra đó trả về giá trị true
hay false
.
Dựa vào kết quả trả về, chúng ta sẽ thực hiện các bước tiếp theo.
Ngoài các toán tử quan hệ, còn có một toán tử instanceof
được sử dụng để so sánh kiểu của một đối tượng với một kiểu xác định.
# Sử dụng toán tử instanceof để so sánh kiểu đối tượng
Như đã nói ở trên, toán tử instanceof
được sử dụng để so sánh kiểu của một đối tượng với một kiểu xác định nào đó.
Ví dụ:
class ViDuInstanceof {
public static void main(String[] args) {
String test = "abcxyz";
boolean result;
result = test instanceof String;
System.out.println(result);
}
}
Khi chúng ta chạy chương trình. Chúng ta nhận được kết quả là true
.
Bởi vì biến test là kiểu String
.
Lưu ý!
Bạn sẽ tìm hiểu thêm về cách toán tử instanceof
hoạt động khi bạn đã được học về Class và Object trong Java.
Chương I. Phần 4.6 Toán tử Logic
Video tự học Java 14: Toán tử Logic trong Java
Chúng ta có hai toán tử logic trong Java đó là, ||
và &&
Chúng được sử dụng trong biểu thức điều kiện boolean. Đây là cách chúng làm việc:
-
||
(toán tử OR): Trả về giá trị true
nếu ít nhất một điều kiện là true
-
&&
( toán tử AND): Trả về giá trị true
nếu tất cả điều kiện true
Ví dụ về toán tử logic:
class ViDuToanTuLogic {
public static void main(String[] args) {
int so1 = 1, so2 = 2, so3 = 9;
boolean ketQua;
// Ít nhất một biểu thức đúng để trả về giá trị true
ketQua = (so1 > so2) || (so3 > so1);
// Kết quả sẽ là true. Bởi vì so3 > so1
System.out.println(ketQua);
// Tất cả biểu thức phải đúng để trả về giá trị true
ketQua = (so1 > so2) && (so3 > so1);
// Kết quả nhận được là false vì biểu thức so1 > so2 bị sai
System.out.println(ketQua);
}
}
Khi chúng ta chạy chương trình, kết quả nhận được là:
Chú ý!
Toán tử logic thường xuyên được sử dụng trong vòng lặp.
Chương I. Phần 4.7. Toán tử Ternary
Video tự học Java 15: Toán tử Ternary trong Java
Toán tử Ternary (hay còn gọi là toán tử 3 ngôi) ? :
là cách viết tắt của câu lệnh if ... else
.
Cú pháp của toán tử Ternary là:
bienKetQua = BieuTHucDieuKien ? bieuThuc1 : bieuThuc2
Đây là cách toán tử ternary làm việc:
-
Nếu
BieuThucDieuKien
là true
thì thực hiện bieuThuc1
-
Ngược lại thì thực hiện
bieuThuc2
-
Sau đó giá trị trả về gán vào
bienKetQua
Ví dụ về toán tử Ternary:
class ViDuToanTuTernary {
public static void main(String[] args) {
int tuoi = 18;
String ketQua;
ketQua = (tuoi >= 18) ? "Được xem phim 18+" : "Chưa được xem phim 18+";
System.out.println(ketQua);
}
}
Khi chúng ta chạy chương trình. Kết quả nhận được là:
Được xem phim 18+
Còn khá nhiều toán tử khác. Tuy nhiên, bạn cũng chưa cần biết sâu lắm như toán tử Bitwise và toán tử Bit shift. (Thế nên bài này mình sẽ không nói tới)
Và một số toán tử toán học sẽ được giải thích khi chúng ta sử dụng đến chúng.
Còn bây giờ, bạn chỉ cần hiểu được những toán tử mình vừa giới thiệu ở trên là được thôi.
Chương I. Phần 5. Java Output và Input
Video tự học Java 16: Nhập / Xuất dữ liệu trong Java
Trong phần này bạn sẽ được học về cách hiển thị dữ liệu đầu ra (output) và lấy dữ liệu từ người dùng (input)
Chương I. Phần 5.1. Java Output
Bạn có thể hiện thị dữ liệu đầu ra với:
-
System.out.println()
-
System.out.print()
-
System.out.print()
Ví dụ:
class ViDuOutput1 {
public static void main(String[] args) {
System.out.println("Tự học Lập trình Java (One for All).");
}
}
Kết quả:
Tự học Lập trình Java (One for All)
Vậy,
# Sự khác biệt giữa println(), print() và printf() là gì?
-
print() là in chuỗi trong dấu nháy
-
println() tương tự như print() nhưng sau đó con trỏ sẽ bắt đầu ở dòng mới
-
printf() in chuỗi định dạng theo chuẩn đầu ra
Ví dụ về print() và println():
class ViDuOutput2 {
public static void main(String[] args) {
System.out.println("1. println ");
System.out.println("2. println ");
System.out.print("1. print ");
System.out.print("2. print");
}
}
Khi chạy chương trình, chúng ta nhận được kết quả:
1. println
2. println
1. print 2. print
> Bạn có thể đọc bài viết này để hiểu thêm về printf() trong Java.
Chúng ta hãy xem ví dụ khác:
class ViDuOutput3 {
public static void main(String[] args) {
Double soAm = -6.9;
System.out.println(5);
System.out.println(soAm);
}
}
Khi chạy chương trình, chúng ta nhận được kết quả:
5
-6.9
Như bạn thấy ở trên, chúng ta không sử dụng dấu nháy.
Ví dụ sử dụng toán tử + trong khi sử dụng println():
class ViDuOutput4 {
public static void main(String[] args) {
Double soAm = -6.9;
System.out.println("Tự học " + "Lập trình Java.");
System.out.println("Số = " + soAm);
}
}
Khi chạy chương trình, chúng ta nhận được kết quả:
Tự học Lập trình Java.
Số = -6.9
Bạn hãy nhìn vào dòng System.out.println("Tự học " + "Lập trình Java.");
Hai chuỗi này đã nối lại với nhau do chúng ta sử dụng toán tử +
Và dòng System.out.println("Số = " + soAm);
Ở đây vì soAm
không được đặt trong dấu nháy nên nó được đánh giá như một biến.
Sau đó, giá trị double
được trình biên dịch chuyển thành String
.
Cuối cùng nó được nối vào chuỗi "Số = "
và in tất cả ra màn hình.
Chương I. Phần 5.2. Java input
Có một số cách để có được đầu vào (input) từ người dùng trong Java.
Trong bài này, bạn sẽ học cách lấy đầu vào bằng cách sử dụng đối tượng Scanner
.
Để làm điều đó, bạn cần import class Scanner bằng cách sử dụng cú pháp:
import java.util.Scanner;
Sau đó, chúng ta sẽ tạo một đối tượng của lớp Scanner sẽ được sử dụng để nhận đầu vào từ người dùng.
Scanner input = new Scanner(System.in);
int soInput = input.nextInt();
Ví dụ lấy input từ người dùng:
import java.util.Scanner;
class ViDuInput1 {
public static void main(String[] args) {
// Tạo đối tượng input
Scanner input = new Scanner(System.in);
// Xuất ra thông báo cho người dùng nhập dữ liệu
System.out.print("Nhập vào một số nguyên: ");
// Lấy dữ liệu người dùng vừa nhập
// Gán vào biến soNguyen
int soNguyen = input.nextInt();
// In dữ liệu vừa lấy được ra màn hình
System.out.println("Số bạn vừa nhập là: " + soNguyen);
}
}
Khi chạy chương trình, output sẽ tương tự thế này:
Nhập vào một số nguyên: 6996
Số bạn vừa nhập là: 6996
Ở đây,
Sau khi đối tượng input
của class Scanner
được tạo. Phương thức nextInt()
của class Scanner
sẽ lấy dữ liệu do người dùng nhập vào.
Để lấy các giá trị long
, float
, double
và String
thì bạn cần sử dụng các phương thức tương ứng như: nextLong()
, nextFloat()
, nextDouble()
và next()
Ví dụ lấy dữ liệu kiểu float, double và String từ người dùng:
class ViDuInput2 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
// Lấy số float từ người dùng
System.out.print("Nhập số Float: ");
float soFloat = input.nextFloat();
System.out.println("Số Float bạn vừa nhập là: " + soFloat);
// Lấy số double từ người dùng
System.out.print("Nhập số double: ");
double soDouble = input.nextDouble();
System.out.println("Số Double bạn vừa nhập là: " + soDouble);
// Lấy chuỗi từ người dùng
System.out.print("Nhập vào một chuỗi: ");
String chuoiString = input.next();
System.out.println("Chuỗi bạn vừa nhập là: " + chuoiString);
}
}
Khi chạy chương trình, output sẽ tương tự thế này:
Nhập số Float: 6.9
Số Float bạn vừa nhập là: 6.9
Nhập số Double: -9.6
Số Double bạn vừa nhập là: -9.6
Nhập vào một chuỗi: Tôi tự học Lập trình Java
Chuỗi bạn vừa nhập là: Tôi tự học Lập trình Java
Như đã nói, có nhiều cách để lấy dữ liệu từ người dùng. Bạn có thể tham khảo thêm tại thảo luận trên Stack Overflow
Chương I. Phần 6. Biểu thức, Câu lệnh và Khối trong Java
Trong bài viết này, bạn sẽ tìm hiểu về Expression (Biểu thức), Statement (Câu lệnh) và Block (Khối) trong Java.
Chúng ta đã sử dụng chúng qua các ví dụ mà không cần hiểu gì quá nhiều.
Phần này chỉ giúp bạn nhận biết rõ hơn về chúng.
Chương I. Phần 6.1. Biểu thức trong Java
Biểu thức bao gồm các biến, toán tử, listerals và các cuộc gọi phương thức được ước tính thành một giá trị duy nhất.
Hãy làm một ví dụ:
int diemThi;
diemThi = 9;
Ở đây, diemThi = 9;
là một biểu thức trả về giá trị kiểu int
Double a = 6.9, b = 9.6, ketQua;
ketQua = a + b - 6.5;
Ở đây, ketQua = a + b - 6.5;
là biểu thức.
if (so1 == so2)
System.out.println("Số 1 bằng Số 2");
Ở đây, so1 == so2
là biểu thức trả về giá trị Boolean
.
Còn "Số 1 bằng Số 2"
là một biểu thức trả về String
.
Chương I. Phần 6.2. Câu lệnh trong Java
Câu lệnh trong Java là tất cả mọi thứ tạo nên một đơn vị hoàn chỉnh.
Ví dụ:
int diemSo = 6*9;
Ở đây, 6*9
là một biểu thức trả về giá trị 54
và int diemSo = 6*9;
là một câu lệnh.
Biểu thức làm một phần của câu lệnh.
Một vài biểu thức có thể biến thành câu lệnh với việc kết thúc bằng dấu ;
Nó gọi là câu lệnh biểu thức.
Ví dụ:
diemSo = 10;
# Câu lệnh khai báo
Câu lệnh khai báo được sử dụng để khai báo biến.
Double thueVat = 0.1;
Ở đây, câu lệnh này khai báo biến thueVat
và khởi tạo cho nó giá trị là 0.1
Ngoài ra, chúng ta còn có các câu lệnh điều khiển, phần này các bạn sẽ được học trong phần sau.
Chương I. Phần 6.3. Khối trong Java
Một khối trong Java (Java Block) là một nhóm các câu lệnh được bao bọc bởi cặp dấu ngoặc nhọn { }
Ví dụ:
class ViDuKhoi {
public static void main(String[] args) {
String ngoNguLapTrinh = "Java";
if (thuongHieu == "Java") { // Bắt đầu block
System.out.print("Học ");
System.out.print("Java ngay!");
} // Kết thúc block
}
}
Ở đây, chúng ta có 2 câu lệnh System.out.print("Học ");
và System.out.print("Java ngay!");
nằm bên trong một khối.
Lưu ý!
Một khối cũng có thể không có câu lệnh nào.
Đến đây, bạn đã cơ bản làm quen bước đầu với ngôn ngữ lập trình Java.
Phần tiếp theo, chúng ta sẽ đi sâu hơn về một phần rất quan trọng trong lập trình, không chỉ riêng ngôn ngữ Java.
CHƯƠNG II: HỌC LẬP TRÌNH CẤU TRÚC ĐIỀU KHIỂN TRONG JAVA
Trong phần này, bạn sẽ được học về các cấu trúc điều khiển trong lập trình Java như:
-
Cấu trúc if...else
-
Câu lệnh switch
-
Vòng lặp for
-
Vòng lặp for...each
-
Vòng lặp while
-
Câu lệnh break
-
Câu lệnh continue
Chúng ta sẽ đi ngay vào phần đầu tiên.
Chương II. Phần 1: Cấu trúc if...else trong Java
Video tự học Java 17: Cấu trúc IF ELSE trong Java
Trong phần hướng dẫn tự học lập trình Java này, bạn sẽ học sử dụng hai câu lệnh if và if...else để kiểm soát dòng chảy của chương trình.
Trong lập trình, chúng ta thường chỉ muốn thực thi một phần code nào đó nếu điều kiện kiểm tra là đúng hoặc sai (true hay false).
Nhưng trường hợp như thế này chúng ta sẽ cần cấu trúc điều khiển.
Chương II. Phần 1.1. Câu lệnh if
Cú pháp của câu lệnh if trong Java là:
if (BieuThucDieuKien) {
// Các câu lệnh ...
}
Trong đó:
-
BieuThucDieuKien
: Là biểu thức logic sẽ trả về giá trị true
hoặc false
-
Nếu
BieuThucDieuKien
trả về giá trị là true
thì các câu lệnh bên trong khối được thực thi.
-
Nếu
BieuThucDieuKien
trả về giá trị là false
thì chương trình sẽ bỏ qua các câu lệnh trong khối.
Lưu đồ thuật toán if trong Java
Ví dụ về câu lệnh if trong Java:
class ViDuCauLenhIf {
public static void main(String[] args) {
int so = 10;
if (so > 0) {
System.out.println("Số Dương");
}
System.out.println("Câu lệnh này luôn được thực thi.");
}
}
Khi chạy chương trình, bạn nhận được kết quả là:
Số Dương
Câu lệnh này luôn được thực thi.
Như bạn thấy trong ví dụ trên:
-
so
được gán bằng 10 nên lớn hơn 0. Do đó biểu thức so > 0
trả về giá trị true
-
Vậy nên, code bên trong khối if được thực thi.
Bây giờ, hãy thử đổi giá trị so
thành số âm (-10
) và chạy lại chương trình xem sao.
Kết quả trong trường hợp này sẽ là:
Câu lệnh này luôn được thực thi.
Đúng như bạn nghĩ,
-
Khi
so = -10
thì nhỏ hơn 0. Do đó, biểu thức so > 0
trả về giá trị false
.
-
Máy tính sẽ bỏ qua các câu lệnh bên trong khối if.
-
Câu lệnh
System.out.println("Câu lệnh này luôn được thực thi.");
không nằm trong khối if nên vẫn được thực thi bình thường
Chương II. Phần 1.2. Câu lệnh if...else trong Java
Câu lệnh if chỉ định máy tính thực thi các câu lệnh bên trong nó nếu điều kiện là true
.
Tuy nhiên, câu lệnh if
cũng có thể có khối else
.
Các câu lệnh trong khối else
sẽ được thực thi khi điều kiện được đánh giá là false
.
Cú pháp câu lệnh if...else trong Java:
if (bieuThucDieuKien) {
// Code thực thi khi biểu thức biểu thức điều kiện đúng
} else {
// Code thực thi khi biểu thức biểu thức điều kiện sai
}
Lưu đồ thuật toán if...else trong Java
Ví dụ sử dụng câu lệnh if...else trong Java:
class ViDuCauLenhIfElse {
public static void main(String[] args) {
int so = 10;
if (so > 0) {
System.out.println("Số Dương.");
}
else {
System.out.println("Số Âm.");
}
System.out.println("Câu lệnh này luôn được thực thi.");
}
}
Khi chạy chương trình, kết quả nhận được là:
Số Dương
Câu lệnh này luôn được thực thi.
Chương trình được thực thi tương tự như ví dụ trên.
Bây giờ, hãy đổi lại giá trị của so
thành -10
và chạy lại chương trình.
Kết quả nhận được là:
Số Âm
Câu lệnh này luôn được thực thi.
Chương II. Phần 1.3. Câu lệnh if...else...if trong Java
Video tự học Java 18: Cấu trúc IF ELSE IF trong Java
Trong Java, nếu bạn muốn đánh giá điều kiện và chỉ thực thi một block code trong số nhiều block.
Trong trường hợp này bạn có thể sử dụng câu lệnh if else if.
Cú pháp câu lệnh if....else...if trong Java:
if (BieuThucDieuKien1) {
// codes
}
else if(BieuThucDieuKien2) {
// codes
}
else if (BieuThucDieuKien3) {
// codes
}
.
.
else {
// codes
}
Câu lệnh if được đánh giá từ trên xuống dưới.
Nếu biểu thức điều kiện nào là true
, máy tính thực thi code bên trong khối đó. Sau đó nó thoát ra khỏi cấu trúc if else if.
Nếu tất cả biểu thức điều kiện là false
, code bên trong khối else sẽ được thực thi.
Ví dụ sử dụng câu lệnh if...else...if trong Java:
class ViDuIfElseIf {
public static void main(String[] args) {
int so = 0;
if (so > 0) {
System.out.println("Số Dương.");
}
else if (so < 0) {
System.out.println("Số Âm.");
}
else {
System.out.println("Số 0.");
}
}
}
Khi chạy chương trình, kết quả nhận được là:
Số 0.
Chương II. Phần 1.4. Câu lệnh if else lồng nhau trong Java
Video tự học Java 19: Cấu trúc NESTED IF trong Java
Trong Java, bạn hoàn toàn có thể đặc câu lệnh if...else
bên trong câu lệnh if...else
khác.
> LƯU Ý: Code trong Video sẽ không giống hoàn toàn trong bài viết bởi mình mong muốn bạn xem video thì học được logic. Sau đó đọc ví dụ trong bài viết để kiểm chứng kiến thức vừa học. Nếu hiểu -> Đó là một thành công.
Nó được gọi là câu lệnh if...else
lồng nhau (Hay là NESTED IF)
Ví dụ câu lệnh if else if lồng nhau để tìm số lớn nhất trong 3 số nguyên cho trước:
class ViDuIfElseLongNhau {
public static void main(String[] args) {
int a = 1, b = 5, c = 8, max;
if (a >= b) {
if (a >= c) {
max = a;
} else {
max = c;
}
} else {
if (b >= c) {
max = b;
} else {
max = c;
}
}
System.out.println("Số lớn nhất là: " + max);
}
}
Khi chạy chương trình, kết quả nhận được là:
Số lớn nhất là: 8
Chương II. Phần 2: Câu lệnh switch trong Java
Video tự học Java 20: Cấu trúc SWITCH CASE trong Java
Trong phần này, bạn sẽ được học cách sử dụng câu lệnh switch trong Java. (Hay còn gọi là Cấu trúc Switch Case)
Câu lệnh switch
có thể được sử dụng để thay thế cho câu lệnh if...else...if
để đạt được tính dễ đọc hơn.
Cú pháp câu lệnh switch trong Java:
switch (bien / BieuThucDieuKien) {
case value1:
// statements
break;
case value2:
// statements
break;
.. .. ...
.. .. ...
default:
// statements
}
Câu lệnh switch
đánh giá biểu thức (chủ yếu được sử dụng cho biến) và so sánh với giá trị của mỗi case
.
Điểm khác của switch so với câu lệnh if...else...if là:
-
Câu lệnh
switch
sẽ thực hiện tất cả các câu lệnh nếu bất kỳ case
nào được khớp.
-
Còn câu lệnh
if...else...if
sẽ thoát hoàn toàn sau khi thực hiện câu lệnh với biểu thức được khớp.
Do đó, nếu đã có một case được khớp, chương trình đã thực thi tất cả câu lệnh bên trong khối đó xong.
Bạn muốn thoát hoàn toàn khỏi câu lệnh switch
cũng như không cần đánh giá các case tiếp theo, chúng ta sẽ sử dụng câu lệnh break
.
Ngoài ra, khối default
sẽ được thực thi nếu không có case nào được khớp.
Lưu đồ thuật toán câu lệnh switch trong Java
Và nên nhớ, câu lệnh switch chỉ hoạt động với:
-
Dữ liệu nguyên thủy:
byte
, short
, char
và int
-
và
Character
, Byte
, Short
và Integer
Ví dụ sử dụng câu lệnh switch trong Java:
class ViDuCauLenhSwitch {
public static void main(String[] args) {
int soChoNgoi = 4;
String loaiXe;
switch (soChoNgoi) {
case 2:
loaiXe = "Xe Thể thao";
break;
case 4:
loaiXe = "Sedan";
break;
case 7:
loaiXe = "SUV";
break;
default:
loaiXe = "Xe khác";
break;
}
System.out.println(loaiXe);
}
}
Khi chạy chương trình, kết quả nhận được là:
Sedan
Một ví dụ khác về cách sử dụng câu lệnh switch trong Java:
Chúng ta sẽ sử dụng switch để thực hiện Cộng +
, trừ -
, Nhân *
, Chia /
hai số cho nhau. Hai số này này lấy từ người dùng.
Và, người dùng có thể quyết định lựa chọn phép tính nào.
Lưu ý!
Bạn sẽ cần đến kiến thức trong phần Java input đã học ở trên.
import java.util.Scanner;
class MayTinhDonGian {
public static void main(String[] args) {
char phepTinh;
Double so1, so2, ketQua;
Scanner scanner = new Scanner(System.in);
System.out.print("Chọn phép tính (+, -, * hoặc /): ");
phepTinh = scanner.next().charAt(0);
System.out.print("Nhập Số 1 và Số 2: ");
so1 = scanner.nextDouble();
so2 = scanner.nextDouble();
switch (phepTinh) {
case '+':
ketQua = so1 + so2;
System.out.print(so1 + " + " + so2 + " = " + ketQua);
break;
case '-':
ketQua = so1 - so2;
System.out.print(so1 + " - " + so2 + " = " + ketQua);
break;
case '*':
ketQua = so1 * so2;
System.out.print(so1 + " * " + so2 + " = " + ketQua);
break;
case '/':
ketQua = so1 / so2;
System.out.print(so1 + " / " + so2 + " = " + ketQua);
break;
default:
System.out.println("Phép tính không hợp lệ");
break;
}
}
}
Khi bạn chạy chương trình, out sẽ tương tự như thế này:
Chọn phép tính (+, -, * hoặc /): *
Nhập Số 1 và Số 2: 6
9
6 * 9 = 54
Chương II. Phần 3: Vòng lặp for trong Java
Vòng lặp được sử dụng trong lập trình để lặp lại một hành động (khối code) cụ thể.
Trong phần hướng dẫn tự học Java này, bạn sẽ tìm hiểu để tạo ra một vòng lặp trong lập trình.
Chương II. Phần 3.1. Vòng lặp for là gì?
Vòng lặp for là một vòng lặp đơn giản, thường xuyên được sử dụng trong lập trình Java.
Vòng lặp for thực hiện một khối code cho đến khi điều kiện đánh giá thành false.
Cú pháp của vòng lặp for trong Java:
for (KhoiTaoBienDem; BieuThucDieuKien; CapNhatBienDem){
// Code thực thi hành động nào đó
}
Để hiểu vòng lặp for trong Java hoạt động như thế nào, chúng ta hãy xem lưu đồ thuật toán sau:
Lưu đồ thuật toán vòng lặp for trong Java
Ví dụ về vòng lặp for trong Java:
// Chương trình in ra số từ 1 đến 5
class ViDuVongLapFor {
public static void main(String[] args) {
for (int i = 1; i <= 5; ++i) {
System.out.println("Số " + i);
}
}
}
Khi chạy chương trình, chúng ta nhận được kết quả:
Số 1
Số 2
Số 3
Số 4
Số 5
Giải thích chương trình vòng lặp for:
-
Chúng ta khởi tạo giá trị
i = 1
-
Thực hiện đánh giá biểu thức
i <= 5
. Nếu true
thì thực hiện câu lệnh println()
bên trong.
-
Sau đó, update điều kiện, thực thi
++i
.
-
Bây giờ,
i
có giá trị là 2
, i
vẫn nhỏ hơn hoặc bằng 5
.
-
Tiếp tục thực hiện câu lệnh
println()
-
...
-
Khi
i
tăng lên thành 6
. Biểu thức i <= 5
trả về giá trị false
thì thoát khỏi vòng lặp.
Chúng ta sẽ tiếp tục thực hiện một vòng lặp nữa để hiểu sâu hơn về vòng lặp for trong Java:
// Chương trình cộng các số từ 1 đến 100
class ViDuVongLapFor2 {
public static void main(String[] args) {
int tong = 0;
for (int i = 1; i <= 100; ++i) {
tong += i; // sum = sum + i
}
System.out.println("Tổng = " + tong);
}
}
Khi chạy chương trình, chúng ta nhận được kết quả:
Tổng = 5050
Ở đây:
-
Biến
tong
được khởi tạo giá trị 0
-
Sau đó trong vòng lặp
for
. Khởi tạo biến đếm i = 0
.
-
Kiểm tra biểu thức
i <= 100
. Nếu true
, thì thực hiện cộng i
vào biến tong
.
-
Tiếp tục, tăng giá trị
i
thêm 1
. Tiếp tục đánh giá biểu thức i <= 100
-
Khi
i
lớn hơn 100
. Thoát khỏi vòng lặp và in giá trị của tong
ra màn hình
# Vòng lặp for vô cực
Nếu biểu thức kiểm tra không bao giờ false, vòng lặp for sẽ chạy mãi mãi. Đây gọi là vòng lặp Vô cực (vô hạn, vĩnh cửu) (Nghe 'Vô cực" ngầu hơn nhỉ =)) )
Hãy cùng làm thử một ví dụ:
// Vòng lặp vô cực
class ViDuVongLapForVoCuc {
public static void main(String[] args) {
for (int i = 1; i <= 10; --i) {
System.out.println("Tự học JAVA Không phải JAV");
}
}
}
Khi chúng ta đặt biểu thức kiểm tra là i <= 10
, nhưng khi update điều kiện thì chúng ta lại đặt là --i
.
Điều này khiến i <= 10
luôn luôn đúng (true
).
Dẫn tới vòng lặp for này chạy không bao giờ nghỉ.
Lưu ý!
Biểu thức khởi tạo, biểu thức kiểm tra, biểu thức update đều là tùy chọn, có thể có hoặc không.
Vì thế, đây cũng là một vòng lặp vô cực.
Ok, vậy là bạn đã hiểu về vòng lặp for trong Java.
Bây giờ, hãy cùng tìm hiểu về vòng lặp for - each trong Java.
Chương II. Phần 4: Vòng lặp for each trong Java
Vòng lặp for each trong Java là vòng lặp chuyên được sử dụng để lặp qua các mảng, tập hợp.
Vòng lặp for each còn được gọi là Vòng lặp for tăng cường.
Đây là một ví dụ để lặp qua các phần tử của một mảng bằng cách sử dụng vòng lặp for tiêu chuẩn:
class ViDuVongLapForTieuChuan {
public static void main(String[] args) {
char[] mangKyTu = {'t', 'u', 'h', 'o', 'c'};
for (int i = 0; i < mangKyTu.length; ++ i) {
System.out.println(mangKyTu[i]);
}
}
}
Và, thay vì sử dụng vòng lặp for tiêu chuẩn.
Java cung cấp một vòng lặp for each để chuyên làm những việc tương tự như thế này.
class ViDuVongLapForEach {
public static void main(String[] args) {
char[] mangKyTu = {'t', 'u', 'h', 'o', 'c'};
// Vòng lặp for each
for (char kytu: mangKyTu) {
System.out.println(kytu);
}
}
}
Khi chạy cả hai vòng lặp này, chúng ta được một kết quả tương tự:
t
u
h
o
c
Việc sử dụng vòng lặp for each dễ viết hơn và làm cho code của bạn dễ đọc hơn.
Do đó, người ta khuyến nghị sử dụng vòng lặp for each hơn vòng lặp for tiêu chuẩn (bất cứ khi nào có thể)
Chương II. Phần 4.1. Cú pháp vòng lặp for each trong Java
Ở trên chúng ta đã thử làm ví dụ bằng vòng lặp for each. Bây giờ, hãy phân tích cú pháp của vòng lặp for each xem nào:
for (data_type item: collection){
}
Trong đó:
-
data_type
là kiểu dữ liệu của item
-
item
là tên đại diện cho từng phần tử trong collection (bạn có thể đặt tùy ý)
-
collection
là tập hợp hoặc tên của mảng bạn đang muốn lặp qua nó
Chương II. Phần 4.2. Ví dụ vòng lặp for each trong Java
Để bạn hiểu hơn cách hoạt động của vòng lặp for each hãy cùng thực hiện thêm các ví dụ.
Đây là một chương trình tính tổng của các phần tử trong mảng.
class TinhTongPhanTuTrongMang {
public static void main(String[] args) {
int[] mangSoNguyen = {2, 0, 2, 0, 0, 3};
int tong = 0;
for (int soNguyen: mangSoNguyen) {
tong += soNguyen;
}
System.out.println("Tổng = " + tong);
}
}
Khi chạy chương trình, kết quả nhận được là:
Tổng = 7
Lưu ý!
tong += soNguyen
tương đương với tong = tong + soNguyen
.
Giải thích chương trình:
-
Lặp lần 1:
tong = 0
, soNguyen = 2
. Thực hiện tong += soNguyen
. Lúc này, tong = 2
.
-
Lặp lần 2:
tong = 2
, soNguyen = 0. Thực hiện tong += soNguyen
. Lúc này, tong = 2
.
-
Lặp lần 3:
tong = 2
, soNguyen = 2
. Thực hiện tong += soNguyen
. Lúc này, tong = 4
.
-
Lặp lần 4:
tong = 4
, soNguyen = 0
. Thực hiện tong += soNguyen
. Lúc này, tong = 4
.
-
Lặp lần 5:
tong = 4
, soNguyen = 0
. Thực hiện tong += soNguyen
. Lúc này, tong = 4
.
-
Lặp lần 6:
tong = 4
, soNguyen = 3
. Thực hiện tong += soNguyen
. Lúc này, tong = 7
.
-
Thực hiện in giá trị tong ra màn hình.
Như vậy, bạn đã hiểu các thực hiện lặp qua mảng bằng vòng lặp for each chưa?
Chương II. Phần 5: Vòng lặp while trong Java
Vòng lặp while cũng được sử dụng lặp lại một khối code cụ thể. Trong phần này, bạn sẽ học cách tạo vòng lặp while và do ... while trong Java.
Vòng lặp while sẽ lặp lại một khối code cụ thể cho đến khi điều kiện nhất định được thỏa mãn (biểu thức kiểm tra trở thành false).
Vòng lặp while thường được sử dụng nếu bạn không biết trước số lần lặp, ví dụ:
-
Tiếp tục hiển thị thêm các bài viết khi người dùng còn lăn chuột trên newsfeed.
-
Tiếp tục đếm thời gian (đồng hồ tính giờ) khi người dùng chưa bấm dừng lại.
-
....
Chương II. Phần 5.1. Cú pháp vòng lặp while trong Java
Cú pháp của vòng lặp while trong Java là:
while (bieuThucDieuKien) {
// Code thực thi cái gì đó
}
Trong đó:
-
bieuThucDieuKien
là biểu thức điều kiện trả về giá trị boolean (true hay false)
Chương II. Phần 5.2. Vòng lặp while hoạt động thế nào?
Dưới đây là cách vòng lặp while trong Java hoạt động.
-
Biểu thức
bieuThucDieuKien
là biểu thức boolean trả về giá trị true
hoặc false
.
-
Nếu
bieuThucDieuKien
được đánh giá là true
. Code bên trong vòng lặp sẽ được thực thi một lần.
-
Tiếp tục đánh giá
bieuThucDieuKien
lần nữa.
-
Nếu
bieuThucDieuKien
được đánh giá là true
. Code bên trong vòng lặp sẽ được thực thi một lần.
-
Khi
bieuThucDieuKien
trả về giá trị false
. Thoát khỏi vòng lặp
Xem lưu đồ thuật toán vòng lặp while để hiểu dòng chảy của vòng lặp.
Lưu đồ thuật toán vòng lặp while
Chương II. Phần 5.3. Ví dụ về vòng lặp while trong Java.
Để hiểu cách vòng lặp while hoạt động, hãy cùng xem ví dụ sau:
// Chương trình in ra số từ 1 đến 10
class ViDuVongLapWhile {
public static void main(String[] args) {
int i = 1;
while (i <= 10) {
System.out.println("Số: " + i);
++i;
}
}
}
Khi chạy chương trình, kết quả bạn nhận được là:
Số: 1
Số: 2
Số: 3
Số: 4
Số: 5
Số: 6
Số: 7
Số: 8
Số: 9
Số: 10
Chú ý, câu lệnh ++i
bên trong thân vòng lặp while.
Sau lần lặp thứ 10, biến i
sẽ có giá trị 11
.
Khi đó, kiểm tra biểu thức điều kiện i <=10
trả về giá trị false
.
Điều này dẫn tới việc chấm dứt vòng lặp while.
Chúng ta cũng có thể vận dụng vòng lặp while để xử lý bài toán cộng từ 1 đến 100 như sau:
// Chương trình cộng 1 đến 100 sử dụng vòng lặp while
class CongTu1Den100 {
public static void main(String[] args) {
int tong = 0, i = 100;
while (i != 0) {
tong += i; // Tương đương với tong = tong + i
--i;
}
System.out.println("Tổng = " + tong);
}
}
Khi chạy chương trình, kết quả nhận được là:
Tổng = 5050
* Kết quả giống như sử dụng vòng lặp for đúng không?
> Thuật toán rất quan trọng, nó là nền tảng để giúp bạn nắm vững ngôn ngữ, cách vận dụng ngôn ngữ hơn. Tham khảo hướng dẫn HỌC THUẬT TOÁN với JAVA để tìm hiểu thêm các thuật toán phổ biến bạn nhé.
Chương II. Phần 5.4. Vòng lặp do while trong Java
Vòng lặp do while trong Java cũng lặp lại một khối code cụ thể cho đến khi thỏa mãn điều kiện nào đó.
Vòng lặp do while hoạt động tương tự như vòng lặp while.
Tuy nhiên, điểm khác biệt là vòng lặp do while sẽ thực hiện khối code ít nhất 1 lần, cho dù điều kiện đúng hay sai.
Nghĩa là, lần lặp đầu tiên, khối code sẽ được thực thi, sau đó mới kiểm tra điều kiện.
Cú pháp vòng lặp do while trong Java là:
do {
// Code thực thi cái gì đó
} while (bieuThucDieuKien);
Trong đó:
-
bieuThucDieuKien
là biểu thức điều kiện boolean trả về giá trị true
hoặc false
.
Chương II. Phần 5.5. Vòng lặp do while hoạt động thế nào?
Đây là cách vòng lặp do while trong Java hoạt động:
-
Thực hiện khối code bên trong vòng lặp 1 lần
-
Đánh giá biểu thức
bieuThucDieuKien
-
Nếu
bieuThucDieuKien
trả về giá trị true
. Tiêp tục thực hiện khối code trong block do
-
Tiếp tục đánh giá biểu thức
bieuThucDieuKien
. Nếu true thì thực hiện khối code trong block do
-
Nếu
bieuThucDieuKien
trả về giá trị false
. Chấm dứt vòng lặp
Xem lưu đồ thuật toán vòng lặp do while để hiểu dòng chảy của vòng lặp.
Lưu đồ thuật toán vòng lặp do while
Để hiểu sâu hơn về cách vòng lặp do while hoạt động, hãy cùng làm ví dụ sau:
Chương trình này tính tổng các số được người dùng nhập vào cho đến khi người dùng nhập vào 0.
Đẩy lấy được dữ liệu đầu vào (input) từ người dùng, hãy xem lại phần Java input ở trên.
Code chương trình:
import java.util.Scanner;
class TinhTong {
public static void main(String[] args) {
Double so, tong = 0.0;
Scanner input = new Scanner(System.in);
// Vòng lặp do while
do {
System.out.print("Nhập vào một số: ");
so = input.nextDouble();
tong += so;
} while (so != 0.0);
System.out.println("Tổng = " + tong);
}
}
Khi chạy chương trình, kết quả nhận được là:
Nhập vào một số: 2.5
Nhập vào một số: 1.0
Nhập vào một số: 2.5
Nhập vào một số: 5.0
Nhập vào một số: 0.0
Tổng = 11.0
Chương II. Phần 5.6. Vòng lặp while vô cực
Tương tự như ở trong vòng lặp for vô cực.
Vòng lặp while vô cực (vô hạn) sẽ liên tục thực hiện khối code nào đó nếu điều kiện kiểm tra luôn đúng.
Ví dụ vòng lặp while vô cực:
class VongLapWhileVoCuc {
public static void main(String[] args) {
int i = 100;
// Vòng lặp while vô cực
while (i == 100) {
System.out.println("Tự học Lập trình Java");
}
}
}
Ở đây, điều kiện i == 100
luôn đúng (true
), thế nên câu lệnh System.ou.println()...
sẽ được thực thi liên tục, không dừng lại.
Lưu ý!
Chương trình lặp vô hạn có thể làm treo máy của bạn.
Vì thế hãy cẫn thận khi lập trình.
Chương II. Phần 6. Câu lệnh break trong Java
Trong phần câu lệnh switch bạn đã nhìn thấy giải thích qua về các hoạt động của câu lệnh break.
Phần này, chúng ta sẽ tìm hiểu kỹ hơn một chút về câu lệnh break.
Câu lệnh break
thường được sử dụng để chấm dứt vòng lặp ngay lập tức mà không cần đợi kiểm tra biểu thức.
Chương trình sẽ chuyển sang câu lệnh tiếp theo sau vòng lặp ngay lập tức.
Cú pháp của câu lệnh break:
break;
Chương II. Phần 6.1. Ví dụ về cách sử dụng câu lệnh break trong Java
Để hiểu cách hoạt động của câu lệnh break trong Java, hãy cùng làm một vài ví dụ:
// Ví dụ câu lệnh break
class ViDuBreak1 {
public static void main(String[] args) {
for (int i = 1; i <= 10; ++i) {
// Chấm dứt vòng lặp nếu i bằng 5
if (i == 5) {
break;
}
System.out.println(i);
}
}
}
Khi chạy chương trình, kết quả nhận được là:
1
2
3
4
Như bạn thấy, biểu thức kiểm tra là i <= 10
. Khi i == 5
vẫn thỏa mãn biểu thức kiểm tra của vòng lặp.
Tuy nhiên, câu lệnh break;
đã chấm dứt vòng lặp tại đây.
Dẫn tới, 5, 6, 7, 8, 9, 10 không được in ra màn hình.
Hãy cùng làm một ví dụ khác để hiểu về câu lệnh break.
Chương trình lấy các số được người dùng nhập vào để tính tổng cho đến khi người dùng nhập số âm.
* Bạn nên xem lại cách lấy dữ liệu từ người dùng trong phần Java input.
Code chương trình:
import java.util.Scanner;
class TinhTongSoDuong {
public static void main(String[] args) {
// Khởi tạo biến
Double so, tong = 0.0;
Scanner input = new Scanner(System.in);
// Sử dụng vòng lặp while để tính tổng
while (true) {
System.out.print("Nhập vào một số: ");
so = input.nextDouble();
/* Sử dụng break để chấm dứt vòng lặp
khi người dùng nhập vào số âm */
if (so < 0.0) {
break;
}
tong += so;
}
System.out.println("Tổng = " + tong);
}
}
Khi chạy chương trình, output sẽ tương tự như thế này:
Nhập vào một số: 1.0
Nhập vào một số: 2.5
Nhập vào một số: 1.5
Nhập vào một số: 3.5
Nhập vào một số: -1.0
Tổng = 8.5
Trong chương trình trên, biểu thức điều kiện luôn đúng.
Lẽ ra vòng lặp while sẽ lặp lại vô hạn.
Tuy nhiên, chúng ta lại thiết lập khi người dùng nhập vào số âm thì thực hiện break.
Do đó, nếu muốn dừng tính tổng, người dùng chỉ cần nhập số âm.
Sau đó, tổng các số đã nhập (trừ số âm) sẽ được in ra màn hình.
Ngoài ra, chúng ta hoàn toàn có thể đặt vòng lặp bên trong vòng lặp (Vòng lặp lồng nhau)
Và ở vòng lặp bên trong chúng ta đặt câu lệnh break;
Khi câu lệnh break được kích hoạt, nó sẽ không thoát hoàn toàn khỏi vòng lặp, mà chỉ dừng vòng lăp bên trong.
Và tiếp tục chạy vòng lặp bên ngoài, như ví dụ sau:
class VongLapLongNhau {
public static void main (String[] args) {
for (int i = 1; i <= 3; ++i) {
System.out.println("Chương " + i);
for (int j = 1; i <= 5; ++j) {
System.out.println("Phần " + j);
if (j == 3) {
System.out.println("Thoát vòng lặp bên trong");
break;
}
}
}
System.out.println("Kết thúc chương trình lặp");
}
}
Ở vòng lặp for bên trong, chúng ta đặt biểu thức điều kiện là j <= 5.
Khi j được tăng lên thành 3 (vẫn nhỏ hơn 5).
Tuy nhiên, khi j == 3 thì chúng ta đặt câu lệnh break và thoát khỏi vòng lặp for này.
Vòng lặp for bên ngoài tiếp tục tăng giá trị i thêm 1.
Sau đó, tiếp tục chạy lại vòng lặp for bên trong.
Do đó, kết quả bạn nhận được sẽ là như thế này:
Chương 1
Phần 1
Phần 2
Phần 3
Thoát vòng lặp for bên trong
Chương 2
Phần 1
Phần 2
Phần 3
Thoát vòng lặp for bên trong
Chương 3
Phần 1
Phần 2
Phần 3
Thoát vòng lặp for bên trong
Kết thúc chương trình lặp
Còn có một cách bạn có thể thoát khỏi vòng lặp theo chỉ định bằng cách đặt label
cho vòng lặp.
Sau đó, chúng ta sẽ sử dụng break label;
tương tự như ví dụ sau:
class ViDuBreakLabel {
public static void main(String[] args) {
vongLap1:
for( int i = 1; i < 5; i++) {
vongLap2:
for(int j = 1; j < 3; j ++ ) {
System.out.println("i = " + i + " và j = " +j);
if ( i == 2) {
break vongLap1;
}
}
}
}
}
Khi chạy chương trình, kết quả nhận được là:
i = 1 và j = 1
i = 1 và j = 2
i = 2 và j = 1
Như bạn thấy, khi i
bằng 2
, chương trình vẫn in ra i = 2 và j = 1
.
Tuy nhiên, sau khi in, điều kiện i == 2
sẽ được đánh giá và trả lại giá trị true
. Lúc này, câu lệnh break vongLap1;
được kích hoạt.
Và chương trình lặp thoát hoàn toàn, không giống như chỉ thoát vòng lặp bên trong như ở ví dụ trên.
Và nếu, chúng ta thực hiện đánh giá điều kiện i == 2
trước, như thế này:
class ViDuBreakLabe2 {
public static void main(String[] args) {
vongLap1:
for( int i = 1; i < 5; i++) {
vongLap2:
for(int j = 1; j < 3; j ++ ) {
if ( i == 2) {
break vongLap1;
}
System.out.println("i = " + i + " và j = " +j);
}
}
}
}
Kết quả nhận được là:
i = 1 và j = 1
i = 2 và j = 2
Ok, cho đến bây giờ, bạn đã hiểu về câu lệnh break trong Java chưa?
Chương II. Phần 7. Câu lệnh continue trong Java
Trong phần này, bạn sẽ học cách sử dụng câu lệnh continue trong Java.
Câu lệnh continue sẽ cho phép chương trình bỏ qua lần lặp hiện tại của một vòng lặp.
Khi câu lệnh continue được thực thi, trình điều khiển chương trình nhảy đến cuối vòng lặp.
Sau đó, biểu thức kiểm tra điều khiển vòng lặp được đánh giá.
Trong trường hợp của vòng lặp for, câu lệnh cập nhật được thực thi trước khi biểu thức kiểm tra được đánh giá.
Chương II. Phần 7.1. Ví dụ về cách sử dụng continue trong lập trình Java.
Để hiểu cách hoạt động của câu lệnh coutinue, chúng ta hãy cùng làm một vài ví dụ.
// Chương trình ví dụ về câu lệnh continue trong Java
class ViDuContinue {
public static void main(String[] args) {
// Lặp từ 1 đến 10
for (int i = 1; i <= 10; ++i) {
// Bỏ qua 5, 6, 7, 8
if (i > 4 && i < 9) {
continue;
}
// In ra số từ 1 đến 10
System.out.println(i);
}
}
}
Khi chạy chương trình trên, chúng ta có được kết quả:
1
2
3
4
9
10
Khối if trong vòng lặp for có biểu thức điều kiện là i > 4 && i < 9
.
Lưu ý!
Trong vòng lặp for, trước khi đánh giá biểu thức điều kiện, chương trình đã thực hiện ++i
Do đó, khi i
bằng 5
, 6
, 7
, 8
sẽ bị trình điều khiển chương trình bỏ qua.
Tiếp đó, i = 9
, chương trình sẽ tiếp tục in giá trị của i
ra màn hình.
Tiếp tục, chúng ta cùng đên với một ví dụ khác về câu lệnh continue
Chương trình tính tổng tối đa 5 số được nhập vào bởi người dùng. Nếu người dùng nhập số âm hoặc số 0, nó sẽ không được cộng vào tổng.
* Nếu bạn quên cách lấy dữ liêu của người dùng thì hãy đọc lại phần Java input
import java.util.Scanner;
class TinhTongSoDuong {
public static void main(String[] args) {
Double so, tong = 0.0;
Scanner input = new Scanner(System.in);
for (int i = 1; i < 6; ++i) {
System.out.print("Nhập vào một số Dương: ");
so = input.nextDouble();
/* Nếu người dùng nhập vào 0 hoặc số âm
thì bỏ qua */
if (so <= 0.0) {
continue;
}
tong += so;
}
System.out.println("Tổng = " + tong);
}
}
Khi chạy chương trình, output sẽ tương tự như:
Nhập vào một số Dương: 1
Nhập vào một số Dương: -1
Nhập vào một số Dương: 2
Nhập vào một số Dương: 0
Nhập vào một số DƯơng: 2.0
Tổng = 5
Bài tập: Bạn thử sử dụng câu lệnh continue trong vòng lặp lồng nhau thử xem. Nó hoạt động như thế nào?
Chương II. Phần 7.2. Câu lệnh continue label trong Java
Bạn cũng có thể sử dụng câu lệnh continue trong Java để bỏ qua vòng lặp hiện tại và tiếp tục thực hiện một vòng lặp được chỉ định (label).
Cách sử dụng cũng giống như câu lệnh break.
Hãy cùng xem ví dụ về continue label trong Java:
// Ví dụ sử dụng continue label trong Java
class ViDuContinueLabel {
public static void main(String[] args) {
vongLapFor1:
for (int i = 1; i <= 3; ++i) {
for (int j = 1; j <= 3; ++j) {
/*Khi j = 2 thì bỏ qua lần lặp này
và tiếp tục chạy vòng lặp vongLapFor1*/
if (j == 2)
continue vongLapFor1;
System.out.println("i = " + i + "; j = " + j);
}
}
}
}
Khi chạy chương trình, kết quả nhận được là:
i = 1 và j = 1
i = 2 và j = 1
i = 3 và j = 1
Lưu ý!
Việc sử dụng label trong lập trình không được khuyến khích vì nó làm code của bạn khó hiểu.
Nếu đứng trước tình huống sử dụng label thì tốt hơn hết là nên tái cấu trúc lại code. Cố gắng tìm cách giải quyết khác.
Như vậy là đến đây, bạn đã được hướng dẫn tự học ngôn ngữ Java với:
-
Chương 1: Làm quen với ngôn ngữ lập trình Java
-
Chương 2: Cấu trúc điều khiển trong Java.
Tiếp theo, chúng ta sẽ đến với một phần rất quan trọng trong lập trình Java, đó là Mảng trong Java.
Trong khi làm việc thực tế, chúng ta sẽ thao tác với mảng rất nhiều. Do đó, phần Mảng sẽ là một chương.
CHƯƠNG III: HỌC LẬP TRÌNH VỚI MẢNG TRONG JAVA
Trong chương này, bạn sẽ học cách làm việc với các mảng trong Java.
Bạn sẽ học được cách khai báo, khởi tạo và truy cập các phần tử mảng qua từng ví dụ cụ thể.
Mảng trong Java giống như một con tàu chở một loại mặt hàng duy nhất.
Nó được sinh ra để chứa nhiều giá trị cùng loại.
Trước khi chưa biết về mảng, chúng ta thường tạo nhiều biến để lưu giá trị, như thế này:
int a = 1;
int b = 2;
int c = 3;
int d = 4;
int e = 5;
...
int x =
Nhưng với mảng, chúng ta chỉ chỉ cần tạo một mảng duy nhất:
int[] diem = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
Chương III. Phần 1. Cách khai báo Mảng trong Java
Đây là cách bạn có thể khai báo một mảng trong Java:
dataType[] arrayName;
Trong đó:
-
dataType
có thể là một kiểu dữ liệu nguyên thủy (int, char, Double, byte...) hoặc một đối tượng (chúng ta sẽ học về đối tượng sau)
-
arrayName
là tên của mảng do bạn định nghĩa
Hãy lấy một ví dụ đơn giản như thế này:
int[] diem;
Ở đây, mảng diem
có thể chứa các số nguyên (int)
Nhưng,
Mảng có thể lưu trữ bao nhiêu phần tử?
Ngôn ngữ Java được sử dụng rất phổ biến bởi tính chặt chẽ của nó. Nó cho phép chúng ta kiểm soát tài nguyên, bộ nhớ rất tốt.
Khi khai báo mảng diem
ở trên, chúng ta chưa phân bổ bộ nhớ cho các phần tử của mảng.
Bây giờ, hãy phân bổ cho nó.
diem = new int[10];
Bây giờ, mảng diem đã có thể chứa được 10 phần tử kiểu int (độ dài của mảng là 10).
Lưu ý!
Trong Java, một khi độ dài được xác định, nó không thể thay đổi
Bạn cũng có thể vừa khai báo, vừa phân bổ bộ nhớ cho mảng trong một câu lệnh, như thế này:
int[] diem = new int[10];
Chương III. Phần 2. Chỉ số Mảng trong Java
Trong Java, bạn có thể truy cập các phần tử của mảng thông qua chỉ số (index)
Hãy xem xét lại ví dụ trước:
int[] diem = new int[10];
Hình ảnh minh họa mảng trong Java
Trong đó:
-
0
là vị trí của phần tử đầu tiên (Trong lập trình, máy đếm từ 0, 1, 2, 3, ...)
-
Để truy cập phần tử đầu tiên ta sử dụng
diem[0]
, phần tử thứ hai là diem[1]
, ....
-
Nếu độ dài của mảng là
n
thì phần tử cuối cùng là diem[n - 1]
-
Giá trị mặc địch ban đầu của các phần tử mảng số là
0
, và false
đối với mảng logic.
Ví dụ chứng minh giá trị mặc định của các phần tử mảng số là 0
:
class ViDuMang1 {
public static void main(String[] args) {
int[] diem = new int[5];
System.out.println(diem[0]);
System.out.println(diem[1]);
System.out.println(diem[2]);
System.out.println(diem[3]);
System.out.println(diem[4]);
}
}
Khi chạy chương trình, kết quả nhận được là:
0
0
0
0
0
Cách tốt hơn để truy cập các mảng trong Java là sử dụng vòng lặp.
// Sử dụng vòng lặp for để lặp qua mảng trong Java
class ViDuLapMang {
public static void main(String[] args) {
int[] diem = new int[5];
for (int i = 0; i < 5; ++i) {
System.out.println(diem[i]);
}
}
}
Chương III. Phần 2.1. Cách khởi tạo Mảng trong Java
Trong Java, bạn có thể khởi tạo mảng trong khi khai báo hoặc bạn có thể khởi tạo (hoặc thay đổi giá trị) sau này trong chương trình theo yêu cầu của bạn.
int[] diem = {9, 8, 1, 7, 4};
Câu lệnh trên khởi tạo mảng ngay khi khai báo.
Độ dài của mảng được xác định bằng số lượng giá trị phân tách nhau bằng dấu phảy ,
Trong ví dụ này, độ dài của mảng là 5.
Ví dụ khởi tạo mảng trong Java
Bây giờ, hãy viết một chương trình đơn giản để in các giá trị này ra màn hình:
class ViDuMang2 {
public static void mani (String[] args) {
int[] diem = {9, 8, 1, 7, 4};
// Sử dụng vòng lặp for để lặp qua mảng
for (int i = 0; i <= 5; ++i) {
System.out.println("Phần tử ở vị trí " + i + " là: " + diem[i]);
}
}
}
Khi chạy chương trình, kết quả nhận được là:
Phần tử ở vị trí 0 là: 9
Phần tử ở vị trí 1 là: 8
Phần tử ở vị trí 2 là: 1
Phần tử ở vị trí 3 là: 7
Phần tử ở vị trí 4 là: 4
Chương III. Phần 2.2. Cách truy cập phần tử mảng trong Java.
Trong lập trình Java, bạn có thể dễ dàng truy cập phần tử của mảng thông qua chỉ số (index) của nó.
Ví dụ:
class ViDuMang3 {
public static void mani (String[] args) {
int[] diem = {9, 8, 1, 7, 4};
diem[2] = 10;
// Sử dụng vòng lặp for để lặp qua mảng
System.out.println("Phần tử đầu tiên của mảng diem là " + diem[0]);
System.out.println("Phần tử thứ 3 của mảng diem là " + diem[2]);
System.out.println("Phần tử thứ 4 của mảng diem là " + diem[3]);
}
}
Khi chạy chương trình, kết quả nhận được là:
Phần tử đầu tiên của mảng diem là: 9
Phần tử thứ 3 của mảng diem là: 10
Phần tử thứ 4 của mảng diem là: 7
* Lưu ý: Trong lập trình, máy tính đếm từ 0, 1, 2, 3, 4, 5, ...
Như vậy, câu lệnh diem[2] = 0;
đã thực hiện gán lại giá trị của phần tử thứ 3 trong mảng diem.
Một ví dụ khác bên dưới đây sẽ tính tổng và giá trị trung bình của điểm trong mảng diem int:
class ViDuMang4 {
public static void main (String[] args) {
int[] diemToan = {9, 8, 1, 7, 4};
int tong = 0;
Double diemTrungBinh;
// Sử dụng for each để lặp
for (int diem : diemToan) {
tong += diem;
}
// Tính toán độ dài của mảng
int doDaiMang = diemToan.length;
// Ép kiểu của tong và doDaiMang về kiểu double
// và tính điểm trung bình môn Toán
diemTrungBinh = ((double)tong / (double)doDaiMang);
// In kết quả ra màn hình
System.out.println("Tổng điểm = " + tong);
System.out.println("Điểm trung bình môn Toán = " + diemTrungBinh);
}
}
Khi chạy chương trình, kết quả nhận được là:
Tổng điểm = 29
Điểm trung bình môn Toán = 5.8
Chương trình ở trên chắc không có gì khó hiểu nhỉ?
Chỉ có phần (double)tong
và (double)doDaiMang
là có thể bạn chưa được học.
Trong ngôn ngữ Java, kiểu dữ liệu rất chặt chẽ, do đó nếu dữ liệu không khớp với kiểu đã khai báo thì sẽ gây ra lỗi.
Chính vì thế, khi tính điểm trung bình rất có khả năng giá trị trả về sẽ là số thập phân.
Vì thế, chúng ta cần ép kiểu của tong
và doDaiMang
về kiểu double
để kết quả trả về phù hợp với kiểu dữ liệu của diemTrungBinh
mà chúng ta đã khai báo.
* Bạn cũng có thể chỉ cần ép kiểu 1 biến (double)tong
hoặc (double)doDaiMang
.
Chương III. Phần 2. Mảng đa chiều trong Java.
Trong Java, bạn có thể khai báo một mảng chứa các mảng được gọi là mảng đa chiều.
Trước khi tìm hiểu mảng đa chiều, hãy chắc chắn bạn đã đọc kỹ phần mảng một chiều trong Java và tự tay làm qua các ví dụ.
Trong phần đó bạn đã được học cách khai báo, khởi tạo, truy cập mảng. Mảng đa chiều cũng tương tự như vậy.
Ví dụ:
int[][] a = new int[3][4];
Ở đây, a
là một mảng 2 chiều (còn gọi là mảng 2d).
Mảng này có thể chứa 12 phần tử kiểu int.
Khai báo mảng 2 chiều trong Java.
Lưu ý!
Trong Java, máy tính bắt đầu đếm từ 0. Do đó mảng bắt đầu từ 0 chứ không phải 1
Tương tự như vậy, bạn có thể khai báo mảng 3 chiều như thế này:
int[][][] a = new int[3][4][2];
Ở đây, a
chứa được tối đa 3*4*2 (24) phần tử int.
Lưu ý!
Trong Java, độ dài của các hàng trong mảng có thể khác nhau.
Chương III. Phần 2.1. Cách khởi tạo Mảng đa chiều trong Java.
Đây là cách bạn có thể khởi tạo mảng 2 chiều trong Java.
int[][] a = {
{2, 3, 4},
{3, 4, 3, 5},
{3},
}
Như đã nói ở trên, mỗi thành phần của mảng a
là một mảng và chiều dài của mỗi hàng cũng khác nhau.
Ví dụ khởi tạo mảng 2 chiều trong Java
Thử viết chương trình để chứng minh ví dụ trên:
class ViDuMangDaChieu1 {
public static void main(String[] args) {
int[][] a = {
{2, 3, 4},
{3, 4, 3, 5},
{3},
};
System.out.println("Độ dài hàng 1: " + a[0].length);
System.out.println("Độ dài hàng 2: " + a[1].length);
System.out.println("Độ dài hàng 3: " + a[2].length);
}
}
Khi chạy chương trình, kết quả bạn nhận được là:
Độ dài hàng 1: 3
Độ dài hàng 2: 4
Độ dài hàng 3: 1
Bởi vì, mỗi một thành phần của một mảng đa chiều cũng là một mảng.
Vì thế, a[0]
, a[1]
, a[2]
cũng là một mảng.
Do đó, bạn có thể sử dụng thuộc tính length
để tính độ dài của chúng.
Chương III. Phần 2.2. Ví dụ về mảng đa chiều trong Java.
Để hiểu hơn về mảng đa chiều, hãy cùng làm thêm ví dụ về chúng.
Ví dụ dưới đây sẽ sử dụng vòng lặp for để in ra tất cả các phần tử trong mảng.
class ViDuMangDaChieu2 {
public static void main(String[] args) {
// Khởi tạo mảng
int[][] a = {
{2, 3, 4},
{3, 4, 3, 5},
{3},
};
// Lặp qua mảng và in ra các phần tử
for (int i = 0; i < a.length; ++i) {
for (int j = 0; j < a[i].length; ++j) {
System.out.println("Phần tử a[" + i + "]" + "[" + j + "] là: " + a[i][j]);
}
}
}
}
Khi chạy chương trình, kết quả nhận được là:
Phần tử a[0][0] là: 2
Phần tử a[0][1] là: 3
Phần tử a[0][2] là: 4
Phần tử a[1][0] là: 3
Phần tử a[1][1] là: 4
Phần tử a[1][2] là: 5
Phần tử a[2][0] là: 3
Cách làm trên cũng ổn.
Tuy nhiên, để lặp qua mảng, tốt hơn hết là sử dụng vòng lặp for each.
class ViDuMangDaChieu3 {
public static void main(String[] args) {
// Khởi tạo mảng
int[][] a = {
{2, 3, 4},
{3, 4, 3, 5},
{3},
};
// Lặp qua mảng và in ra các phần tử
// Sử dụng vòng lặp for each
for (int[] mangCon: a) {
for (int giaTri: mangCon) {
System.out.println(giaTri);
}
}
}
}
Khi chạy chương trình, kết quả nhận được là:
2
3
4
3
4
3
5
3
Qua 2 ví dụ ở trên, chúng ta đa thử khởi tạo mảng 2 chiều và thao tác với chúng.
Bây giờ, hãy thử tạo mảng 3 chiều thử xem nhé.
// Khởi tạo mảng 3 chiều
int[][][] mang3Chieu = {
{
{2, 3, 4},
{3, 4, 3, 5},
{3},
},
{
{1, 2, 3, 4},
{5, 5}
}
};
Bạn có thể hiểu đơn giản thế này, mảng 3 chiều là mảng các mảng 2 chiều.
Có nghĩa là, mỗi phần tử của mảng có thể là một mảng 2 chiều.
Tương tự như mảng 2d, mảng 3d có thể có độ dài khác nhau.
Ví dụ sử dụng vòng lặp for each để in tất cả các phần tử trong mảng 3D.
class ViDuMangDaChieu4 {
public static void manin(String[] args) {
// Khởi tạo mảng 3 chiều
int[][][] mang3Chieu = {
{
{2, 3, 4},
{3, 4, 3, 5},
{3},
},
{
{1, 2, 3, 4},
{5, 5}
}
};
// In tất cả các phần tử trong mảng 3D
// Sử dụng vòng lặp for each
for (int[][] mang2Chieu: mang3Chieu) {
for (int[] mangCon: mang2Chieu) {
for (int giaTri: mangCon) {
System.out.println(giaTri);
}
}
}
}
}
Khi chạy chương trình, kết quả nhận được là:
2
3
4
3
4
3
5
3
1
2
3
4
5
5
Lưu ý!
Mảng càng sâu sẽ làm hiệu suất của chương trình càng chậm
Chương III. Phần 3. Cách copy mảng trong Java.
Trong phần hướng dẫn tự học lập trình Java này, bạn sẽ tìm hiểu về các cách sao chép mảng / copy mảng trong Java (cả một chiều và hai chiều).
Chương III. Phần 3.1. Sao chép mảng Java sử dụng toán tử gán
Bạn có thể sao chép mảng bằng cách sử dụng toán tử gán =, ví dụ:
class ViDuCopyMang1 {
public static void main(String[] args) {
int [] diemToan = {1, 2, 3, 4, 5, 6};
// Copy mảng sử dụng toán tử gán
int [] diemToan1 = diemToan;
for (int diem: diemToan1) {
System.out.print(diem + ", ");
}
}
}
Khi chạy chương trình, kết quả nhận được là:
1, 2, 3, 4, 5, 6,
Mặc dù kỹ thuật sao chép mảng trong Java bằng toán tử gán có vẻ làm rất tốt.
Nhưng có một vấn đề với cách sao chép mảng này:
Nếu bạn thay đổi phần tử của 1 mảng, mảng còn lại cũng thay đổi theo. Ví dụ:
class ViDuCopyMang1 {
public static void main(String[] args) {
int [] diemToan = {1, 2, 3, 4, 5, 6};
// Copy mảng sử dụng toán tử gán
int [] diemToan1 = diemToan;
// Thay đổi phần tử trong mảng diemToan
diemToan[0] = 10;
System.out.print("Mảng diemToan1: ");
// In tất cả các phần tử trong mảng diemToan1
for (int diem: diemToan1) {
System.out.print(diem + ", ");
}
// Thay đổi phần tử trong mảng diemToan1
diemToan1[1] = 10;
System.out.print('\n' + "Mảng diemToan: ");
// In tất cả phần tử trong mảng diemToan
for (int diem: diemToan) {
System.out.print(diem + ", ");
}
}
}
Kết quả:
Mảng diemToan1: 10, 2, 3, 4, 5, 6,
Mảng diemToan: 10, 10, 3, 4, 5, 6,
Như bạn thấy, trong ví dụ trên:
-
Chúng ta gán lại phần tử đầu tiên của mảng
diemToan
-
Sau đó in ra tất cả các phần tử của mảng
diemToan1
.
-
Nhận thấy mảng
diemToan1
đã thay đổi theo sự thay đổi của mảng diemToan
Tương tự,
-
Chúng ta thử nghiệm thay đổi phần tử thứ 2 của mảng
diemToan1
-
Rồi thử in tất cả các phần tử của mảng
diemToan
-
Nhận thấy, mảng
diemToan
cũng đã thay đổi theo sự thay đổi của mảng diemToan1
Như vậy, kết luận là, sử dụng toán tử gán để sao chép mảng dẫn tới 2 mảng cùng tham chiếu đến cùng một vị trí.
Sự thay đổi của mảng này sẽ kéo theo sự thay đổi của mảng kia.
Đây được gọi là sao chép nông (shallow copy).
Chương III. Phần 3.2. Sao chép mảng Java sử dụng vòng lặp
Chúng ta cũng có thể sao chép mảng trong Java bằng cách sử dụng vòng lặp, ví dụ:
import java.util.Arrays;
class ViDuCopyMang2 {
public static void main(String[] args) {
int [] diemToan = {1, 2, 3, 4, 5, 6};
int [] diemVan = new int[6];
for (int i = 0; i < diemToan.length; ++i) {
diemVan[i] = diemToan[i];
}
// Chuyển đổi mảng thành chuỗi
System.out.println(Arrays.toString(diemVan));
}
}
Khi chạy chương trình copy mảng trên, kết quả nhận được là:
[1, 2, 3, 4, 5, 6]
Ở đây, chúng ta đã tạo ra một mảng trống là diemVan
với độ dài là 6 phần tử.
Quay mỗi lần lặp, chúng ta lại sao chép phần tử trong mảng diemToan
sang mảng diemVan
(với cùng vị trí).
Khi sử dụng cách sao chép mảng này, mảng diemToan
và diemVan
không chia sẻ cùng một tham chiếu.
Do đó, nếu sau khi copy, bạn thay đổi mảng diemToan
hoặc mảng diemVan
thì chúng không ảnh hưởng đến nhau.
Lưu ý!
Phương thức toString() được sử dụng để chuyển đổi mảng thành chuỗi (để hỗ trợ in ra màn hình)
Chương III. Phần 3.3. Sao chép mảng Java sử dụng phương thức arraycopy()
Class System chứa phương thức arraycopy()
cho phép bạn sao chép dữ liệu từ mảng này sang mảng khác.
Phương thức arraycopy() rất linh hoạt và hiệu quả.
Nó cũng cho phép bạn sao chép một phần theo chỉ định của mảng nguồn vào mảng đích.
// Cú pháp sao chép mảng sử dụng phương thức arraycopy()
public static void arraycopy(Object mangNguon, int viTriBatDauMangNguon,
Object mangDich, int viTriBatDauMangDich, int doDai)
Trong đó:
-
mangNguon
: Là mảng bạn muốn sao chép
-
viTriBatDauMangNguon
: Là vị trí bắt đầu mảng nguồn (index) bạn muốn sao chép
-
mangDich
: Là mảng bạn muốn sao chép đến
-
viTriBatDauMangDich
: Là vị trí bắt đầu mảng đich (index) bạn muốn sao chép đến
-
doDai
: là số phần tử bạn muốn sao chép
Hãy thử làm ví dụ sao chép mảng trong Java bằng phương thức arraycopy():
// Import cái này để sử dụng phương thức toString()
import java.util.Arrays;
class ViDuCopyMang3 {
public static void main(String[] args) {
int[] n1 = {1, 2, 3, 4, 5, 6};
int[] n3 = new int[5];
// Tạo mảng n2 có độ dài của mảng n1
int[] n2 = new int[n1.length];
// Sao chép mảng n1 sang mảng n2
System.arraycopy(n1, 0, n2, 0, n1.length);
System.out.println("Mảng n2 = " + Arrays.toString(n2));
// Sao chép 2 phần tử mảng n1, bắt đầu từ phần tử
// có index là 2 đến mảng n3 (bắt đầu từ vị trí index 1)
System.arraycopy(n1, 2, n3, 1, 2);
System.out.println("Mảng n3 = " + Arrays.toString(n3));
}
}
Khi chạy chương trình, kết quả nhận được là:
Mảng n2 = [1, 2, 3, 4, 5, 6]
Mảng n3 = [0, 3, 4, 0, 0]
Lưu ý!
Giá trị ban đầu mặc định của mảng int là 0
Chương III. Phần 3.4. Sao chép mảng Java sử dụng phương thức copyOfRange()
Ngoài ra, bạn có thể sử dụng phương thức copyOfRange() được định nghĩa trong class java.util.Arrays để sao chép mảng.
Như thế, bạn sẽ không cần tạo mảng đích trước khi gọi phương thức này.
Đây là cách phương thức copyOfRange() hoạt động:
// Để sử dụng Phương thức toString() và copyOfRange()
import java.util.Arrays;
class ViDuCopyMang4 {
public static void main(String[] args) {
int[] mangNguon = {1, 2, 3, 4, 5};
// Sao chép mảng nguồn đến mảng đích 1
int[] mangDich1 = Arrays.copyOfRange(mangNguon, 0, mangNguon.length);
System.out.println("Mảng mangDich1 = " + Arrays.toString(mangDich1));
// Sao chép mảng nguồn đến mảng đích 2
// từ index bằng 2 đến index bằng 4 (Không bao gồm ví trí index 4)
// Có nghĩa là chỉ sao chép giá trị 3, 4 trong mảng nguồn
int[] mangDich2 = Arrays.copyOfRange(mangNguon, 2, 4);
System.out.println("Mảng mangDich2 = " + Arrays.toString(mangDich2));
}
}
Kết quả khi chạy chương trình nhận được là:
Mảng mangDich1 = [1, 2, 3, 4, 5]
Mảng mangDich2 = [3, 4]
Chương III. Phần 3.5. Sao chép mảng 2 chiều sử dụng vòng lặp
Ở trên chúng ta đã học được cách sao chép mảng 1 chiều. Và dĩ nhiên, chúng ta cũng có thể sao chép mảng 2 chiều.
Ví dụ sử dụng vòng lặp để sao chép mảng 2 chiều:
import java.util.Arrays;
class ViDuCopyMang2Chieu {
public static void main(String[] args) {
int[][] mangNguon = {
{2, 3, 4},
{3, 4, 3, 5},
{3},
};
int[][] mangDich = new int[mangNguon.length][];
for (int i = 0; i < mangDich.length; ++i) {
// Phân bổ không gian cho mỗi hàng của mảng đích
mangDich[i] = new int[mangNguon[i].length];
for (int j = 0; j < mangDich[i].length; ++j) {
mangDich[i][j] = mangNguon[i][j];
}
}
// Hiển thị mảng đích
System.out.println(Arrays.deepToString(mangDich));
}
}
Khi chạy chương trình, kết quả nhận được là:
[[2, 3, 4], [3, 4, 3, 5], [3]]
Bạn có thể thấy chúng ta đã sử dụng phương thức deepToString() của class Arrays.
Phương thức deepToString() hiển thị trực quan rất tốt phải không?
Bạn có thể tìm hiểu thêm về deepToString() trong tài liệu của Oracle.
Ngoài ra, bạn cũng có thể sử dụng System.arraycopy()
hoặc Arrays.copyOf()
trong trường hợp mảng 1 chiều.
import java.util.Arrays;
class ViDuCopyMang2Chieu2 {
public static void main(String[] args) {
int[][] mangNguon = {
{2, 3, 4},
{3, 4, 3, 5},
{3},
};
int[][] mangDich = new int[mangNguon.length][];
for (int i = 0; i < mangNguon.length; ++i) {
// Phân bổ không gian cho mỗi hàng của mảng đích
mangDich[i] = new int[mangNguon[i].length];
System.arraycopy(mangNguon[i], 0, mangDich[i], 0, mangDich[i].length);
}
// Hiển thị mảng đích
System.out.println(Arrays.deepToString(mangDich));
}
}
Kết quả:
[[2, 3, 4], [3, 4, 3, 5], [3]]
Ok, vậy là bạn đã được học về mảng trong Java.
Đi tới đây cũng là khá xa, nắm kha khá về ngôn ngữ Java rồi đấy.
Và, để tiếp tục.
Bạn nên biết rằng: Java là ngôn ngữ lập trình hướng đối tượng (OOP).
Trong chương tới đây, bạn sẽ được học OOP thật kỹ càng. Học từ cách thể tạo class và object tùy chỉnh cho đến việc vận dụng các tính chất của OOP.
CHƯƠNG IV. HỌC LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG (Part1)
Chương IV. Phần 1. Class và Object trong Java
Java là một ngôn ngữ lập trình hướng đối tượng (OOP).
OOP là viết tắt của từ Object-oriented programming
Lập trình hướng đối tượng cho phép bạn chia các vấn đề phức tạp thành các tập nhỏ hơn bằng cách tạo ra các đối tượng.
Những đối tượng này có chung đặc điểm:
Hãy cùng lấy một ví dụ:
-
Đèn có thuộc tính sáng hoặc không sáng
-
Đèn có thể có hành vi tắt đèn và bật đèn
Tương tự:
-
Ô tô là một đối tượng
-
Ô tô có thuộc tính bánh xe, tên, thương hiệu, ghế ngồi ...
-
Ô tô có hành vi là di chuyển, rẽ trái, rẽ phải. lùi ...
Bạn sẽ được học về 3 đặc điểm của hướng đối tượng như đóng gói dữ liệu, kế thừa, đa hình ở chương tiếp theo.
Trong chương này, hãy để mọi thứ đơn giản nhất đã.
Video tự học Java 30: Class và Object trong Java
Chương IV. Phần 1.1. Class trong Java
Trước khi bạn tạo ra các đối tượng trong Java, bạn cần định nghĩa một class.
Một class là một bản kế hoạch chi tiết cho đối tượng.
Chúng ta có thể nghĩ về class như một bản thiết kế ngôi nhà.
Nó chứa tất cả các chi tiết về sàn nhà, cửa ra vào, cửa sổ... Và dựa trên những mô tả này, chúng ta có thể xây dựng ra các ngôi nhà.
Ở đây, Nhà là đối tượng (object).
Chương IV. Phần 1.2. Cách định nghĩa class trong Java
Đây là cách bạn có thể định nghĩa một class trong Java:
class TenClass {
// Biến
// Phương thức
}
Còn đây, là một ví dụ về class đèn mà chúng ta đã mô tả ở trên:
class Den {
// Biến thể hiện (instance variable)
private String denSang;
// Phương thức bật đèn
public void batDen(){
denSang = "Đèn đang sáng";
}
// Phương thức tắt đèn
public void tatDen() {
denSang = "Đèn đang tắt";
}
}
Ở đây, chúng ta đã định nghĩa:
-
Một class tên là
Den
(Xem thêm vê quy tắc đặt tên class trong Java)
-
Một biến thể hiện là
denSang
, có kiểu dữ liệu là String
.
-
Hai phương thức
batDen()
và tatDen()
Tất cả biến và phương thức trong class được gọi là members của class.
Và lưu ý, chúng ta có từ khóa public
và private
. Đây là từ khóa được sử dụng để điều chỉnh mức độ truy cập.
-
Từ khóa
private
làm cho biến thể hiện và phương thức chỉ có thể truy cập được từ bên trong cùng class.
-
Từ khóa
public
làm cho biến thể hiện và phương thức có thể truy cập được từ bên ngoài class.
Trong chương trình trên, biến denSang
là private
.
Nếu bạn cố gắng truy cập vào biến denSang
từ class khác (mà không phải bên trong class Den
) thì trình biên dịch sẽ ném ra lỗi.
Chương IV. Phần 1.3. Cách tạo Object trong Java
Khi class được định nghĩa, chỉ có đặc tả của đối tượng được xác định. Bộ nhớ chưa được phân bổ.
Để truy cập các thành viên được định nghĩa bên trong class, bạn cần tạo các đối tượng.
Hãy thử tạo các đối tượng của class Den
.
class Den {
// Biến thể hiện (instance variable)
private String denSang;
// Phương thức bật đèn
public void batDen(){
denSang = "Đèn đang sáng";
}
// Phương thức tắt đèn
public void tatDen() {
denSang = "Đèn đang tắt";
}
}
class TaoDoiTuongDen {
public static void main(String[] args) {
// Tạo đối tượng den1 từ class Den
Den den1 = new Den();
// Tạo đối tượng den2 từ class Den
Den den2 = new Den();
}
}
Ở đây, chúng ta đã tạo ra 2 đối tượng den1
và den2
từ class Den
.
Chương IV. Phần 1.4. Cách truy cập thành viên trong Java
Bạn có thể truy cập các thành viên (members) bạn có thể sử dụng toán tử dot .
Ví dụ truy cập phương thức batDen()
trong class Den
:
den1.batDen();
Bạn đã nghe thấy từ "Phương thức" (method) rất nhiều rồi đúng không?
Trong phần tiếp theo chúng ta sẽ tìm hiểu kỹ hơn. Bây giờ hãy để mọi thứ đơn giản đã.
Khi bạn gọi đến phương thức:
-
Tất cả các câu lệnh trong phương thức sẽ được thực thi
-
Sau đó, trình điều khiển chương trình nhảy trở lại, tiếp tục thưc thi câu lệnh ngay sau câu lệnh vừa được gọi.
Ảnh minh họa cách thực thi phương thức trong Java
Tương tự, bạn cũng có thể truy cập vào biến thể hiện.
den1.denSang = "Đèn sáng rồi ....";
Chú ý, từ khóa private khiến cho biến denSang
chỉ có thể truy cập được từ bên trong class Den
.
Vì thế, nếu câu lệnh den1.denSang = "Đèn sáng rồi ....";
được đặt trong phương thức main()
ở class TaoDoiTuongDen
thì chương trình sẽ ném ra lỗi.
Chương IV. Phần 1.5. Ví dụ về Class và Object trong Java
Bây giờ, hãy làm một ví dụ hoàn chỉnh về class và object trong Java.
Lần này chúng ta chỉ cần tạo một đối tượng đèn để ngắn gọn hơn.
class Den {
// Biến thể hiện (instance variable)
private String denSang;
// Phương thức bật đèn
public void batDen(){
denSang = "Đèn đang sáng";
}
// Phương thức tắt đèn
public void tatDen() {
denSang = "Đèn đang tắt";
}
public void thongBao() {
System.out.println("Thông báo: " + denSang);
}
}
class TaoDoiTuongDen {
public static void main(String[] args) {
// Tạo đối tượng den1 từ class Den
Den den = new Den();
// Bật đèn
den.batDen();
// Thông báo trạng thái của đèn
den.thongBao();
// Tắt đèn
den.tatDen();
// Thông báo trạng thái của đèn
den.thongBao();
}
}
Khi chúng ta chạy chương trình, kết quả nhận được là:
Thông báo: Đèn đang sáng
Thông báo: Đèn đang tắt
Trong chương trình trên:
-
Chúng ta đã tạo một class
Den
để định nghĩa các biến và phương thức.
-
Biến
denSang
nhận giá trị dạng String
.
-
Hai phương thức
batDen()
và tatDen()
là để gán giá trị cho biến denSang
.
-
Phương thức
thongBao()
là để in ra giá trị denSang
đang chứa.
Tiếp đó:
-
Chúng ta tạo class
TaoDoiTuongDen
-
Câu lệnh
Den den = new Den();
là để tạo một đối tượng den từ class Den
-
Sau đó, chúng ta thử bật đèn. Câu lệnh
den.batDen();
chỉ định chương trình thực thi code bên trong phương thức batDen()
.
-
Có nghĩa là gán giá trị
"Đèn đang sáng"
cho biến denSang
.
-
Và in ra giá trị
denSang
đang chứa bằng câu lệnh den.thongBao();
-
Tương tự, chúng ta tắt đèn bằng cách gọi đến phương thức
tatDen()
và lại in thông báo.
Lưu ý rằng, biến được định nghĩa bên trong class được gọi là biến thể hiện (instance variable) là có lý do của nó.
Vì khi một đối tượng được khởi tạo, nó được gọi là một thể hiện (instance).
Mỗi thể hiện chứa bản sao riêng của các biến này. Thế nên gọi là biến thể hiện (instance variable)
Ví dụ, biến denSang
ở trên đầu được tạo ra cho đối tượng den1
và den2
là khác nhau.
Chương IV. Phần 2. Phương thức trong Java
Ok, như đã đề cập ở trên, phần này bạn sẽ được học về Phương thức trong Java.
Bạn sẽ học được cách định nghĩa phương thức, cách sử dụng phương thức trong lập trình.
Vậy...
Chính xác thì ....
Chương IV. Phần 2.1. Phương thức là gì?
Trong Toán học, chắc bạn đã được học về hàm.
Ví dụ, hàm f(x) = x ^ 2 là một hàm trả về giá trị bình phương của x.
Tương tự, trong lập trình cũng thế. Một hàm là một khối code thực hiện một nhiệm vụ cụ thể nào đó.
Và trong phương pháp lập trình hướng đối tượng, hàm được gọi với tên gọi khác là "Phương thức" để gần với ý nghĩa hướng đối tượng hơn.
Phương thức trong tiếng Anh là method
Các phương thức này được liên kết với một class. Và nó định nghĩa hành vi của class đó.
Chương IV. Phần 2.2. Các kiểu phương thức.
Tùy thuộc vào việc một phương thức được định nghĩa bởi người dùng hay có sẵn trong thư viện chuẩn mà chia thành hai loại phương thức:
Chương IV. Phần 2.3 Phương thức trong thư viện tiêu chuẩn
Video tự học Java 31: Phương thức dựng sẵn (Có trong thư viện tiêu chuẩn)
Các phương thức kiểu Standard Library Methods là các phương thức được dựng sẵn trong Java (Built-in Functions).
Bạn có thể gọi để sử dụng chúng ngay.
Các thư viện chuẩn này đi kèm với Thư viện Java Class (JCL) trong tệp lưu trữ Java (* .jar) với JVM và JRE.
Ví dụ:
-
print() là phương thức trong java.io.PrintSteam.
-
sqrt() là phương thức trong lớp Math.
Ví dụ về phương thức sqrt():
// Ví dụ phương thức sqrt() có sẵn trong class Math
public class ViDuSqrtMethod {
public static void main(String[] args) {
System.out.print("Căn bậc 2 của 4 là: " + Math.sqrt(4));
}
}
Khi chạy chương trình, kết quả nhận được là:
Căn bậc 2 của 4 là: 2
Chương IV. Phần 2.4. Phương thức tự định nghĩa trong Java
Video tự học Java 32: Phương thức tự định nghĩa trong Java
Bạn cũng có thể tự định nghĩa các phương thức bên trong một class theo mong muốn của bạn.
Các phương thức như vậy được gọi là phương thức do người dùng định nghĩa (user-defined methods)
Trước khi bạn có thể sử dụng (gọi một phương thức), bạn cần định nghĩa nó.
Đây là cách bạn định nghĩa các phương thức trong Java:
public static void myMethod() {
System.out.println("Phương thức myMethod được gọi");
}
Ở đây, chúng ta đã định nghĩa phương thức có tên là myMethod
Bạn có thể thấy:
-
Từ khóa
public
cho phép bạn có thể truy cập phương thức từ bên ngoài class nó được định nghĩa.
-
Từ khóa
static
cho phép bạn có thể truy cập phương thức mà không cần tạo đối tượng.
-
Từ khóa
void
biểu thị rằng phương thức này không trả về bất kỳ giá trị nào.
Trong chương trình trên, phương thức myMethod không chấp nhận bất kỳ đối số nào.
Do đó dấu ngoặc tròn () này không có gì.
Lưu ý!
Bạn sẽ được học cách truyền đối số cho một phương thức trong các phần sau.
Cú pháp đầy đủ để định nghĩa một phương thức trong Java là:
modifier static returnType nameOfMethod (Parameter List) {
// method body
}
Trong đó:
-
modifier: Xác định mức độ truy cập như public, private và protected
-
static: Nếu bạn sử dụng từ khóa static thì phương thức có thể được truy cập mà không cần tạo đối tượng.
-
returnType: Giá trị phương thức có thể trả về
Nó có thể trả về các kiểu dữ liệu gốc (int, float, double, v.v.), các đối tượng gốc (String, Map, List, v.v.) hoặc bất kỳ đối tượng nào được tích hợp hoặc đối tượng do người dùng định nghĩa.
-
nameOfMethod: Tên (định danh) của phương thức
Bạn có thể đặt bất kỳ tên nào cho một phương thức.
Tuy nhiên, thông thường, tên phương thức được đặt theo hành vi của nó ví dụ: tinhDiemTrungBinh
, tinhDienTichHinhTron
, ...
-
Parameters (Arguments): Các tham số là các giá trị được truyền cho một phương thức.
Bạn có thể truyền đối số cho một phương thức với số lượng bất kỳ.
-
Method body: Nó xác định phương thức thực sự làm gì, cách các tham số được thao tác với các câu lệnh lập trình và giá trị nào được trả về. Các câu lệnh được đặt bên trong dấu ngoặc nhọn
{ }
là phần thân của phương thức.
Sau khi bạn đã định nghĩa phương thức. Nó có thể được gọi trong main
Khi bạn gọi phương thức, đây là câu lệnh thường dùng để gọi phương thức kiểu static
.
myMethod();
Còn khi gọi phương thức non-static
, chúng ta gọi thông qua đối tượng (Giả sử mình đã tạo đối tượng den của class Den):
den.myMethod();
-
Đầu tiên, trình điều khiển chương trình bắt đầu trong hàm main. (Không quan tâm main được đặt ở đầu / giữa / cuối)
-
Khi gặp phương thức nào hợp lệ. Thì nhảy đến thực thi code bên trong phương thức (đã được định nghĩa trong class)
-
Sau đó, trình điều khiển lại tiếp tục nhảy đến dòng tiếp theo (trong main), sau phương thức vừa thực thi.
Chương IV. Phần 2.5. Ví dụ phương thức trong Java.
Để hiểu hơn về phương thức trong Java, hãy cùng làm theo ví dụ:
// Ví dụ phương thức trong Java
class Main {
public static void main(String[] args) {
System.out.println("Sắp gọi đến một phương thức");
// Gọi phương thức
myMethod();
System.out.println("Phương thức thực thi thành công");
}
// Định nghĩa phương thức
private static void myMethod(){
System.out.println("Dòng này in từ trong myMethod");
}
}
Khi chạy chương trình, kết quả nhận được là:
Sắp gọi đến một phương thức
Dòng này in từ trong myMethod
Phương thức thực thi thành công
Phương thức myMethod()
trong chương trình trên không chấp nhận bất kỳ đối số nào.
Ngoài ra, phương thức không trả về bất kỳ giá trị nào (kiểu trả về là void
).
Lưu ý rằng, chúng ta đã gọi phương thức mà không tạo đối tượng của class. Đó là vì myMethod()
là static
.
Dưới đây là một ví dụ khác về phương thức trong lập trình Java.
Trong ví dụ này, phương thức của chúng ta là non-static và nằm trong một class khác.
class Main {
public static void main(String[] args) {
Output obj = new Output();
System.out.println("Sắp gọi đến một phương thức");
// Gọi đến phương thức myMethod() trong class Output
obj.myMethod();
System.out.println("Phương thức thực thi thành công");
}
}
class Output {
// public: Phương thức này có thể gọi từ bên ngoài class
public void myMethod() {
System.out.println("Dòng này in từ trong myMethod");
}
}
Khi chạy chương trình, kết quả nhận được là:
Sắp gọi đến một phương thức
Dòng này in từ trong myMethod
Phương thức thực thi thành công
Lưu ý rằng, đầu tiên chúng ta đã tạo đối tượng của class Ouput
.
Sau đó phương thức myMethod được gọi bằng cách sử dụng đối tượng obj
. Điều này là do myMethod()
là một phương thức non-static.
Chương IV. Phần 2.6. Phương thức chấp nhận đối số và có giá trị trả về trong Java.
Video tự học Java 33: Phương thức có tham số trong Java
Một phương thức Java có thể không có hoặc có rất nhiều tham số.
-
Các tham số khi định nghĩa phương thức sẽ chấp nhận các đối số tương ứng được truyền vào khi gọi phương thức.
-
Ví dụ: Khi định nghĩa phương thức như thế này
public void phuongThucA(int x) {}
thì x
lúc này gọi là tham số. Còn khi gọi phuongThucA(3);
trong main() thì 3
gọi là đối số.
Và, phương thức cũng có thể trả về một giá trị.
Ví dụ:
// Ví dụ phương thức trả về giá trị trong Java
class BinhPhuongMotSo {
public static void main(String[] args) {
int ketQua;
ketQua = tinhBinhPhuong();
System.out.println("Bình phương của 10 là: " + ketQua);
}
public static int tinhBinhPhuong() {
// Câu lệnh trả về giá trị
return 10 * 10;
}
}
Khi chạy chương trình, kết quả nhận được là:
Bình phương của 10 là: 100
Trong đoạn code trên, phương thức tinhBinhPhuong()
không chấp nhận bất kỳ đối số nào và luôn trả về giá trị 10 * 10
.
Ở trên, kiểu trả về của phương thức tinhBinhPhuong()
là kiểu int
.
Có nghĩa là, phương thức này tính toán xong sẽ trả về một số nguyên (để bạn có thể tiếp tục sử dụng kết quả này làm gì đó)
Như bạn thấy, nó đang bị cố định.
Như thế thì chương trình khá là ngu.
Chẳng nhẽ muốn tính bình phương số khác lại phải code lại chương trình khác?
Không, không cần như thế.
Chúng ta sẽ sửa lại chương trình trên như sau:
// Ví dụ phương thức chấp nhận đối số
public class TinhBinhPhuongMotSo {
public static void main(String[] args) {
int ketQua, n;
n = 3
ketQua = tinhBinhPhuong(n);
System.out.println("Bình phương của 3 là: " + ketQua);
n = 4
ketQua = tinhBinhPhuong(n);
System.out.println("Bình phương của 4 là: " + ketQua);
}
static int tinhBinhPhuong(int i) {
return i * i;
}
}
Khi chạy chương trình, kết quả nhận được là:
Bình phương của 3 là: 9
Bình phương của 4 là: 16
Bạn thấy không, với chương trình này, chúng ta chỉ cần truyền đối số mà không cần code lại phương thức.
Video tự học Java 34: Phương thức có nhiều tham số trong Java
Bạn cũng có thể truyền nhiều đối số hơn bằng cách khai báo một danh sách tham số khi định nghĩa phương thức:
// Ví dụ phương thức chấp nhận nhiều đối số
public class TinhToan {
public static int tinhTong (int i, int j) {
return i + j;
}
public static int tinhTich (int x, int y) {
return x * y;
}
public static void main(String[] args) {
System.out.println("10 + 30 = " + tinhTong(10, 30));
System.out.println("10 x 20 = " + tinhTich(10, 20));
}
}
Khi chạy chương trình, kết quả nhận được là:
10 + 30 = 50
10 x 20 = 200
Lưu ý rằng, khi bạn khai báo đã chỉ định kiểu int. Nếu khi gọi bạn truyền bất kỳ kiểu dữ liệu nào khác, nó sẽ tạo ra lỗi.
Có nghĩa là:
-
Kiểu dữ liệu của đối số thứ nhất phải phù hợp với kiểu dữ liệu khi định nghĩa tham số thứ nhất
-
Kiểu dữ liệu của đối số thứ hai phải phù hợp với kiểu dữ liệu khi định nghĩa tham số thứ hai.
-
...
Cùng làm thêm một ví dụ khác về phương thức trong Java.
Ví dụ này sẽ tính bình phương các số từ 1 đến 5:
// Ví dụ phương thức tính bình phương trong Java
public class TinhBinhPhuong {
// Định nghĩa phương thức tính bình phương
private static int tinhBinhPhuong(int x){
return x * x;
}
public static void main(String[] args) {
// Sử dụng vòng lặp for
for (int i = 1; i <= 5; i++) {
// Tính bình phương
ketQua = tinhBinhPhuong(i)
System.out.println("Bình phương của " + i + " là : " + ketQua);
}
}
}
Khi chạy chương trình, kết quả nhận được là:
Bình phương của 1 là: 1
Bình phương của 2 là: 4
Bình phương của 3 là: 9
Bình phương của 4 là: 16
Bình phương của 5 là: 25
Trong ví dụ trên, phương thức tinhBinhPhuong()
lấy tham số kiểu int
. Dựa trên đối số được truyền, phương thức trả về giá trị bình phương của nó.
Ở đây, đối số i
kiểu int
được truyền cho phương thức tinhBinhPhuong()
trong khi gọi phương thức.
ketQua = tinhBinhPhuong(i)
Khi truyền đối số i
cho phương thức tinhBinhPhuong()
. Phương thức này sẽ trả lại giá trị bình phương của i
.
Và gán giá trị trả về cho ketQua
.
Ưu điểm chính của phương thức là khả năng tái sử dụng code.
Bạn chỉ cần viết một lần và sử dụng lại ở khắp nơi.
Và lưu ý, hãy đặt tên phương thức thật dễ hiểu và đúng với hành vi của nó để giúp dễ dàng gỡ lỗi hơn sau này.
Chương IV. Phần 3. Constructor trong Java.
Trong phần hướng dẫn tự học lập trình Java này, bạn sẽ tìm hiểu về constructor trong Java.
Bạn cũng sẽ được học cách để tạo và sử dụng constructor thông qua các ví dụ cụ thể.
Chương IV. Phần 3.1. Constructor là gì?
Một constructor (còn gọi là hàm tạo) tương tự như một phương thức (nhưng không thực sự là một phương thức) được gọi tự động khi một đối tượng được khởi tạo.
Trình biên dịch Java phân biệt giữa một phương thức và một hàm tạo theo tên và kiểu trả về của nó.
Trong Java, một hàm tạo phải có cùng tên với class và không phải trả về bất kỳ giá trị nào.
class ViDuConstructor {
ViDuConstructor() {
// Phần thân constructor
}
}
Ở trên, hàm tạo tên là ViDuConstructor
trùng với tên của class.
Còn phương thức thì:
class ViDuConstructor {
void ViDuConstructor() {
// Phần thân constructor
}
}
Mặc dù có tên giống với class, nhưng nó lại có kiểu trả về là void (không trả về cái gì).
Thế nên, đây là phương thức chứ không phải constructor.
Chương IV. Phần 3.2. Ví dụ đơn giản về constructor trong Java.
Chúng ta hãy xem qua ví dụ bên dưới đây để hiểu hơn về constructor trong Java.
// Ví dụ về constructor trong Java
class ConsMain {
private int x;
// constructor
private ConsMain(){
System.out.println("Constructor được gọi");
x = 10;
}
public static void main(String[] args){
ConsMain obj = new ConsMain();
System.out.println("Giá trị của x = " + obj.x);
}
}
Khi chạy chương trình, kết quả nhận được là:
Constructor được gọi
Giá trị của x = 10
Như bạn thấy, constructor được gọi ngay sau khi đối tượng được tạo (mặc dù chúng ta không có truy cập nó như kiểu các phương thức).
Constructor có thể có hoặc không chấp nhận đối số.
Chương IV. Phần 3.3. Constructor không đối số (Không tham số)
Video tự học Java 35: Constructor không tham số trong Java
Constructor không có tham số nào khi định nghĩa được gọi là constructor không đối số (no-arg constructor).
Cú pháp của constructor không đối số như sau:
accessModifier ClassName() {
// constructor body
}
Trong đó:
-
accessModifier
là chỉ định mức độ truy cập cho constructor
-
ClassName
là tên của class (Vì tên constructor phải trùng với tên của class)
Ví dụ về constructor không đối số:
// Ví dụ về constructor không đối số
class NoArgConstructor {
int i;
// Constructor không đối số
private NoArgConstructor(){
i = 10;
System.out.println("Đối tượng được tạo và i = " + i);
}
public static void main(String[] args) {
// Tạo đối tượng
NoArgConstructor obj = new NoArgConstructor();
}
}
Khi chạy chương trình, kết quả nhận được là:
Đối tượng được tạo và i = 10
Ở đây, constructor không chấp nhận đối số nào.
Bạn có để ý từ khóa private
không?
Từ khóa private
chỉ định mức độ truy cập của constuctor NoArgConstructor
. Làm cho nó chỉ có thể được truy cập từ class NoArgConstructor
.
Nếu bạn muốn có thể truy cập constructor từ class khác, bạn phải sửa private
thành public
.
class CongTy {
String tenMien;
// Constructor có thể truy cập
// từ class khác
public CongTy(){
tenMien = "niithanoi.edu.vn";
}
}
public class ThanhLapCongTy {
public static void main(String[] args) {
CongTy niit = new CongTy();
System.out.println("Tên miền Webiste là: "+ niit.tenMien);
}
}
Khi chạy chương trình, kết quả nhận được là:
Tên miền Website là: niithanoi.edu.vn
Chương IV. Phần 3.4. Constructor mặc định trong Java.
Nếu bạn không tự tạo constructor, trình biên dịch Java sẽ tự động tạo một constructor không có đối số trong runtime.
Constructor này được gọi là constructor mặc định. Consturctor mặc định sẽ khởi tạo bất kỳ biến thể hiện nào chưa được khởi tạo.
Để hiểu hơn về constructor mặc định, chúng ta cùng làm ví dụ sau:
// Ví dụ constructor mặc định trong Java
class ConstructorMacDinh {
// Khai báo biến, chưa khởi tạo
// giá trị ban đầu
int a;
boolean b;
public static void main(String[] args) {
ConstructorMacDinh obj = new ConstructorMacDinh();
System.out.println("a = " + obj.a);
System.out.println("b = " + obj.b);
}
}
Khi chạy chương trình, kết quả nhận được là:
a = 0
b = false
Chương trình bên trên tương đương với:
// Ví dụ constructor mặc định trong Java
class ConstructorMacDinh {
// Khai báo biến, chưa khởi tạo
// giá trị ban đầu
int a;
boolean b;
private ConstructorMacDinh() {
a = 0;
b = false;
}
public static void main(String[] args) {
ConstructorMacDinh obj = new ConstructorMacDinh();
System.out.println("a = " + obj.a);
System.out.println("b = " + obj.b);
}
}
Chương IV. Phần 3.5. Constructor có đối số (Có tham số)
Video tự học Java 36: Constructor không tham số trong Java
Constructor có đối số tức là constructor được định nghĩa có danh sách tham số.
// Cú pháp của constructor có đối số
accessModifier ClassName(arg1, arg2, ..., argn) {
// constructor body
}
Như bạn có thể thấy, khi định nghĩa constructor, chúng ta định nghĩa thêm danh sách tham số.
Các tham số này sử dụng để chấp nhận đối số khi khởi tạo đối tượng.
Ví dụ về constructor có đối số trong Java.
// Ví dụ về constructor có đối số
class XeOto {
String thuongHieu;
private XeOto(String thuongHieu){
thuongHieu = thuongHieu;
System.out.println("Thương hiệu xe ô tô " + thuongHieu + " ra đời");
}
public static void main(String[] args) {
XeOto xe1 = new XeOto("Vinfast");
XeOto xe2 = new XeOto("Tesla");
XeOto xe3 = new XeOto("BMW");
}
}
Khi chạy chương trình, kết quả nhận được là:
Thương hiệu xe ô tô Vinfast ra đời
Thương hiệu xe ô tô Tesla ra đời
Thương hiệu xe ô tô BMW ra đời
Ở ví dụ trên, chúng ta đã truyền đối số kiểu String cho constructor khi khởi tạo đối tượng.
Chương IV. Phần 3.6. Constructor Overloading trong Java
Video tự học Java 37: Constructor Overloading trong Java
Tương tự như method overloading, bạn cũng có thể overload các constructor nếu hai hoặc nhiều constructor có tham số khác nhau. Ví dụ:
// Ví dụ Constructor overloading trong Java
class CongTy {
String tenMien;
public CongTy(){
this.tenMien = "mặc định";
}
public CongTy(String tenMien){
this.tenMien = tenMien;
}
public void layTenMien(){
System.out.println(this.tenMien);
}
public static void main(String[] args) {
CongTy ctyMacdinh = new CongTy();
CongTy niit = new CongTy("niithanoi.edu.vn");
ctyMacdinh.layTenMien();
niit.layTenMien();
}
}
Khi chạy chương trình, kết quả nhận được là:
mặc định
niithanoi.edu.vn
Như bạn thấy, chúng ta có hai constructor nhưng khác nhau về tham số.
Khi khởi tạo đối tượng mà không truyền đối số, constructor mặc định được gọi đến.
Khi khởi tạo đối tượng truyền vào một đối số kiểu String thì constructor tương ứng được gọi đến.
Chương IV. Phần 3.7. Những điểm quan trọng về Constructor trong Java.
Như vậy, chúng ta đã học xong về constructor trong Java. Đây là nhưng điểm quan trọng bạn cần ghi nhớ:
-
Các constructor được gọi ngầm khi bạn khởi tạo các đối tượng.
-
Hai quy tắc để tạo một constructor là:
Quy tắc 1: Tên hàm tạo Java phải khớp chính xác với tên lớp.
Quy tắc 2: Một hàm tạo Java không được có kiểu trả về.
-
Nếu một class không có constructor, trình biên dịch Java sẽ tự động tạo một constructor mặc định trong runtime.
Constructor mặc định khởi tạo các biến thể hiện với các giá trị mặc định. Ví dụ: biến int
sẽ được khởi tạo thành 0
-
Các loại constructor trong Java:
Constructor mặc định - một constructor được trình biên dịch Java tạo tự động nếu nó không được định nghĩa rõ ràng.
Constructor không đối số - một constructor không chấp nhận bất kỳ đối số nào.
Constructor có đối số - chấp nhận một hoặc nhiều đối số. Được sử dụng để xác định các giá trị cụ thể của các biến trong đối tượng
-
Các constructor không thể
abstract
hoặc static
hoặc final
-
Constructor có thể bị overload (nạp chồng) nhưng không thể bị override (ghi đè)
Đó, đến hết phần này, mình tin chắc bạn đã có kiến thức đủ về constructor để sử dụng lâu dài để tự học lập trình Java hay làm việc với Java rồi.
Chương IV. Phần 4. Mức độ truy cập trong Java
Trong phần này, bạn sẽ tìm hiểu về các Công cụ sửa đổi truy cập (Access Modifiers) khác nhau trong Java và cách chúng hoạt động trong các tình huống khác nhau.
Chương IV. Phần 4.1. Access Modifier là gì?
Access Modifier (Công cụ sửa đổi truy cập) là các từ khóa thiết lập mức độ truy cập (mức độ hiển thị) của class, interface, variable, data members, method hoặc constructor và setters của chúng (cập nhật giá trị của một biến).
Chúng còn được gọi là visibility modifier.
Bạn không thể thiết lập mức độ truy cập cho getters (truy xuất giá trị của một biến) vì chúng luôn có cùng mức độ hiển thị như của thuộc tính.
Sử dụng công cụ sửa đổi truy cập để đạt được tính đóng gói dữ liệu tốt hơn.
Có nghĩa là, với điều chỉnh mức độ truy cập, bạn có thể kiểm soát ai có thể truy cập vào dữ liệu của bạn.
Vì thế, bạn có thể ngăn chặn hành vi truy cập dữ liệu bất hợp pháp.
Chương IV. Phần 4.2. Có bao nhiêu mức độ truy cập trong Java?
Một package chỉ đơn giản là một container chứa một nhóm liên quan (các class Java, interface, list ....).
Có bốn từ khóa sửa đổi mức độ truy cập trong Java:
-
Private
: chỉ hiển thị trong cùng class
-
Default
: chỉ hiển thị bên trong package (private package)
-
Protected
: chỉ hiển thị bên trong package hoặc tất cả các subclass
-
Public
: hiển thị ở mọi nơi
Mới đầu nghe cũng có vẻ hơi khó hiểu nhỉ :D.
Lần đầu tiên mình nghe cũng vậy. Phải đọc đi đọc lại nhiều lần mới hiểu.
Bây giờ, mình sẽ giải thích chi tiết tất cả các mức độ truy cập ở dưới đây.
Chương IV. Phần 4.3. Mức độ truy cập mặc định (default)
Nếu không có từ khóa sửa đổi mức độ truy cập nào được chỉ định rõ ràng cho một class, variable, method hoặc constructor thì ....
Theo mặc định, nó được coi là một công cụ sửa đổi truy cập mặc định.
// Ví dụ định nghĩa mức độ truy cập mặc định
package defaultPackage;
class Logger {
void message(){
System.out.println("Đây là một dòng chữ");
}
}
Ở đây, class Logger
có mức độ truy cập theo mặc định.
Và class logger
này hiển thị với các class thuộc gói defaultPackage
.
Nếu bạn import class Logger
trong package khác và cố gắng khởi tạo nó, bạn sẽ gặp lỗi biên dịch.
Chương IV. Phần 4.4. Mức độ truy cập private
Video tự học Java 38: Từ khóa Private trong Java
Chỉ các method và các data members là có thể được khai báo là private
.
Còn các class hoặc interface không thể khai báo là private
.
Tuy nhiên, các class được khai báo bên trong một class khác (class lồng nhau) có thể được khai báo là private.
Các biến private có thể được truy cập bên ngoài class, nếu có các phương thức getter công khai trong class.
Ví dụ:
// Ví dụ từ khóa private
public class Data {
private String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
public class Main {
Public static void main(String[] main){
Data d = new Data();
d.setName("NIIT ICT Hà Nội");
System.out.println(d.getName());
}
}
Khi chạy chương trình, kết quả nhận được là:
NIIT ICT Hà Nội
Ở đây, name là một biến private
và nó chỉ hiển thị bên trong class Data
.
Nhưng, nó có thể được truy cập trong một class khác (class Main
) nhờ sự trợ giúp của các phương thức getter
và setter
công khai.
* Đôi điều nhắn nhủ với các bạn đang học lập trình.
Vì thế, khi học lập trình hãy luyện tập thật nhiều, mắc lỗi thật nhiều để trở thành một lập trình viên tốt hơn.
Chương IV. Phần 4.5. Mức độ truy cập Protected
Video tự học Java 39: Từ khóa Protected trong Java
Công cụ sửa đổi mức độ truy cập protected
có thể truy cập trong cùng một package cũng như các subclass của class base.
Chỉ các method và các data members mới có thể được khai báo là protected
Còn các class hoặc interface thì không thể được khai báo là protected
.
Ví dụ, đây là class Logger
bên trong package1
// File Logger.java
package package1;
public class Logger {
protected void debug(String logLine){
System.out.println("Dòng cần Debug: "+logLine);
}
}
Còn đây là class Main
bên trong package2
// File Main.java
package package2;
import package1.Logger;
public class Main extends Logger {
public static void main(String [] args){
Main logger = new Main();
// Gọi phương thức debug() từ class Logger
logger.debug("Được gọi từ class Main");
}
}
Khi chạy chương trình, chúng ta được kết quả như sau:
Dòng cần debug: Được gọi từ class Main
Như bạn có thể thấy:
-
Logger.java
và Main.java
ở hai package khác nhau
-
Phương thức
debug()
trong class Logger được khai báo là protected
. Và chỉ có thể được truy cập bên trong package1
-
Tuy nhiên, nó vẫn có thể được truy cập từ trong class
Main
(trong package2
).
-
Đó là vì class
Main
kế thừa class Logger
Chương IV. Phần 4.6. Mức độ truy cập Public
Video tự học Java 40: Từ khóa Public trong Java
Công cụ sửa đổi mức độ truy cập public
không bị hạn chế phạm vi hiển thị.
Công cụ sửa đổi truy cập public
có thể được áp dụng cho các class và interface cùng với các method, data members và cả variable.
Ví dụ, chúng ta có file Logger.java
// File Logger.java
public class Logger {
public int debugLevel = 1;
public void debug(String logLine){
System.out.println("Sửa lỗi: "+logLine);
}
public void info(String logLine){
System.out.println("Thông tin: "+logLine);
}
}
Và file LoggerImp.java
// File LoggerImp.java
public class LoggerImp {
public static void main( String[] args ) {
Logger logger = new Logger();
logger.debug("Cấp độ " + logger.debugLevel);
logger.debugLevel = 5;
logger.info("Lỗi cấp " + logger.debugLevel);
}
}
Khi chạy chương trình, kết quả nhận được là:
Sửa lỗi: Cấp độ 1
Thông tin: Lỗi cấp 5
Ở đây:
-
Trong class
LoggerImp
, bạn có thể khởi tạo class Logger
vì nó được khai báo là public
.
-
Các biến và phương thức bên trong lớp
LoggerImp
cũng là public
.
-
Do đó, bạn có thể sử dụng nó trực tiếp trong class
LoggerImp
của bạn.
Chương IV. Phần 5. Học sử dụng từ khóa this trong Java
Trong phần này, bạn sẽ được học về từ khóa this
trong ngôn ngữ lập trình Java.
Tìm hiểu về this
là gì và học cách sử dụng this
trong lập trình.
Chương IV. Phần 5.1. this là gì?
Trong lập trình Java, từ khóa this
đề cập đến object hiện tại.
// Ví dụ từ khóa this trong java
class MyClass {
int bienTheHien;
MyClass(int bienTheHien){
this.bienTheHien = bienTheHien;
System.out.println("this tham chiếu đến = " + this);
}
public static void main(String[] args) {
MyClass obj = new MyClass(8);
System.out.println("Đối tượng tham chiếu = " + obj);
}
}
Khi chạy chương trình, kết quả nhận sẽ tương tự như sau:
this tham chiếu đến: MyClass@2a139a55
Đối tượng tham chiếu: MyClass@2a139a55
Như bạn có thể thấy, this
và obj
là tương tự nhau.
Điều này có nghĩa là this
là từ khóa dùng để tham chiếu đến đối tượng hiện tại.
Có 3 tình huống thường sử dụng từ khóa this
Chương IV. Phần 5.2. Sử dụng từ khóa this để phân biệt biến tham chiếu
Video tự học Java 41: Từ khóa This trong Java (Phần 1)
Trong Java, chúng ta không được phép để khai báo hai hay nhiều biến có cùng tên bên trong phạm vi một (phạm vi class hoặc phạm vi method).
Tuy nhiên, biến thể hiện và tham số có thể có cùng tên, như sau:
Ví dụ:
// Ví dụ khai báo 2 biến cùng tên trong Java
class MyClass {
int bienA; // bienA là biến thể hiện
MyClass(int bienA){ // bienA là tham số
bienA = bienA;
}
}
Điều này là có thể vì 2 biến này có phạm vi khác nhau.
Tuy nhiên, tên giống nhau hay gây nhầm lẫn.
Để khắc phục vấn đề này, chúng ta sử dụng từ khóa this
.
Trước tiên, hãy xem ví dụ sau để thấy rõ vấn đề khi chạy chương trình.
// Ví dụ vấn đề khi có 2 biến trùng tên
class MyClass {
int bienA; // bienA là biến thể hiện
MyClass(int bienA){ // bienA là tham số
bienA = bienA;
}
public static void main(String[] args) {
MyClass obj = new MyClass(5);
System.out.println("obj.bienA = " + obj.bienA);
}
}
Khi chạy chương trình, kết quả nhận được là:
obj.bienA = 0
Kết quả chúng ta mong đợi lẽ ra là obj.bienA = 5
Nhưng chúng ta lại nhận được kết quả là obj.bienA = 0
Điều này là do chương trình không phân biệt được đâu là biến thể hiện và đâu là biến tham chiếu.
Ok, bây giờ thay đổi một chút trong chương trình trên với từ khóa this.
// Sử dụng this để tránh vấn đề khi có 2 biến trùng tên
class MyClass {
int bienA; // bienA là biến thể hiện
MyClass(int bienA){ // bienA là tham số
this.bienA = bienA;
}
public static void main(String[] args) {
MyClass obj = new MyClass(5);
System.out.println("obj.bienA = " + obj.bienA);
}
}
Bây giờ, bạn đoán xem. Kết quả nhận được là gì?
Chính xác, chúng ta nhận được:
obj.bienA = 5
Bây giờ, bạn đã nhận được kết quả đúng với mong đợi.
Đó là vì khi bạn tạo một đối tượng, trình biên dịch Java biết đối tượng nào đã gọi đến constructor.
Khi trình biên dịch Java gọi constructor, this
bên trong constructor được thay thế bởi đối tượng đã gọi constructor.
Lưu ý!
Nếu bạn truyền các tham số với tên khác biến thể hiện thì trình biên dịch tự động thêm từ khóa this
.
Ví dụ:
// Đoạn code này
class MyClass {
int bienA;
MyClass(int i) {
bienA = i;
}
}
Tương đương với:
// Tương đương với
class MyClass {
int bienA;
MyClass(int i) {
this.bienA = i;
}
}
Một cách sử dụng phổ biến của từ khóa this là trong phương thức getter và setter.
Video tự học Java 42: Từ khóa This trong Java (Phần 2)
Dưới đây là Code của chương trình sử dụng từ khóa this trong getter và setter (Lưu ý: Code sẽ không giống trong video hoàn toàn đâu nhé)
Hãy hiểu bản chất, đừng cố gắng "Chép code"
// Sử dụng this trong phương thức getter và setter
class CongTy {
String domain;
void setDomain( String domain ) {
this.domain = domain;
}
String getDomain(){
return this.domain;
}
public static void main( String[] args ) {
CongTy com = new CongTy();
com.setDomain("NIITHANOI.EDU.VN");
System.out.println("Website công ty: "+ com.getDomain());
}
}
Khi chạy chương trình, kết quả nhận được là:
Website công ty: NIITHANOI.EDU.VN
Chương IV. Phần 5.3. Sử dụng từ khóa this trong Constructor Overloading
Trong khi làm việc với Constructor Overloading, bạn có thể cần gọi một constructor từ một constructor khác.
Nhưng, constructor không thể được gọi là một cách rõ ràng.
Do đó, để thực hiện việc này, bạn có thể sử dụng một hình thức khác của từ khoá this
là this()
Cú pháp để sử dụng là:
this.(thamSo1, thamSo2, ...);
Đây là ví dụ cách gọi một constructor từ constructor khác:
// Ví dụ gọi Constructor từ Constructor khác
// bằng cách sử dung từ khóa this
class GoiPhucTap {
private int a, b;
// Constructor có đối số
private GoiPhucTap( int i, int j ){
this.a = i;
this.b = j;
}
private GoiPhucTap(int i){
// Gọi constructor GoiPhucTap(int i, int j);
this(i, i);
}
private GoiPhucTap(){
// Gọi constructor GoiPhucTap(int i);
this(0);
}
@Override
public String toString(){
return this.a + " + " + this.b + "i";
}
public static void main( String[] args ) {
GoiPhucTap g1 = new GoiPhucTap(2, 3);
GoiPhucTap g2 = new GoiPhucTap(3);
GoiPhucTap g3 = new GoiPhucTap();
System.out.println(c1);
System.out.println(c2);
System.out.println(c3);
}
}
Khi chạy chương trình, chúng ta nhận được kết quả:
2 + 3i
3 + 3i
0 + 0i
Đây là cách gọi khá lòng vòng, phức tạp.
Bạn phải lưu ý khi sử sụng this(). Constructor gọi this() sẽ thực thi chậm hơn.
Nếu class có ít đối tượng thì sử dụng this() sẽ hiệu quả, giảm được kha khá code.
Đó, đây là cách bạn có thể gọi constructor từ constructor khác bằng this().
Chương IV. Phần 5.4. Truyền this như một đối số
Nếu bạn cần phải truyền đối tượng hiện tại như một đối số đến một phương thức, bạn có thể sử dụng từ khóa this
.
// Ví dụ truyền this như đối số
class ViDuThis {
int x;
int y;
ViDuThis(int x, int y) {
this.x = x;
this.y = y;
System.out.println("Trước khi truyền this đến phương thức cong2():");
System.out.println("x = " + this.x + ", y = " + this.y);
cong2(this);
System.out.println("Sau khi truyền this đến phương thức cong2():");
System.out.println("x = " + this.x + ", y = " + this.y);
}
void cong2(ViDuThis o){
o.x += 2;
o.y += 2;
}
}
class DemoThis {
public static void main( String[] args ) {
ViDuThis obj = new ViDuThis(1, -1);
}
}
Khi chạy chương trình, kết quả nhận được là:
Trước khi truyền this đến phương thức cong2():
x = 1, y = -1
Sau khi truyền this đến phương thức cong2():
x = 3, y = 1
Chương IV. Phần 6. Học sử dụng biểu thức Lambda trong Java.
Trong phần này, bạn sẽ học về tính năng mới được giới thiệu trong Java 8, đó là sự hỗ trợ cho biểu thức lambda sử dụng functional interface.
Biểu thức Lambda là một chủ đề nóng khi Java 8 đã được phát hành.
Lambda đã được thêm vào trong phiên bản JDK 8 để tăng cường hiệu suất Java bằng cách tăng cường ý nghĩa của ngôn ngữ.
Nhưng, trước khi học về lambda. Chúng ta cần hiểu...
Chương IV. Phần 6.1. Functional Interface là gì?
Nếu một Java interface có một và chỉ một phương thức trừu tượng thì nó được gọi là Functional Interface.
Có nghĩa là chỉ có một phương thức để nói lên mục đích của interface.
Ví dụ, Runnable
interface từ package java.lang
là một functional interface vì chỉ có một phương thức là run()
Ví dụ: Định nghĩa một Functional Interface trong Java
import java.lang.FunctionalInterface;
@FunctionalInterface
public interface MyInterface{
double getValue();
}
Chú ý!
Chú thích @FunctionalInterface
là không bắt buộc, nhưng nên sử dụng để thông báo cho trình biên dịch Java biết Interface này là một Functional Interface và nó chỉ có có một phương thức trừu tượng duy nhất.
Trong Java 7, Functional Interface được gọi là Single Abstrack Method hoặc SAM.
Ví dụ: Khai triển SAM với class ẩn danh trong Java
public class FunctionInterfaceTest {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Chỉ là chạy Runable Functional Interface");
}
}).start();
}
}
Khả năng truyền một class ẩn danh đến một constructor hoặc một phương thức đã làm cho việc viết code Java 7 dễ hơn.
Tuy nhiên, cú pháp là vẫn còn khá dài cần nhiều thứ lằng nhằng, khó nhớ. (Như bạn thấy ở ví dụ trên =)) )
Thế nên,
Java 8 mở rộng sức mạnh của một SAM bằng một bước tiến xa hơn.
Khi chúng ta đã biết rằng một Functional Interface chỉ có một phương thức thì câu hỏi nên hay không nên định nghĩa tên một phương thức và sau đó truyền như một đối số?
Biểu thức Lambda sẽ cho bạn câu trả lời chính xác nhất.
Vậy,
Biểu thức Lambda là gì?
Về cơ bản, biểu thức Lambda là môt phương thức vô danh / không có tên.
Biểu thức Lambda cũng không tự thực thi.
Thay vào đó, nó được dùng để khai triển một phương thức xác định bởi một Functional Interface.
Chương IV. Phần 6.2. Cách định nghĩa biểu thức Lambda trong Java.
Biểu thức lambda giới thiệu một cú pháp và toán tử mới trong ngôn ngữ Java.
Toán tử mới được gọi là người lambda operator hoặc arrow operator (->)
Chúng ta thường viết một phương thức đơn giản trả về một hằng số như sau:
double getPiValue() { return 3.1415; }
Cách viết tương đương khi sử dụng lambda như sau:
() -> 3.1415
Trong biểu thức lambda trên:
-
Bên trái biểu thức chỉ định bất thì tham số nào cần thiết
-
Phía bên phải là phần thân, nó chỉ định hành động của biểu thức lambda
Cũng có 2 kiểu viết phần thân của biểu thức lambda:
Kiểu 1: Biểu thức lambda viết trên một dòng
() -> System.out.println("Phần Lambda trong bài Tự học Lập trình Java");
Kiểu 2: Biểu thức lambda viết như một khối code
() -> {
double pi = 3.1415;
return pi;
}
Biểu thức lambda cũng có thể có tham số, ví dụ:
// Biểu thức lambda có tham số
(n) -> (n%2) == 0
Biểu thức lambda này đánh giá giá trị của n
là chẵn hay lẻ.
Nếu phần thân của biểu thức lambda là một khối code, bạn luôn phải return
một giá trị rõ ràng.
Nhưng, nếu biểu thức lambda được viết trên một dòng, câu lệnh return
là không cần thiết.
Hãy thử viết một ví dụ thực tế với biểu thức lambda mà chỉ cần trả giá trị của Pi.
Như đã nói trước đó, biểu thức lambda không tự thực thi chính nó.
Thay vào đó, nó tạo thành khai triển của các phương thức trừu tượng được xác định bởi Functional Interface.
Trước tiên, hãy tạo một file có tên là IMyInterface.Java
// Đây là một Functional Interface
@FunctionalInterface
public interface IMyInterface{
double getPiValue();
}
Lưu ý!
Chữ I ở đầu tên IMyInterFace là quy tắc đặt tên phổ biến cho Interface
Bây giờ, chúng ta gán biểu thức lambda đến thể hiện của Functional Interface.
public class LambdaMain {
public static void main( String[] args ) {
IMyInterface myInterface;
myInterface = () -> 3.1415;
System.out.println("Giá trị của Pi = " + myInterface.getPiValue());
}
}
Khi chạy chương trình, kết quả nhận được là:
Giá trị của Pi = 3.1415
Biểu thức lambda phải tương thích với những phương thức trừu tượng.
Điều này có nghĩa là, nếu bạn gán () -> "3.1415"
cho myInterface
, thì không hợp lệ và sẽ không làm việc vì kiểu String không tương thích với kiểu double được định nghĩa trong Functional Interface.
Có thể bạn sẽ không sử dụng biểu thức lambda như thế này khi lập trình thực tế.
Hãy thử một ví dụ khác mà biểu thức lambda có một tham số:
// Ví dụ Biểu thức Lambda có tham số
@FunctionalInterface
interface IMyInterface {
String reverse(String n);
}
public class ParamLambdaMain {
public static void main( String[] args ) {
IMyInterface myInterface = (str) -> {
String result = "";
for (int i = str.length() - 1; i >= 0 ; i--)
result += str.charAt(i);
return result;
};
System.out.println("Đảo ngược Lambda = " + myInterface.reverse("Lambda"));
}
}
Khi chạy chương trình, kết quả nhận được là:
Đảo ngược Lambda = adbmaL
Chương IV. Phần 6.3. Generic Functional Interface
Functional Interface ở trên chỉ chấp nhận String và trở về String object.
Tuy nhiên, chúng ta có thể tạo Functional Interface chung, để bất kỳ loại dữ liệu nào cũng được chấp nhận.
Hãy xem cách nó được thực hiện trong ví dụ dưới đây:
// IGenericInterface.java
@FunctionalInterface
interface IGenericInterface<T> {
T func(T t);
}
Bây giờ, IGenericInterface
là tương thích với bất kỳ biểu thức lambda nào có một tham số và trả về giá trị của cùng loại.
// GenericLambda.java
public class GenericLambda {
public static void main( String[] args ) {
IGenericInterface<String> reverse = (str) -> {
String result = "";
for (int i = str.length() - 1; i >= 0 ; i--) {
result += str.charAt(i);
return result;
}
};
System.out.println("Đảo ngược Lambda = " + reverse.func("Lambda"));
IGenericInterface<Integer> factorial = (n) -> {
int result = 1;
for (int i = 1; i <= n; i++) {
result = i * result;
return result;
}
};
System.out.println("Kết quả 5! = " + factorial.func(5));
}
}
Khi chạy chương trình, kết quả nhận được là:
Đảo ngược Lambda = adbmaL
Kết quả 5! = 120
Chương IV. Phần 6.4. Biểu thức Lambda và Stream API
Package mới java.util.stream
đã được thêm vào JDK8 cho phép lập trình viên Java thực hiện các hoạt động như search, filter, map, reduce hoặc thao tác với các tập hợp như List
Ví dụ, chúng ta có một luồng dữ liệu (giả sử là một List các String) là sự kết hợp của tên quốc gia và thành phố.
Bây giờ, chúng ta có thể xử lý luồng dữ liệu này và lấy về các thành phố từ Việt Nam.
Chúng ta có thể thực hiện một số hoạt động trong luồng bởi sự kết hợp của Stream API và Biểu thức Lambda.
// Ví dụ Sử dụng Stream API và Biểu thức Lambda
import java.util.ArrayList;
import java.util.List;
public class StreamMain {
static List<String> city = new ArrayList<>();
// Chuẩn bị dữ liệu
public static List getCity(){
city.add("Vietnam, Hanoi");
city.add("Vietnam, HCM");
city.add("Japan, Kyoto");
city.add("Japan, Tokyo");
city.add("USA, New York");
return city;
}
public static void main( String[] args ) {
List<String> myCity = getCity();
System.out.println("Thành phố của Việt Nam:");
// Lọc thành phố từ Vietnam
myCity.stream()
.filter((c) -> c.startsWith("Vietnam"))
.map((c) -> c.toUpperCase())
.sorted()
.forEach((c) -> System.out.println(c));
}
}
Khi chạy chương trình, kết quả nhận được là:
Thành phố của Việt Nam:
Vietnam, Hanoi
Vietnam, HCM
Stream API cho phép chúng ta truy cập đến các phương thức như filter()
, map()
, foEach()
và lấy biểu thức Lambda như một input.
Chúng ta có thể sử dụng cả các phương thức Java được dựng sẵn hoặc tự định nghĩa các biểu thức riêng sử dụng cú pháp đã học ở trên.
Như thế, chương trình của chúng ta sẽ giảm đi rất nhiều dòng code.
Chương IV. Phần 7. Đệ quy trong Java (Recursion)
Trong phần này, bạn sẽ được tìm hiểu về cách tạo hàm đệ quy (Recursion Function).
Qua phần này, bạn cũng sẽ biết được ưu điểm và nhược điểm của hàm đệ quy và biết sử dụng đúng lúc, đúng chỗ.
Một phương thức tự gọi chính nó được gọi là Phương thức đệ quy.
Ví dụ Đệ quy trong thế giới thực - Tự học Lập trình Java
Trên đây là một ví dụ về để quy trong thế giới thực. Bạn chỉ cần đặt một chiếc gương trước một chiếc gương là có thể tạo ra hình ảnh đệ quy rất thú vị.
Chương IV. Phần 7.1. Đệ quy hoạt động như thế nào?
Trước tiên, để hiểu cách đệ quy hoạt động chúng ta hãy cùng xem hình minh họa:
Ví dụ Minh họa cách đệ quy hoạt động - Tự học lập trình Java
Ở chương trình trên:
-
Phương thức
recurse()
được gọi từ bên trong các phương thứ main (Cuộc gọi bình thường)
-
Ngoài ra, phương thức
recurse()
cũng được gọi từ bên trong chính nó. Đây là một cuộc gọi đệ quy.
Đệ quy vẫn tiếp tục cho đến khi thỏa mãn một số điều kiện ngăn chặn nó thực thi.
Nếu không nó đệ quy vĩnh viễn.
Do đó, để ngăn chặn đệ quy vĩnh viễn, chúng ta có thể sử dụng câu lệnh if...else.
Chương IV. Phần 7.2. Ví dụ đệ quy trong Java.
Để hiểu rõ cách đệ quy hoạt động, hãy cùng làm một ví dụ đệ quy.
Ví dụ này sẽ tính toán giai thừa của một số.
// Ví dụ đệ quy trong Java
class TinhGiaiThua {
static int tinhGiaiThua( int n ) {
if (n != 0)
return n * tinhGiaiThua(n-1); // Cuộc gọi đệ quy
else
return 1;
}
public static void main(String[] args) {
int soCanTinh = 4, ketQua;
ketQua = tinhGiaiThua(soCanTinh);
System.out.println("Kết quả: " + soCanTinh + "! = " + ketQua);
}
}
Khi chạy chương trình, kết quả nhận được là:
Kết quả: 4! = 24
Ban đầu, phương thức tinhGiaiThua()
được gọi từ trong phương thức main
với đối số là soCanTinh
Trong phương thức tinhGiaiThua()
:
-
Lần đầu tiên
soCanTinh
bằng 4
-
Lần tiếp theo
soCanTinh
bằng 3
-
Quá trình gọi cứ thế đến khi
soCanTinh
bằng 0
Khi soCanTinh
bằng 0
, điều kiện if
bị sai, phần else
sẽ được thực thi và trả về 1
.
và ketQua
được truyền đến phương thức main
Chương IV. Phần 7.3. Ưu điểm và Nhược điểm của Đệ quy
Khi một cuộc gọi đệ quy được gọi, vị trí lưu trữ mới cho biến được phân bổ trên stack.
Mỗi khi đệ quy trả về, các biến và tham số cũ được loại ra khỏi stack. Do đó, đệ quy thường sử dụng bộ nhớ nhiều hơn và nói chung là chậm.
Mặt khác, ưu điểm chính của đệ quy là giải pháp đơn giản hơn nhiều và mất ít thời gian để viết, sửa lỗi và duy trì.
Chương IV. Phần 8. Java Instanceof
Trong phần hướng dẫn này, bạn sẽ được học chi tiết về toán tử instanceof trong Java thông qua các ví dụ cụ thể.
Trong Java, từ khóa instanceof
là một toán tử nhị phân.
Nó được sử dụng để kiểm tra xem một đối tượng có phải là một thể hiện của một class cụ thể hay không.
Toán tử này cũng kiểm tra xem một đối tượng có phải là một thể hiện của một class khai triển của một interface hay không (sẽ được thảo luận sau trong hướng dẫn này).
Cú pháp của toán tử instanceof là:
result = objectName instanceof className;
Trong đó:
-
objectName
: Là tên đối tượng
-
result
: true
nếu đối tượng là một thể hiện của class, false
nếu ngược lại.
Ví dụ về toán tử instanceof:
// Ví dụ đơn giản toán tử instanceof
class Main {
public static void main (String[] args) {
String ten = "NIITHANOI.EDU.VN";
Integer tuoi = 17;
System.out.println("ten là thể hiện của String: "+ (ten instanceof String));
System.out.println("tuoi là thể hiện của Integer: "+ (tuoi instanceof Integer));
}
}
Khi chạy chương trình, kết quả nhận được là:
ten là thể hiện của String: true
tuoi là thể hiện của Integer: true
Trong ví dụ trên, chúng ta đã tạo một tên đối tượng ten
của String
và đối tượng tuoi
của loại Integer
.
Sau đó, chúng tôi đã sử dụng toán tử instanceof
để kiểm tra xem ten
có thuộc kiểu String
hay không và tuoi
có thuộc loại Integer
hay không.
Chương IV. Phần 8.1. Sử dụng toán tử instanceof trong kế thừa
Trong trường hợp kế thừa, toán tử instanceof
được sử dụng để kiểm tra xem một đối tượng của lớp con có phải là một thể hiện của lớp cha hay không.
// Ví dụ sử dụng instanceof trong kế thừa
class DongVat {
}
class Cho extends DongVat {
}
class Main {
public static void main(String[] args){
Cho cho = new Cho();
System.out.println("cho là thể hiện của Cho: "+ (cho instanceof Cho));
System.out.println("cho là thể hiện của DongVat: "+ (cho instanceof DongVat));
}
}
Khi chạy chương trình, kết quả nhận được là:
cho là thể hiện của Cho: true
cho là thể hiện của DongVat: true
Trong ví dụ trên, cho
là một thể hiện của cả lớp Cho
và DongVat
.
Do đó, cả cho instanceof Cho
và cho instanceof DongVat
đều trả về giá trị true
.
Chương IV. Phần 8.2. Class Object
Trong Java, tất cả các class được kế thừa từ class Object
.
Trong quá trình kế thừa class Object, chúng ta không cần từ khóa extends
Đây là một ngoại lệ trong Java.
// Ví dụ về class Object
class DongVat {
}
class Cho {
}
class Meo {
}
class Main {
public static void main(String[] args) {
Cho cho = new Cho();
DongVat dv = new DongVat();
Meo meo = new Meo();
System.out.println("cho là thể hiện của Object: "+ (cho instanceof Object));
System.out.println("dv là thể hiện của Object: "+ (dv instanceof Object));
System.out.println("meo là thể hiện của Object: "+ (meo instanceof Object));
}
}
Khi chạy chương trình, kết quả nhận được là:
cho là thể hiện của Object: true
dv là thể hiện của Object: true
meo là thể hiện của Object: true
Như bạn thấy đấy, kết quả đã chứng minh tất cả đều là thể hiện của class Object
.
Điều này là do class Object
là class root (gốc) được định nghĩa trong package java.lang
.
Tất cả các class khác đều là các class con của class Object tạo thành một hệ thống phân cấp trong Java.
Chương IV. Phần 8.3. Object Upcasting và Downcasting trong Java
Trong Java, một đối tượng của một class con có thể được coi là một đối tượng của class cha.
Điều này được gọi là upcasting.
Trình biên dịch Java tự động thực hiện upcasting.
// Ví dụ upcasting trong Java
class DongVat {
public void hienThiThongTin() {
System.out.println("Tôi là động vật.");
}
}
class Cho extends DongVat {
}
class Main {
public static void main(String[] args) {
Cho cho = new Cho();
DongVat dv = cho;
dv.hienThiThongTin();
}
}
Khi chạy chương trình, kết quả nhận được là:
Tôi là động vật.
Trong ví dụ trên, chúng ta đã tạo một đối tượng cho
của class Cho
.
Chúng ta đã sử dụng đối tượng cho đó để tạo một đối tượng dv
của class DongVat
với tính năng upcasting.
Code thực thi mà không có bất kỳ vấn đề. gì.
Điều này là do upcasting được tự động thực hiện bởi trình biên dịch Java.
Downcasting thì ngược lại với Upcasting.
Trong trường hợp downcasting, một đối tượng của lớp cha được coi là một đối tượng của lớp con.
Chúng ta phải hướng dẫn rõ ràng trình biên dịch để thực hiện downcasting trong Java.
// Ví dụ downcasting trong Java
class DongVat {
}
class Meo extends DongVat {
public void hienThiThongTin() {
System.out.println("Tôi là một con mèo.");
}
}
class Main {
public static void main(String[] args) {
Meo meo1 = new Meo();
DongVat dv = meo1; // Upcasting
Meo meo2 = (Meo)dv; // Downcasting
meo2.hienThiThongTin();
}
}
Khi chúng ta chạy chương trình, chúng tôi sẽ nhận được ngoại lệ ClassCastException
.
Tại sao lại thế nhỉ?
Ở đây, chúng ta đã tạo ra một đối tượng dv
của class cha DongVat
.
Sau đó chúng ta đã cố gắng ép đối tượng dv
đến đối tượng meo1
của lớp con Meo
.
Điều này đã gây ra vấn đề.
Đó là bởi vì đối tượng dv
của lớp cha DongVat
cũng có thể tham chiếu đến các lớp con khác dẫn tới sự mơ hồ.
Mà mơ hồ là không thể được chấp nhận trong Java.
Để giải quyết vấn đề này, chúng ta có thể sử dụng toán tử instanceof
. Đây là cách thực hiện:
// Ví dụ sửa lỗi Downcasting bằng instanceof
class DongVat {
}
class Meo extends DongVat {
public void hienThiThongTin() {
System.out.println("Tôi là một con mèo.");
}
}
class Main {
public static void main(String[] args) {
Meo meo1 = new Meo();
DongVat dv = meo1; // Upcasting
if (dv instanceof Meo){
Meo meo2 = (Meo)dv; // Downcasting
meo2.hienThiThongTin();
}
}
}
Khi chạy chương trình, kết quả nhận được là:
Tôi là một con mèo.
Trong ví dụ trên, chúng ta đã sử dụng toán tử instanceof
để kiểm tra xem đối tượng dv
có phải là một thể hiện của class Meo
hay không.
Việc downcasting chỉ được thực hiện khi biểu thức dv instanceof Meo
là true
.
Chương IV. Phần 8.4. instanceof trong Interface
Toán tử instanceof cũng được sử dụng để kiểm tra xem một đối tượng của một class cũng là một thể hiện của interface mà từ đó class khai triển hay không.
// Kiểm tra đối tượng có phải thể hiện của interface
// mà class đó khai triển hay không
interface IDongVat {
}
class Meo implements IDongVat {
}
class Main {
public static void main(String[] args) {
Meo meo = new Meo();
System.out.println("meo là thể hiện của IDongVat: "+(meo instanceof IDongVat));
}
}
Khi chạy chương trình, kết quả nhận được là:
meo là thể hiện của IDongVat: true
Trong ví dụ trên, chúng tađã tạo ra một class Meo
khai triển interface IDongVat
Sau đó, đối tượng meo
của class Meo
được tạo.
Chúng ta đã sử dụng toán tử instanceof
để kiểm tra xem đối tượng meo
có phải là một thể hiện của interface IDongVat
hay không?
Kết quả đã chứng minh đúng là như vậy.
CHƯƠNG V. TỰ HỌC LẬP TRÌNH JAVA HƯỚNG ĐỐI TƯỢNG (Part 2)
Chương V. Phần 1. Kế thừa trong Java
Trong phần này, bạn sẽ được hướng dẫn tự học về tính kế thừa trong Java với sự trợ giúp của các ví dụ.
Kế thừa là một trong những tính năng chính của OOP (Lập trình hướng đối tượng) cho phép chúng ta định nghĩa một class từ một class đã có.
Ví dụ:
// Ví dụ về kế thừa trong Java
class DongVat {
// Phương thức an()
// Phương thức ngu()
}
class Cho extends DongVat {
// Phương thức keu()
}
Trong Java, chúng ta sử dụng từ khóa extends
để kế thừa từ một class.
Ở đây, chúng ta đã kế thừa class Cho
từ class DongVat
Khi đó, class DongVat
được gọi là superclass (class cha hoặc class cơ sở) và Cho
là subclass (class con hoặc class dẫn xuất).
Class con kế thừa các trường và phương thức của class cha.
Ảnh minh họa kế thừa trong Java
V. Phần 1.1. Mối quan hệ IS-A trong Java.
Kế thừa là một mối quan hệ IS-A. Chúng ta chỉ sử dụng kế thừa nếu giữa hai class có mối quan hệ IS-A.
Đây là một số ví dụ:
-
Xe ô tô là một phương tiện
-
Quả cam là một loại hoa quả
-
Bác sĩ đa khoa là một bác sĩ
Một ví dụ khác về kế thừa trong Java:
class DongVat {
public void an() {
System.out.println("Đang ăn...");
}
public void ngu() {
System.out.println("Đang ngủ...");
}
}
class Cho extends DongVat {
public void sua() {
System.out.println("Gâu gâu gâu");
}
}
class Main {
public static void main(String[] args) {
Cho cauVang = new Cho();
cauVang.an();
cauVang.ngu();
cauVang.sua();
}
}
Khi chạy chương trình, kết quả nhận được là:
Đang ăn...
Đang ngủ...
Gâu gâu gâu
Ở đây, chúng ta đã thực hiện kế thừa class Cho
từ superclass là DongVat
.
Class Cho
lúc này sẽ kế thừa cả các phương thức an()
và ngu()
từ class DongVat
.
Do đó, các đối tượng của lớp Cho có thể truy cập vào các thành viên của cả class Cho
và DongVat
.
Chương V. Phần 5.2. Từ khóa protected trong kế thừa
Chúng ta đã được học về từ khóa private và public trong phần trước:
-
Thành viên
private
chỉ có thể truy cập bên trong class
-
Thành viên
public
có thể được truy cập từ bất cứ đâu
Ngoài ra, bạn cũng có thể khai báo các phương thức và các trường là protected
.
Thành viên pretected có thể được truy cập:
-
Từ bên trong subclass của nó
-
Từ bên trong cùng package
Ví dụ về kế thừa có sử dụng từ khóa protected.
class DongVat {
protected String giong;
private String mauSac;
public void an() {
System.out.println("Đang ăn...");
}
public void ngu() {
System.out.println("Đang ngủ...");
}
public String getMauSac(){
return mauSac;
}
public void setMauSac(String mau){
mauSac = mau;
}
}
class Cho extends DongVat {
public void thongTin(String m){
System.out.println("Giống chó: " + giong);
System.out.println("Màu sắc: " + m);
}
public void sua() {
System.out.println("Gâu gâu gâu");
}
}
class Main {
public static void main(String[] args) {
Cho cauVang = new Cho();
cauVang.an();
cauVang.ngu();
cauVang.sua();
cauVang.giong = "Chó cỏ";
cauVang.setMauSac("Vàng");
cauVang.thongTin(cauVang.getMauSac());
}
}
Khi chạy chương trình, kết quả nhận được là:
Đang ăn...
Đang ngủ...
Gâu gâu gâu
Giống chó: Chó cỏ
Màu sắc: Vàng
Ở đây, trường giong
bên trong class DongVat
là protected
.
Chúng ta đã truy cập trường này trong class main bằng cách sử dụng:
cauVang.giong = "Chó cỏ";
Điều này có thể, bởi vì class DongVat
và class Main
nằm trong cùng một package (cùng file).
V. Phần 1.3. Phương thức Overriding
Từ các ví dụ trên, chúng ta biết rằng các đối tượng của một class con cũng có thể truy cập các phương thức của class cha của nó.
Vậy,
Điều gì xảy ra nếu có một phương thức được định nghĩa trong cả class cha và class con?
Câu hỏi rất hay!
Trong trường hợp đó, phương thức trong class con sẽ ghi đè phương thức trong class cha.
Ví dụ:
// Ví dụ Overloading trong Java
class DongVat {
protected String giong = "Động vật";
public void an() {
System.out.println("Đang ăn...");
}
public void ngu() {
System.out.println("Đang ngủ...");
}
}
class Cho extends DongVat {
@Override
public void an() {
System.out.println("Đang ăn cơm...");
}
public void sua() {
System.out.println("Gâu gâu gâu");
}
}
class Main {
public static void main(String[] args) {
Cho cauVang = new Cho();
cauVang.an();
cauVang.ngu();
cauVang.sua();
}
}
Khi chạy chương trình, kết quả nhận được là:
Đang căn cơm...
Đang ngủ...
Gâu gâu gâu
Ở đây, phương thức an()
có mặt trong cả class DongVat
và class Cho
.
Khi chúng ta đã tạo một đối tượng cauVang của class Cho.
Sau đó chúng ta gọi phương thức an()
bằng cách sử dụng đối tượng cauVang
, phương thức bên trong class Cho
được gọi mà phương thức tương tự của class DongVat
không được gọi.
Đây được gọi là ghi đè phương thức (Method Overriding)
Lưu ý!
> Trong chương trình trên, chúng tôi đã sử dụng @Override
để báo cho trình biên dịch rằng chúng ta đang ghi đè một phương thức.
> Nó không bắt buộc. Nhưng khuyến khích sử dụng để tăng khả năng đọc của chương trình.
Nếu chúng ta cần gọi phương thức an()
của class DongVat
từ các class con của nó, chúng ta sử dụng từ khóa super
.
// Ví dụ sử dụng từ khóa super trong Java
class DongVat {
public DongVat() {
System.out.println("Tôi là động vật");
}
public void an() {
System.out.println("Đang ăn...");
}
}
class Cho extends DongVat {
public Cho(){
super();
System.out.println("Tôi là một chú chó");
}
@Override
public void an() {
super.an();
System.out.println("Tôi đang ăn cơm");
}
public void sua() {
System.out.println("Gâu gâu gâu");
}
}
class Main {
public static void main(String[] args) {
Cho cauVang = new Cho();
cauVang.an();
cauVang.sua();
}
}
Khi chạy chương trình, kết quả nhận được là:
Tôi là động vật
Tôi là một chú chó
Đang ăn...
Tôi đang ăn cơm
Gâu gâu gâu
Ở đây, chúng ta đã sử dụng từ khóa super
để gọi đến constructor.
Ngoài ra, chúng ta đã gọi phương thức an()
của superclass DongVat bằng câu lệnh super.an()
.
Lưu ý!
> Có sự khác biệt trong việc sử dụng từ khóa super trong khi gọi constructor và gọi phương thức.
V. Phần 1.4. Các kiểu kế thừa
Có 5 kiểu kế thừa trong Java:
-
Kế thừa đơn: Class
B
chỉ kế thừa từ class A
-
Kế thừa đa cấp: Class
B
kế thừa từ class A
. Sau đó class C
kế thừa class B
.
-
Kế thừa phân cấp: Class
A
là cha của class B
, C
, D
, E
...
-
Đa kế thừa: Class
C
kế thừa từ interface A
và B
...
-
Kế thừa lai: Kết hợp của hai hay nhiều loại kế thừa
Lưu ý!
> Java không hỗ trợ đa kế thừa và kế thừa lai thông qua các class. Tuy nhiên, chúng ta có thể đạt được đa kế thừa trong Java thông qua các interface.
> Còn interface là gì thì chúng ta sẽ được học sau.
Như bạn đã được học về kế thừa trong Java ở trên, chúng ta sử dụng kế thừa vì lý do sau:
-
Lý do quan trọng nhất là khả năng tái sử dụng code. Code có trong class cha không cần phải viết lại trong class con.
-
Lý do tiếp nữa là để đạt được tính đa hình trong runtime thông qua việc ghi đè phương thức. Chúng ta sẽ tìm hiểu thêm về tính đa hình trong các phần sau.
Chương V. Phần 2. Ghi đè phương thức trong Java.
Bạn đã được tiếp xúc qua về ghi đè phương thức ở phần trên, trong phần này, chúng ta sẽ học kỹ hơn thông qua các ví dụ.
Như bạn đã biết, Kế thừa là một thuộc tính của lập trình hướng đối tượng cho phép chúng ta tạo ra được một class mới (class con) từ một class khác (class cha).
Các class con kế thừa những đặc tính và phương thức của class cha.
Bây giờ, nếu các phương thức tương tự được định nghĩa trong cả hai class cha và class con, thì...
Phương thức của class con sẽ ghi đè lên phương thức của class cha.
Đây được gọi là ghi đè phương thức (Method Overriding)
// VD1: Ghi đè phương thức
class DongVat {
public void hienThiThongTin() {
System.out.println("Tôi là Động vật");
}
}
class Cho extends DongVat {
@override
public void hienThiThongTin() {
System.out.println("Tôi là một chú chó");
}
}
class Main {
public static void main(String[] args) {
Cho cauVang = new Cho();
cauVang.hienThiThongTin();
}
}
Khi chạy chương trình, kết quả nhận được là:
Tôi là một chú chó
Trong chương trình trên, chúng ta đã định nghĩa phương thức có tên là hienThiThongTin()
trong class DongVat
.
Khi tạo class Cho
kế thừa class Dongvat
, chúng ta lại định nghĩa phương thức có tên là hienThiThongTin()
.
Như vậy,
Khi tạo đối tượng cauVang
từ class Cho
và gọi phương thức hienThiThongTin()
thì phương thức hienThiThongTin()
của class Cho
sẽ được gọi (thay vì phương thức của class cha)
Lưu ý!
> Chúng ta sử dụng @override
để thông báo cho trình biên dịch biết chúng ta định ghi đè phương thức.
> Nó không phải bắt buộc. Nhưng khi sử dụng, phương thức được đánh dấu @override
phải tuân theo quy tắc ghi đè phương thức.
> Nếu không sẽ gây ra lỗi biên dịch.
Chương V. Phần 2.1. Quy tắc ghi đè phương thức trong Java.
Trong Java, muốn ghi đè phương thức phải tuân theo 3 quy tắc sau:
-
Cả class cha và class con phải có cùng tên phương thức, cùng một kiểu trả về và cùng danh sách tham số.
-
Không thể ghi đè phương thức
static
và final
.
-
Chúng ta phải luôn ghi đè lên phương thức trừu tượng của class cha (Bạn sẽ được học trong phần phương thức trừu tượng).
Chương V. Phần 2.2. Cách sử dụng từ khóa super trong ghi đè phương thức
Có một câu hỏi phổ biến trong khi ghi đè phương thức.
Liệu chúng ta có thể gọi phương thức đã bị ghi đè không?
Câu trả lời là: Có! Chúng ta có thể.
Để truy cập phương thức đã bị ghi đè. Chúng ta cần sử dụng từ khóa super.
// VD2: Gọi phương thức đã bị ghi đè
class DongVat {
public void hienThiThongTin() {
System.out.println("Tôi là Động vật");
}
}
class Cho extends DongVat {
@override
public void hienThiThongTin() {
super.hienThiThongTin();
System.out.println("Tôi là một chú chó");
}
}
class Main {
public static void main(String[] args) {
Cho cauVang = new Cho();
cauVang.hienThiThongTin();
}
}
Kết quả nhận được sau khi chạy chương trình là:
Tôi là Động vật
Tôi là một chú chó
Trong ví dụ trên, class Cho
có phương thức hienThiThongTin()
của class cha DongVat
.
Khi chúng ta gọi phương thức hienThiThongTin()
qua đối tượng cauVang
(được tạo từ class Cho
), phương thức của class Cho
được gọi.
Tuy nhiên,
Bên trong phương thức đó chúng ta sử dụng super.hienThiThongTin()
có nghĩa là gọi đến phương thức hienThiThongTin()
cua class cha.
Lưu ý!
> Constructor trong Java không được kế thừa. Do đó, không thể ghi đè constructor.
> Và chúng ta vẫn có thể gọi constructor của class cha từ class con bằng super() (Xem kỹ hơn ở phần sau)
Chương V. Phần 2.3. Chỉ định mức độ truy cập trong ghi đè phương thức
Các phương thức tương tự có trong cả class cha và class con có thể có mức độ truy cập khác nhau.
Tuy nhiên, nó có giới hạn.
Đối với phương thức trong class con, chúng ta chỉ có thể sử dụng mức độ truy cập có phạm vi lớn hơn của phương thức trong class cha.
Ví dụ,
Giả sử, một phương pháp hienThiThongTin()
trong class cha được khai báo là protected.
Sau đó, các phương thức tương tự hienThiThongTin()
trong class con có thể là public
hay protected
(Không thể là private
)
// VD3: Mức độ truy cập trong ghi đè phương thức
class DongVat {
protected void hienThiThongTin() {
System.out.println("Tôi là Động vật");
}
}
class Cho extends DongVat {
@override
public void hienThiThongTin() {
System.out.println("Tôi là một chú chó");
}
}
class Main {
public static void main(String[] args) {
Cho cauVang = new Cho();
cauVang.hienThiThongTin();
}
}
Khi chạy chương trình, kết quả nhận được là:
Tôi là một chú chó
Lưu ý rằng, phương thức hienThiThongTin()
trong class con có mức độ truy cập là public
lớn hơn mức độ truy cập protected
của phương thức hienThiThongTin()
trong class con.
Chương V. Phần 3. Từ khóa super trong Java.
Trong hướng dẫn tự học Java này, chúng ta sẽ tìm hiểu về từ khóa super trong Java thông qua các ví dụ cụ thể.
Từ khóa super trong Java được sử dụng trong các class con để truy cập các thành viên của superclass (thuộc tính, constructor và phương thức).
Trước khi chúng ta tìm hiểu về từ khóa super, bạn cần phải hiểu hết các ví dụ ở phần Kế thừa trong Java đã nhé.
Có 3 trường hợp chúng ta sẽ sử dụng từ khóa super:
-
Để gọi các phương thức của class cha được ghi đè trong class con.
-
Để truy cập các thuộc tính (trường) của class cha nếu cả class cha và class con có các thuộc tính trùng tên.
-
Để gọi một cách rõ ràng constructor không đối số (theo mặc định) hoặc có đối số từ constructor của class con.
Chương V. Phần 3.1. Sử dụng từ khóa super để truy cập phương thức bị ghi đè của class cha
Nếu các phương thức có cùng tên được định nghĩa trong cả class cha và class con, thì phương thức trong class con sẽ ghi đè phương thức trong class cha.
Đây được gọi là ghi đè phương thức.
class DongVat {
// Phương thức bị ghi đè
public void hienThiThongTin(){
System.out.println("Tôi là Động vật");
}
}
class Cho extends DongVat {
// Ghi đè phương thức của class cha
@Override
public void hienThiThongTin(){
System.out.println("Tôi là một chú chó");
}
public void thongBao(){
hienThiThongTin();
}
}
class Main {
public static void main(String[] args) {
Cho cauVang = new Cho();
cauVang.thongBao();
}
}
Khi chạy chương trình, kết quả nhận được là:
Tôi là một chú chó
Trong ví dụ này, bằng cách tạo một đối tượng cauVang
của lớp Cho
, chúng ta có thể gọi phương thức thongBao()
của nó để thực hiện câu lệnh hienThiThongTin()
.
Vì hienThiThongTin()
được định nghĩa trong cả hai class, nên phương thức của class con Cho
sẽ ghi đè lên phương thức của class cha DongVat
.
Do đó, phương thức hienThiThongTin()
của class con được gọi.
Vậy,
Nếu bạn vẫn cứ muốn gọi phương thức đã bị ghi đè của class cha thì sao?
Điều này hoàn toàn có thể được.
Lúc này, chúng ta cần sử dụng super.hienThiThongTin()
nếu bạn cần gọi phương thức hienThiThongTin()
của class cha.
class DongVat {
// Phương thức bị ghi đè
public void hienThiThongTin(){
System.out.println("Tôi là Động vật");
}
}
class Cho extends DongVat {
// Ghi đè phương thức của class cha
@Override
public void hienThiThongTin(){
System.out.println("Tôi là một chú chó");
}
public void thongBao(){
// Gọi phương thức của class con
hienThiThongTin();
// Gọi phương thức của class cha
super.hienThiThongTin();
}
}
class Main {
public static void main(String[] args) {
Cho cauVang = new Cho();
cauVang.thongBao();
}
}
Khi chạy chương trình, kết quả nhận được là:
Tôi là một chú chó
Tôi là Động vật
Chương V. Phần 3.2. Sử dụng từ khóa super để truy cập thuộc tính của class cha trong Java.
Class cha và class con có thể có các thuộc tính có cùng tên.
Do đó, nếu muốn truy cập thuộc tính của class cha (từ trong class con) thì chúng ta cần đến từ khóa super:
class DongVat {
protected String giong ="Động vật";
}
class Cho extends DongVat {
public String giong = "Chó cỏ";
public void inGiongCho() {
System.out.println("Tôi là " + giong);
System.out.println("Tôi là " + super.giong);
}
}
class Main {
public static void main(String[] args) {
Cho cauVang = new Cho();
cauVang.inGiongCho();
}
}
Khi chạy chương trình, kết quả nhận được là:
Tôi là Chó cỏ
Tôi là Động vật
Trong ví dụ này, chúng ta đã định nghĩa cùng một loại thuộc tính trong cả class DongVat
và class Cho
.
Sau đó chúng ta đã tạo ra một đối tượng cauVang
của class Cho
.
Sau đó nữa, gọi phương thức inGiongCho()
thông qua đối tượng này.
Bên trong hàm inGiongCho()
có:
-
giong
đề cập đến thuộc tính của class con Cho
-
super.giong
đề cập đến thuộc tính của class DongVat
Do đó, chúng ta có kết quả như trên.
Chương V. Phần 3.3. Sử dụng từ khóa super truy cập constructor của class cha
Như chúng ta biết, khi một đối tượng của một class được tạo, constructor mặc định của nó sẽ tự động được gọi.
Để gọi một cách rõ ràng constructor của class cha từ constructor của class con, chúng ta sử dụng super()
.
Đó là một hình thức đặc biệt của từ khóa super.
super()
chỉ có thể được sử dụng bên trong constructor của class con và phải là câu lệnh đầu tiên.
class DongVat {
// Constructor mặc định không đối số
// của class cha
DongVat() {
System.out.println("Tôi là Động vật");
}
}
class Cho extends DongVat {
// Constructor mặc định không đối số
// của class con
Cho() {
// Gọi constructor mặc định của class cha
super();
System.out.println("Tôi là một chú chó");
}
}
class Main {
public static void main(String[] args) {
Cho cauVang = new Cho();
}
}
Khi chạy chương trình, kết quả nhận được là:
Tôi là Động vật
Tôi là một chú chó
Ở đây, khi một đối tượng cauVang
của class Cho
được tạo, nó sẽ tự động gọi constructor mặc định có hoặc không có đối số của class đó.
Bên trong hàm tạo của class con, câu lệnh super()
gọi đến constructor của class cha và thực hiện các câu lệnh bên trong nó.
Do đó, chúng tôi nhận được kết quả Tôi là Động vật
đầu tiên.
Tuy nhiên, không bắt buộc sử dụng super()
.
Ngay cả khi super()
không được sử dụng trong constructor của class con, trình biên dịch sẽ gọi constructor mặc định của class cha.
Vậy,
Khi nào cần phải gọi rõ ràng?
Câu trả lời là khi bạn muốn gọi constructor có đối số của class cha từ trong constructor của class con.
Và super()
đại diện cho constructor có đối số phải là câu lệnh đầu tiên trong phần thân của constructor của class con, nếu không, chúng ta sẽ gặp lỗi biên dịch.
class DongVat {
// Constructor mặc định không đối số
DongVat() {
System.out.println("Tôi là Động vật");
}
// Constructor có đối số
DongVat(String giong) {
System.out.println("Giống: " + giong);
}
}
class Cho extends DongVat {
// Constructor mặc định
Dog() {
// Gọi constructor có đối số
// của class cha
super("Chó");
System.out.println("Tôi là một chú chó");
}
}
class Main {
public static void main(String[] args) {
Cho cauVang = new Cho();
}
}
Khi chạy chương trình, kết quả nhận được là:
Giống: Chó
Tôi là một chú chó
Trình biên dịch có thể tự động gọi constructor không đối số. Tuy nhiên, nó không thể gọi các constructor có đối số.
Nếu cần gọi một constructor có đối số trong constructor của class con, chúng ta cần phải gọi rõ ràng.
Lưu ý!
> Trong ví dụ trên, chúng ta gọi một rõ ràng super("Chó"). Và trình biên dịch đã không gọi constructor mặc định của class cha.
Chương V. Phần 4. Class trừu tượng (Abstract Class) và Phương thức trừu tượng (Abstract Method)
Trong phần này, chúng ta sẽ học nhanh về trừu tượng trong Java.
Class trừu tượng và phương thức trừu tượng là gì? Làm cách nào để sử dụng chúng trong khi lập trình?
Hãy tiếp tục theo dõi bên dưới đây:
Chương V. Phần 4.1 Class trừu trượng trong Java
Class trừu tượng là một class không thể khởi tạo. Có nghĩa là ta không thể tạo đối tượng từ class trừu tượng.
Trong Java, chúng ta sử dụng từ khóa abstract
để định nghĩa class trừu tượng.
// Cú pháp Class trừu tượng
abstract class DongVat {
// Thuộc tính
// Phương thức
}
Nếu chúng ta cố gắng khởi tạo đối tượng từ class trừu tượng như thế này:
DongVat Cho = new DongVat();
Chúng ta sẽ nhận được lỗi biên dịch:
DongVat is abstract; cannot be instantiated
Mặc dù class trừu tượng không thể khởi tạo, chúng ta có thể tạo class con từ nó.
Chúng ta có thể tạo ra các đối tượng của class con và sử dụng nó để truy cập vào các thành viên của class trừu tượng.
Trước khi chúng ta tìm hiểu chi tiết, bạn cần phải hiểu về phương thức trừu tượng.
Chương V. Phần 4.2. Phương thức trừu tượng trong Java
Bên cạnh class trừu tượng, chúng ta cũng có thể sử dụng từ khóa abstract để khai báo một phương thức.
Một phương thức trừu tượng được khai báo mà không có khai triển. Ví dụ,
abstract void diTe();
Ở đây, phương thức trừu tượng có tên là diTe()
.
Nhưng vì nó là phương thức trừu tượng, nên nó không có phần thân, thay vào đó là một đấu ;
Quan trọng là, chỉ có class trừu tượng mới có thể chứa phương thức trừu tượng.
Nếu không, chương trình sẽ lỗi.
Một class trừu tượng có thể chứa cả phương thức trừu tượng hoặc không trừu tượng.
Đây là một ví dụ:
abstract class DongVat{
// Phương thức thông thường
public void hienThiThongTin(){
System.out.println("Tôi là Động vật");
}
// Phương thức trừu tượng
abstract void diTe();
}
Chương V. Phần 4.3. Kế thừa class trừu tượng trong Java
Một class trừu tượng không thể được khởi tạo.
Vì thế, để truy cập thành viên của class trừu tượng, chúng ta thực hiện kế thừa nó, ví dụ:
abstract class DongVat{
public void hienThiThongTin(){
System.out.println("Tôi là Động vật");
}
}
class Cho extends DongVat{
}
class Main{
public static void main(String[] args){
Cho cauVang = new Cho();
cauVang.hienThiThongTin();
}
}
Khi chạy chương trình, kết quả nhận được là:
Tôi là Động vật
Trong ví dụ trên, chúng ta đã tạo ra một class trừu tượng là DongVat
.
Chúng ta không thể tạo ra các đối tượng của class DongVat
.
Thế nên, để truy cập vào phương thức hienThiThongTin()
của class DongVat
, chúng ta đã tạo một class Cho
kế thừa từ class DongVat
.
Sau đó chúng ta sử dụng đối tượng cauVang
được tạo từ class Cho
để truy cập vào phương thức hienThiThongTin()
của class DongVat
.
Như bạn thấy kết quả, chúng ta đã thành công.
Chương V. Phần 4.5. Ghi đè phương thức trừu tượng
Trong Java, bắt buộc phải ghi đè lên phương thức trừu tượng của class cha khi thực hiện kế thừa.
Lưu ý!
> Nếu các class con cũng được khai báo là trừu tượng, thì không bắt buộc phải ghi đè lên phương thức trừu tượng của class cha.
// Ghi đè phương thức trừu tượng
abstract class DongVat {
abstract void diTe();
public void an() {
System.out.println("Tôi đang ăn...");
}
}
class Cho extends DongVat {
@override
public void diTe() {
System.out.println("Xồ xồ xồ...");
}
}
class Main {
public static void main(String[] args) {
Cho cauVang = new Cho();
cauVang.diTe();
cauVang.an();
}
}
Khi chạy chương trình, kết quả nhận được là:
Xồ xồ xồ...
Tôi đang ăn...
Bạn có thể thấy, chúng ta đã thực hiện ghi đè phương thức diTe()
của class trừu tượng DongVat
.
Khi đó, trong class con Cho
chúng ta có thể khai triển cụ thể phương thức này.
Chương V. Phần 4.6. Truy cập constructor của class trừu tượng.
Tương tự như trong class thông thường, chúng ta truy cập vào constructor của một class trừu tượng bằng cách sử dụng từ khóa super.
Ví dụ:
// Truy cập constructor của class trừu tượng
abstract class DongVat {
DongVat() {
….
}
}
class Cho extends DongVat {
Cho() {
super();
...
}
}
Ở đây, chúng ta đã sử dụng super()
bên trong constructor của class con Cho
để truy cập vào constructor của class cha DongVat
.
Lưu ý!
> super
nên luôn luôn là câu lệnh đầu tiên trong constructor của class con.
Trừu tượng nghe có vẻ lằng nhằng nhỉ?
Vậy,
Chương V. Phần 4.7. Tại sao cần sử dụng trừu tượng?
Trừu tượng là một khái niệm quan trọng của lập trình hướng đối tượng.
Trừu tượng chỉ cho thấy những thông tin cần thiết và tất cả các chi tiết không cần thiết được giữ kín.
Điều này cho phép chúng ta quản lý sự phức tạp của những chi tiết đơn giản dễ bị bỏ quên / ẩn đi.
Ví dụ, Chúng ta đều biết khi bấm phanh thì xe sẽ dừng lại. Tuy nhiên, cụ thể phanh xe làm việc như thế nào thì chúng ta không biết.
Lợi ích của việc ẩn đi cụ thể giúp nhà sản xuất có thể tạo ra nhiều loại phanh khác nhau (Phanh xe máy, phanh ô tô, phanh đường trơn trượt, ...)
Cách nó làm việc khác nhau nhưng về cơ bản phanh là như thế.
Hãy lấy một ví dụ giúp chúng ta hiểu rõ về trừu tượng trong Java.
// Ví dụ về trừu tượng trong Java
abstract class DongVat {
abstract void diTe();
}
class Cho extends DongVat {
public void diTe() {
System.out.println("Xồ xồ xồ...");
}
}
class Meo extends DongVat {
public void diTe() {
System.out.println("Xì xì xì...");
}
}
class Main {
public static void main(String[] args) {
Cho cauVang = new Cho();
cauVang.diTe();
Meo quangThuong = new Meo();
quangThuong.diTe();
}
}
Khi chạy chương trình, kết quả nhận được là:
Xồ xồ xồ...
Xì xì xì...
Trong ví dụ trên, chúng ta đã tạo ra một class cha DongVat
.
Trong đó, class cha DongVat
có một phương thức trừu tượng diTe()
.
Phương thức diTe()
không thể được khai triển bên trong DongVat
. Đó là vì tất cả các động vật đi tè khác nhau.
Chúng ta không thể khai triển diTe()
trong DongVat
mà nó có thể đúng cho tất cả các class con.
Vì vậy, tất cả các class con của class DongVat
sẽ thực hiện diTe()
khác nhau.
Thế nên, tốt nhất là ẩn khai triển phương thức diTe()
trong DongVat
đi.
Chương V. Phần 4.8. Những điểm cần nhớ sau khi học trừu tượng trong Java.
Đây là những điểm cần nhớ khi sử dụng trừu tượng trong lập trình Java:
-
Chúng ta sử dụng các từ khóa
abstract
để tạo ra class / phương thức trừu tượng.
-
Phương thức trừu tượng không có khai triển cụ thể.
-
Một class chứa phương thức trừu tượng phải là trừu tượng
-
Chúng ta không thể khởi tạo đối tượng từ class trừu tượng
-
Để khai triển tính năng trừu tượng của một class, chúng ta thực hiện kế thừa nó.
-
Một class con phải ghi đè lên tất cả các phương thức của một lớp trừu tượng. Tuy nhiên, nếu các lớp con cũng khai báo là abstract, nó không bắt buộc phải ghi đè lên phương thức trừu tượng.
-
Chúng ta có thể truy cập vào các thuộc tính / phương thức static của một class trừu tượng bằng cách sử dụng tham chiếu của class trừu tượng, ví dụ:
DongVat.staticMethod();
Chương V. Phần 5. Học sử dụng Interface trong Java
Trong phần này, chúng ta sẽ tìm hiểu về interface trong Java. Chúng ta sẽ học cách khai triển các interface và hiểu được khi nào nên sử dụng interface qua các ví dụ.
Trong Java, interface là một tập hợp mà tập hợp các đặc tả mà các class khác sẽ khai triển.
Ví dụ:
// Ví dụ interface trong Java
interface HinhDaGiac {
public void tinhDienTich();
}
Chúng ta đã sử dụng từ khóa interface
để khai báo một interface. Do đó, HinhDaGiac
là một interface.
Phương thức tinhDienTich() được khai báo trong interface (chưa có khai triển cụ thể).
Tất cả các class sử dụng interface này phải khai triển phương thức tinhDienTich()
.
Một interface có thể bao gồm các phương thức trừu tượng và hằng số. Ví dụ:
interface HinhDaGiac {
public static final String mauSac = "Xanh";
public void tinhDienTich();
}
Ở trong ví dụ trên, chúng ta tạo một interface là HinhDaGiac
.
Trong interface này có chứa hằng số mauSac, phương thức trừu tượng tinhDienTich()
Tuy nhiên, tất cả các phương thức trong interface được ngầm định là public
, và các trường được ngầm định là hằng số public static final
Do đó, ví dụ trên có thể viết lại thành như thế này:
interface HinhDaGiac {
String mauSac = "Xanh";
void tinhDienTich();
}
Chương V. Phần 5.1. Từ khóa implements trong Interface
Giống như các class trừu tượng, chúng ta không thể tạo các đối tượng trực tiếp từ interface.
Tuy nhiên, chúng ta có thể khai triển các interface trong các class khác.
Trong Java, chúng ta sử dụng từ khóa implements
để thực hiện các interface. Ví dụ:
// Ví dụ khai triển interface trong Java
interface HinhDaGiac {
void tinhDienTich(int chieuDai, int chieuRong);
}
class HinhChuNhat implements HinhDaGiac {
public void tinhDienTich(int chieuDai, int chieuRong) {
System.out.println("Diện tích của hình chữ nhật là: " + (chieuDai * chieuRong));
}
}
class Main {
public static void main(String[] args) {
HinhChuNhat h1 = new HinhDaGiac(){
h1.tinhDienTich(5, 6);
}
}
Khi chạy chương trình, kết quả nhận được là:
Diện tích của hình chữ nhật là: 30
Trong ví dụ trên, chúng ta đã tạo ta interface là HinhDaGiac
. Interface này có chứa phương thức trừu tượng là tinhDienTich()
Sau đó, chúng ta tạo ra class là HinhChuNhat
thông qua việc khai triển interface HinhDaGiac
.
Trong này, chúng ta viết khai triển cụ thể cho phương thức trừu tượng tinhDienTich()
của interface trên.
Cuối cùng, trong Main
, chúng ta khởi tạo đối tượng từ class HinhChuNhat
là gọi phương thức tinhDienTich()
Chương V. Phần 5.2. Tại sao cần sử dụng interface trong Java?
Chúng ta đã biết các inteface là gì, vậy bạn có biết tại sao phải sử dụng interface trong Java không?
Các interface cung cấp các đặc tả mà một class (khai triển nó) phải tuân theo.
Trong ví dụ trên, chúng ta đã sử dụng tinhDienTich()
như là một đặc tả trong interface HinhDaGiac
Cái này giống như là: Mọi hình đa giác đều có thể tính diện tích.
Vì vậy, bất kỳ class nào khai triển interface HinhDaGiac
đều phải có khai triển cụ thể cho phương thức tinhDienTich()
.
Tương tự như các class trừu tượng, các interface giúp chúng ta đạt được sự trừu tượng hóa trong Java.
Ở đây, chúng ta biết phương thức tinhDienTich()
tính diện tích đa giác. Nhưng các đa giác khác nhau có cách tính khác nhau.
Do đó, việc khai triển tinhDienTich()
là độc lập với nhau.
Ngoài ra, các interface cũng được sử dụng để đạt đa kế thừa trong Java.
Hiểu đơn giản:
"Nếu một class con được kế thừa từ hai hoặc nhiều class, thì đó là đa kế thừa."
Nhưng oái oăm là ngôn ngữ Java không cung cấp tính năng đa kế thừa thông qua class.
Tuy nhiên, nó lại cho phép một class kế thừa từ nhiều interface.
Điều này cũng giúp chúng ta đạt được tính đa kế thừa trong Java, ví dụ:
// Ví dụ minh họa về đa kế thừa thông qua interface
interface DuongThang {
...
}
interface HinhDaGiac {
...
}
class HinhChuNhat implements DuongThang, HinhDaGiac{
...
}
Ở đây, HinhChuNhat
đang kế thừa từ 2 interface đó là DuongThang
và HinhDaGiac
.
Lúc này, HinhChuNhat
phải cung cấp khai triển cụ thể cho tất cả các phương thức của 2 interface này.
Chương V. Phần 5.3. Static Method và Private Method trong interface
Trong bản phát hành Java 8, các interface đã có thể bao gồm các phương thức static.
Tương tự như một class, chúng ta có thể truy cập các phương thức static của một interface bằng cách sử dụng các tham chiếu của nó.
Ví dụ:
HinhDaGiac.staticMethod();
Ngoài ra, các interface cũng hỗ trợ các phương thức private từ phiên bản Java 9.
Thế nên, bây giờ bạn có thể sử dụng các phương thức private và phương thức static trong các interface.
Lưu ý!
> Vì bạn không thể khởi tạo interface, các phương thức private được sử dụng làm phương thức cung cấp hỗ trợ cho các phương thức khác trong interface.
Chương V. Phần 5.4. Phương thức mặc định trong interface
Trước đó, tất cả các phương thức trong interface đều trừu tượng.
Nhưng với bản phát hành Java 8, các phương thức có khai triển cụ thể (phương thức mặc định) đã được giới thiệu trong interface.
Để khai báo các phương thức mặc định bên trong các interface, chúng ta sẽ cần sử dụng từ khóa default
. Ví dụ:
// Ví dụ phương thức default trong interface
public default tinhChuVi(){
// Viết code như bình thường trong này
}
Vậy, tại sao cần sử dụng phương thức mặc định?
Giả sử, chúng ta cần thêm một phương thức mới trong một interface.
Chúng ta có thể thêm phương thức trong interface của mình một cách dễ dàng mà không cần khai triển.
Tuy nhiên, câu chuyện không kết thúc ở đó.
Tất cả các class của chúng ta thực hiện interface đó phải cung cấp triển khai cụ thể cho bất kỳ phương thức trừu tượng nào có trong interface.
Nếu có rất nhiều class kế thừa interface này chúng ta sẽ có rất nhiều phương thức, sau đó phải theo dõi ở rất nhiều chỗ.
Lúc này chúng ta sẽ dễ gặp lỗi.
Để giải quyết điều này, Java 8 đã giới thiệu các phương thức mặc định. Các phương thức mặc định được kế thừa như các phương thức thông thường.
Hãy để một ví dụ để hiểu rõ hơn về các phương thức mặc định:
interface HinhDaGiac {
void tinhDienTich();
default void soCanh() {
System.out.println("Số cạnh của hình đa giác.");
}
}
class HinhChuNhat implements HinhDaGiac {
public void tinhDienTich() {
int chieuDai = 5;
int chieuRong = 6;
int dienTich = chieuDai * chieuRong;
System.out.println("Diện tích của hình chữ nhật là: " + dienTich);
}
public void soCanh() {
System.out.println("Hình có 4 cạnh");
}
}
class HinhVuong implements HinhDaGiac {
public void tinhDienTich() {
int doDaiCanh = 5;
int dienTich = doDaiCanh * doDaiCanh;
System.out.println("Diện tích của hình vuông là: " + dienTich);
}
}
class Main {
public static void main(String[] args) {
HinhChuNhat hcn = new HinhChuNhat();
hcn.tinhDienTich();
hcn.soCanh();
HinhVuong hv = new HinhVuong();
hv.tinhDienTich();
}
}
Kết quả khi chạy chương trình là:
Diện tích của hình chữ nhật là: 30
Hình có 4 cạnh
Diện tích của hình vuông là: 25
Trong chương trình trên, chúng ta đã tạo ra một interface là HinhDaGiac
.
Interface này có chứa phương thức trừu tượng tinhDienTich()
(Các class kế thừa interface này phải khai triển cụ thể phương thức này)
Và,
Interface này cũng chứa một phương thức mặc định tên là soCanh()
để thông báo số cạnh của hình đa giác.
Khi tạo class HinhChuNhat
kế thừa interface HinhDaGiac
, chúng ta đã viết khai triển cụ thể cho phương thức tinhDienTich()
.
Bên cạnh đó, chúng ta còn thực hiện ghi đè phương thức mặc định soCanh()
.
Class HinhVuong
cũng kế thừa interface HinhDaGiac
, tuy nhiên khai triển phương thức tinhDienTich()
lại khác class HinhChuNhat
.
Ở class HinhVuong
chúng ta không ghi đè phương thức soCanh()
Chương V. Phần 5.5. Ví dụ luyện tập sử dụng interface
Chúng ta cùng làm một ví dụ tính diện tích tam giác bất kỳ sử dụng interface trong Java xem nhé.
Ở đây mình sử dụng công thức Heron Formulal để tính tam giác có 3 cạnh lần lượt là a, b, c.
Và để sử dụng căn bậc 2 thì sẽ phải import thêm package Math (có sẵn trong Java):
// import package Math để sử dụng căn bậc 2 (sqrt)
import java.lang.Math;
interface HinhDaGiac {
void tinhDienTich();
// Tính chu vi của một đa giác
default void tinhChuVi(int... doDaiCanh) {
int chuVi = 0;
for (int canh: doDaiCanh) {
chuVi += canh;
}
System.out.println("Chu vi: " + chuVi);
}
}
class TamGiac implements HinhDaGiac {
private int a, b, c;
private double p, dienTich; // p là 1/2 chu vi tam giác
// Constructor khởi tạo các cạnh của tam giác
TamGiac(int a, int b, int c) {
this.a = a;
this.b = b;
this.c = c;
p = 0;
}
// Tính diện tích tam giác bất kỳ
public void tinhDienTich() {
p = (double) (a + b + c)/2;
dienTich = Math.sqrt(p*(p-a)*(p-b)*(p-c));
System.out.println("Diện tích: " + dienTich);
}
}
class Main {
public static void main(String[] args) {
TamGiac tg = new TamGiac(4, 5, 6);
// Gọi phương thức tính diện tích của TamGiac
tg.tinhDienTich();
// Gọi phương thức của HinhDaGiac
tg.tinhChuVi(4, 5, 6);
}
}
Kết quả khi chạy chương trình là:
Diện tích: 9.921567416492215
Chu vi: 15
Trong chương trình trên, chúng ta đã tạo ra interface là HinhChuNhat
, nó chứa phương thức trừu tượng tinhDienTich()
và phương thức mặc định tinhChuVi()
Các class kế thừa interface này có thể sử dụng phương thức tinhChuVi()
ngay.
Còn phương thức tinhDienTich()
thì phải viết khai triển cụ thể cho nó rồi mới sử dụng được.
Chương V. Phần 5.6. Từ khóa extends trong interface
Tương tự như class, interface có thể sử dụng từ khóa extends
để kế thừa từ interface khác.
Ví dụ:
// Ví dụ sử dụng từ khóa extends trong interface
interface DuongThang {
// Các trường
// Các phương thức
}
interface HinhDaGiac extends DuongThang {
// Kế thừa trường và phương thức của interface DuongThang
// Các trường, phương thức riêng khác
}
Trong ví dụ trên, interface HinhDaGiac
kế thừa interface DuongThang
.
Nếu class nào khai triển interface HinhDaGiac
thì phải cung cấp khai triển cụ thể cho các phương thức trừu tượng của HinhDaGiac
và DuongThang
.
Ngoài ra, một interface cũng có thể kế thừa nhiều interface khác nhau.
Ví dụ:
// Kế thừa nhiều interface
interface A {
...
}
interface B {
...
}
Interface C extends A, B {
...
}
Như vậy là bạn đã được học kha khá về interface trong Java. Cũng không có gì khó hiểu đúng không?
Chương V. Phần 6. Đa hình trong Lập trình Java
Trong phần này, chúng ta sẽ tìm hiểu về tính đa hình trong lập trình Java, các loại đa hình khác nhau và cách triển khai chúng trong Java..
Đa hình là một khái niệm quan trọng của lập trình hướng đối tượng với Java. Nó được hiểu đơn giản là nhiều hơn một hình thái.
Cùng một thực thể (phương thức hoặc toán tử hoặc đối tượng) có hành vi khác nhau trong tình huống khác nhau. Ví dụ:
Toán tử +
trong Java được sử dụng để thực hiện hai chức năng cụ thể.
Khi nó được sử dụng với số (số nguyên và số thập phân), nó thực hiện phép cộng.
int a = 1;
int b = 2;
int tong = a + b; // Kết quả tổng bằng 3
Và khi chúng ta sử dụng toán tử +
với các chuỗi, nó thực hiện nối chuỗi. Ví dụ:
String chuoiMoi;
String ten = "NIIT ";
String chucNang = "Dạy lập trình";
chuoiMoi = ten + chucNang; // Kết quả: NIIT Dạy lâp trình
Trong Java, chúng ta có hai kiểu đa hình:
-
Đa hình trong Compiler-time
Chương V. Phần 6.1. Đa hình trong Run-time
Trong Java, có thể đạt được tính đa hình trong Run-time (thời gian chạy) thông qua ghi đè phương thức.
Giả sử có phương thức tương tự được tạo ra trong class cha và class con của nó.
Trong trường hợp này, phương thức sẽ được gọi phụ thuộc vào đối tượng được sử dụng để gọi phương thức, ví dụ:
abstract class DongVat {
public abstract void tiengKeu();
}
class Chuot extends DongVat {
@Override
public void tiengKeu() {
System.out.println("Chít chít chít...");
}
}
class Meo extends DongVat {
@Override
public void tiengKeu() {
System.out.println("Meo meo meo...");
}
}
class Main {
public static void main(String[] args) {
Chuot c = new Chuot();
c.tiengKeu();
Meo m = new Meo();
m.tiengKeu();
}
}
Khi chạy chương trình, kết quả nhận được là:
Chít chít chít...
Meo meo meo...
Lưu ý!
> Để hiểu cách ghi đè phương thức hoạt động, bạn vui lòng đọc lại Chương V Phần 2.
Ở ví dụ trên, phương thức tiengKeu() được triển khai khác nhau trong class khác nhau.
Khi chạy chương trình:
-
Câu lệnh
c.tiengKeu()
sẽ gọi đến phương thức của class Chuot
. Vì c
là đối tượng được tạo từ class Chuot
.
-
Câu lệnh
m.tiengKeu()
sẽ gọi đến phương thức của class Meo
. Vì m là đối tượng được tạo từ class Meo
.
Phương thức sẽ được gọi được xác định trong quá trình thực hiện chương trình. Do đó, ghi đè phương thức là đa hình trong Run-time.
Chương V. Phần 6.2. Đa hình trong Compile-time
Tính đa hình trong Compile-time (thời gian biên dịch) có thể đạt được thông qua nạp chồng phương thức và nạp chồng toán tử trong Java.
Trong Java, chúng ta tạo ra các phương thức có cùng tên nhưng khác đối số, như thế này:
void chucNang() {...}
void chucNang(int a) {...}
float chucNang(double a) {...}
float chucNang(int a, float b) {...}
Đây được gọi là nạp chồng phương thức trong lập trình Java.
Hãy lấy một ví dụ thực tế về nạp chồng phương thức trong Java để bạn hiểu về tính đa hình trong Compile - time:
class KyTu {
public void inKyTu(){
for(int i = 0; i < 10; i++) {
System.out.print("*");
}
}
public void inKyTu(char kyTu) {
for(int i = 0; i < 10; i++) {
System.out.print(kyTu);
}
}
}
class Main {
public static void main(String[] args) {
KyTu i = new KyTu();
i.inKyTu();
System.out.println("\n");
i.inKyTu('i');
}
}
Khi chạy chương trình, kết quả nhận được là:
Trong ví dụ trên, phương thức inKyTu()
bị nạp chồng.
-
Nếu chúng ta gọi phương thức
inKyTu()
không có truyền đối số thì ký tự *
sẽ được in ra 10 lần.
-
Nếu chúng ta gọi phương thức
inKyTu()
mà truyền vào bất kỳ ký tự nào thì ký tự đó sẽ được in ra 10 lần.
Vậy, ghi đè phương thức và nạp chồng phương thức có gì khác nhau?
-
Trong trường hợp ghi đè phương thức, các phương thức nên nằm trong các class khác nhau.
-
Trong khi đó, trong trường hợp nạp chồng phương thức, các phương thức nên nằm trong cùng một class.
-
Ghi đè phương thức được thực hiện tại Run-time trong khi nạp chồng phương thức được thực hiện tại Compile-time.
Tính đa hình trong Compile-time còn được thể hiện ở một số toán tử:
-
Toán tử
+
bị nạp chồng khi cộng số hoặc nối chuỗi
-
Toán tử
&
, |
, !
bị nạp chồng cho trong phép logic hoặc bitwise
Chương V. Phần 6.3. Tại sao sử dụng đa hình trong lập trình Java?
Chúng ta sử dụng đa hình vì đa hình cho phép chúng ta viết code nhất quán. Ví dụ:
Giả sử chúng ta cần tính diện tích hình tròn và hình vuông.
Để làm như vậy, chúng ta có thể tạo một class DaGiac
và tạo ra 2 class con là hinhTron
và HinhVuong
được kế thừa từ nó.
Trong trường hợp này, sẽ hợp lý khi tạo một phương thức có cùng tên tinhDienTich()
trong cả hai class con này thay vì tạo các phương thức với các tên khác nhau.
Trong ví dụ nạp chồng phương thức ở trên, chúng ta đã sử dụng cùng tên phương thức inKyTu()
để in ra ký tự theo 2 cách khác nhau nhưng thống nhất
Phương thức print()
trong Java cũng là một ví dụ về Đa hình (nạp chồng phương thức).
Phương thức này được sử dụng để in các giá trị thuộc các loại khác nhau như char, int, String, v.v.
Thậm chí có thể sử dụng nó để in ra nhiều giá trị cùng một lúc.
Chương V. Phần 6.4. Biến đa hình trong Java
Trong Java, các biến đối tượng (biến thể hiện) biểu thị hành vi của các biến đa hình.
Đó là bởi vì các biến đối tượng của một class có thể tham chiếu đến các đối tượng của class cũng như các đối tượng của các class con của nó. Ví dụ:
// Ví dụ biến đa hình trong Java
class DongVat {
public void hienThiThongTin() {
System.out.println("Tôi là động vật");
}
}
class Meo extends DongVat {
@Override
public void hienThiThongTin() {
System.out.println("Tôi là một con mèo");
}
}
class Main {
public static void main(String[] args) {
// Khai báo biến dv của class DongVat
DongVat dv;
// Tạo đối tượng của class DongVat
dv = new DongVat();
dv.hienThiThongTin();
// Tạo đối tượng của class Meo
dv = new Meo();
dv.hienThiThongTin();
}
}
Khi chạy chương trình, kết quả nhận được là:
Tôi là động vật
Tôi là một con mèo
Trong ví dụ trên, chúng ta đã tạo một biến đối tượng dv
của class DongVat
. Ở đây, dv
là một biến đa hình.
Đó là vì:
-
Trong câu lệnh
dv = new DongVat();
, dv
là đối tượng tạo từ class DongVat
.
-
Trong câu lệnh
dv = new Meo();
, dv
là đối tượng tạo từ class Meo
.
Đây là một ví dụ về upcasting trong Java. Hãy xem lại Chương IV. Phần 8.3 để hiểu rõ hơn nhé.
Chương V. Phần 7. Tính đóng gói trong Java.
Trong phần này của bài tự học Java, bạn sẽ được học về đóng gói (encapsulation) và ẩn dữ liệu (data hiding) trong Java với sự trợ giúp của các ví dụ.
Đóng gói là một trong những tính năng chính của lập trình hướng đối tượng. Đóng gói đề cập đến việc gói các trường và phương thức trong một class duy nhất.
Kết hợp các trường và phương thức tương tự trong một class với nhau cũng giúp ẩn dữ liệu.
Chương V. Phần 7.1. Đóng gói trường và phương thức
Nói chung, đóng gói là một quá trình gói code tương tự lại cùng một chỗ.
Trong Java, chúng ta có thể gói các trường và phương thức hoạt động cùng nhau trong một lớp.
Ví dụ:
class HinhChuNhat {
int chieuDai;
int chieuRong;
public void tinhDienTich() {}
}
Trong chương trình trên:
-
Phương thức
tinhDienTich()
tính diện tích hình chữ nhật.
-
Để tính diện tích, nó cần chiều dài và chiều rộng. Do đó, trường
chieuDai
và chieuRong
và phương thức tinhDienTich()
được đóng gói cùng với nhau trong class HinhChuNhat
.
Chương V. Phần 7.2. Ẩn dữ liệu trong Java.
Ẩn dữ liệu (data hiding) là một cách để hạn chế quyền truy cập của các thành viên dữ liệu của chúng ta bằng cách ẩn các chi tiết triển khai.
Đóng gói cũng cung cấp một cách để ẩn dữ liệu.
Và để làm điều đó, bạn có có thể sử dụng các công cụ sửa đổi truy cập.
Trong Java, có bốn công cụ sửa đổi truy cập:
-
public
: hiển thị khắp mọi nơi
-
private
: chỉ hiển thị bên trong class
-
protected
: hiển thị bên trong package và subclass của nó
-
default
: hiển thị bên trong package
// Ví dụ data hiding trong Java
class GaiXinh {
private int tuoi;
public int getTuoi() {
return tuoi;
}
public void setTuoi(int tuoi) {
this.tuoi = tuoi;
}
}
class Main {
public static void main(String[] args) {
GaiXinh quynhKool = new GaiXinh();
quynhKool.setTuoi(26);
System.out.println("Quỳnh Kool " + quynhKool.getTuoi() + " tuổi");
}
}
Khi chạy chương trình, kết quả nhận được là:
Quỳnh Kool 26 tuổi
Trong ví dụ trên, chúng ta có một trường private
là tuoi
.
Vì nó là private, nó không thể được truy cập từ bên ngoài class.
Để truy cập tuoi
, chúng tôi đã sử dụng các phương thức public
là getTuoi()
và setTuoi()
.
Các phương thức này được gọi là phương thức getter và setter.
Nếu chúng ta cố gắng truy cập trường tuoi
như thế này từ class Main
, chúng ta sẽ gặp lỗi:
quynhKool.tuoi = 26;
Việc thiết lập tuoi
thành private
cho phép chúng ta hạn chế truy cập trái phép từ bên ngoài class. Đây chính là ẩn dữ liệu.
// Ẩn dữ liệu bằng protected
class ConGai {
protected String ngheNghiep;
protected void thongBao() {
System.out.println("Tôi là " + ngheNghiep);
}
}
class GaiXinh extends ConGai {
public static void main(String[] args) {
GaiXinh quynhKool = new GaiXinh();
quynhKool.ngheNghiep = "Diễn viên";
quynhKool.thongBao();
}
}
Khi chạy chương trình, kết quả nhận được là:
Tôi là Diễn viên
Trong ví dụ trên, chúng ta đã tạo ra một class ConGai
bao gồm một biến ngheNghiep
và một phương thức thongBao()
đều được khai báo là protected
.
Sau đó, chúng ta đã truy cập các thành viên này từ class GaiXinh
(là subclass của class ConGai
)
Chương V. Phần 7.3. Tạo sao cần đóng gói dữ liệu trong lập trình Java
Trong Java, đóng gói dữ liệu giúp chúng ta kết hợp các trường và phương thức liên quan với nhau, điều này làm cho code của chúng ta sạch hơn và dễ đọc hơn.
Nó cũng giúp kiểm soát việc sửa đổi các trường dữ liệu của chúng ta sau này.
Hãy xem xét một tình huống mà chúng ta muốn trường tuoi
trong một class không âm.
Ở đây chúng ta có thể đặt tuoi
là private
và có thể áp dụng logic bên trong phương thức setTuoi()
.
Ví dụ:
class GaiXinh {
private int tuoi;
public void setTuoi(int tuoi) {
if (tuoi >= 0) {
this.tuoi = tuoi;
}
}
}
Các phương thức getter và setter cung cấp quyền truy cập chỉ đọc hoặc chỉ ghi vào các trường trong class của chúng ta.
Ví dụ:
getTuoi(); // Truy cập chỉ đọc
setTuoi(); // Truy cập chỉ ghi
Nó giúp tách các thành phần của một hệ thống.
Các thành phần tách rời này có thể được phát triển, thử nghiệm và gỡ lỗi độc lập và đồng thời.
Và, mọi thay đổi trong một thành phần cụ thể không có bất kỳ ảnh hưởng nào đến các thành phần khác.
Vậy.
Đóng gói dữ liệu với Ẩn dữ liệu có giống nhau không?
Mọi người thường coi việc đóng gói là ẩn dữ liệu, nhưng điều đó không hoàn toàn đúng.
Đóng gói đề cập đến việc kết hợp các trường và phương thức liên quan lại với nhau. Điều này cho phép chúng ta đạt được khả năng ẩn dữ liệu.
Bạn có thể coi việc ẩn dữ liệu nằm trong việc đóng gói.
Như vậy là qua chương V này bạn đã được học về các đặc điểm chính trong lập trình hướng đối tượng.
Ở chương tiếp theo chúng ta sẽ được học về class lồng nhau, Static class, Anonymous class, enum, ...
CHƯƠNG VI. TỰ HỌC LẬP TRÌNH JAVA HƯỚNG ĐỐI TƯỢNG (Part 3)
Chương VI. Phần 1. Nested class và Inner class trong Java
Trong bài viết này, bạn sẽ học cách làm việc với các class lồng nhau và class bên trong thông qua các ví dụ minh họa.
Trong Java, bạn có thể định nghĩa một class bên trong một class khác. Class như vậy được gọi là Nested class).
// Minh họa nested class
class OuterClass {
...
class NestedClass {
...
}
}
Bạn có thể tạo ra hai kiểu nested class trong Java:
-
Non-static nested class (hay còn gọi là inner class)
Bây giờ, hãy học về non-static nested class trước:
Chương VI. Phần 1.1. Non-Static Nested Class
Non-static nested class là một class trong một class khác, trong đó class có quyền truy cập vào các thành viên của class bên ngoài.
Nó thường được gọi là inner class (class bên trong).
Vì, class bên trong tồn tại bên trong class bên ngoài. Do đó, để khởi tạo một class bên trong, trước tiên bạn phải khởi tạo class bên ngoài.
Dưới đây là ví dụ về cách bạn có thể khai báo các class bên trong (inner class) trong Java.
// Ví dụ về inner class
class LapTop {
double gia;
class CPU{
String nhaSanXuat;
String loaiChip;
int getTheHeCPU(){
return 10;
}
}
protected class RAM{
String nhaSanXuat;
String loaiRAM;
int getBoNhoRAM(){
return 16;
}
}
}
public class Main {
public static void main(String[] args) {
LapTop hp = new LapTop();
LapTop.CPU cpu = hp.new CPU();
LapTop.RAM ram = hp.new RAM();
System.out.println("CPU thế hệ: " + cpu.getTheHeCPU() + "TH");
System.out.println("Bộ nhớ RAM: " + ram.getBoNhoRAM() + "GB");
}
}
Khi chạy chương trình, kết quả nhận được là:
CPU thế thệ: 10TH
Bộ nhớ RAM: 16GB
Trong chương trình trên, class LapTop
chứa hai class bên trong là CPU
và RAM
. Vì, class RAM
là class bên trong nên bạn có thể khai báo nó là protected
.
Trong class Main
, đối tượng của LapTop
được tạo trước tiên.
Và để tạo ra đối tượng của CPU
, chúng ta sử dụng toán tử dot
.
LapTop.CPU cpu = hp.new CPU();
Tiếp theo đây là vấn đề quan trọng.
Chúng ta đã có class bên ngoài (outer class) và class bên trong (inner class).
Vậy, làm thế nào để truy cập thành viên của class bên ngoài từ class bên trong?
Hãy cùng làm một ví dụ:
class Oto {
private String tenXe;
private String loaiXe;
public Oto(String tenXe, String loaiXe) {
this.tenXe = tenXe;
this.loaiXe = loaiXe;
}
private String getTenXe() {
return this.tenXe;
}
class DongCo {
double loaiDongCo;
void setDongCo() {
//Truy cập thuộc tính loaiXe của Oto
if(Oto.this.loaiXe.equals("N20")){
// Gọi phương thức getTenXe của Oto
if(Oto.this.getTenXe().equals("Vinfast")) {
this.loaiDongCo = 3.0;
} else {
this.loaiDongCo = 2.0;
}
}else{
this.loaiDongCo = 2.0;
}
}
double getLoaiDongCo(){
return this.loaiDongCo;
}
}
}
public class Main {
public static void main(String[] args) {
Oto luxSA = new Oto("Vinfast", "N20");
Oto.DongCo dongCo1 = luxSA.new DongCo();
dongCo1.setDongCo();
System.out.println("Động cơ Lux SA là: " + dongCo1.getLoaiDongCo());
Oto x5 = new Oto("BMW", "N20");
Oto.DongCo dongCo2 = x5.new DongCo();
dongCo2.setDongCo();
System.out.println("Động cơ BMW X5 là: " + dongCo2.getLoaiDongCo());
}
}
Khi chạy chương trình, kết quả nhận được là:
Động cơ Lux SA là: 3.0
Động cơ BMW X5 là: 2.0
Trong ví dụ bên trên, bên trong class DongCo (class bên trong class Oto), chúng ta đã sử dụng từ khóa this
để có quyền truy cập vào biến thành viên loaiXe
của class Oto (class bên ngoài) như:
Oto.this.loaiXe.equals("N20")
Điều này là có thể, mặc dù biến loaiXe
là private
trong class Oto
.
Bởi vì private
hiển thị bên trong cùng class (class Oto
chứa class DongCo
)
Bạn cũng có thể nhìn thấy chúng ta đã sử dụng Oto.this
để truy cập thành viên của class Oto
Nếu bạn chỉ sử dụng this
thay vì Oto.this
, nó sẽ truy cập thành viên của class DongCo
.
Tương tự, chúng ta cũng gọi thành công phương thức getTenXe()
của class Oto
.
Oto.this.getTenXe().equals("Vinfast")
Mặc dù phương thức getTenXe()
cũng là phương thức private
của class Oto
.
Chương VI. Phần 1.2. Static inner class
Trong Java, bạn cũng có thể định nghĩa một class static bên trong class khác. Class như vậy được gọi là static nested class.
Tuy nhiên, chúng không được gọi là static inner class.
Không giống như inner class, static nested class không thể truy cập các biến thành viên của lớp bên ngoài.
Đó là vì static nested class yêu cầu bạn tạo một đối tượng của class bên ngoài.
Do đó, nó không có tham chiếu nào về class bên ngoài tồn tại như là OuterClass.this
.
Vì vậy, bạn có thể tạo đối tượng của static nested class trực tiếp như thế này:
OuterClass.InnerClass obj = new OuterClass.InnerClass();
Ví dụ:
class LapTop {
String model;
public LapTop(String model) {
this.model = model;
}
static class USB{
int usb3 = 2;
int usbC = 1;
int getSoCongUSB(){
return usb3 + usbC;
}
}
}
public class Main {
public static void main(String[] args) {
LapTop.USB usb = new LapTop.USB();
System.out.println("Tổng số cổng USB: " + usb.getSoCongUSB());
}
}
Khi chạy chương trình, kết quả nhận được là:
Tổng số cổng USB: 3
Trong chương trình trên, chúng ta đã khai báo class USB bên trong bằng từ khóa static
.
Bạn cũng có thể thấy, trong class Main, chúng ta đã trực tiếp tạo một phiên bản USB từ LapTop
với toán tử dot
mà không tạo một đối tượng của LapTop
trước đó.
LapTop.USB usb = new LapTop.USB();
Và bây giờ, để xem điều gì sẽ xảy ra, nếu chúng ta cố gắng truy cập các thành viên của class bên ngoài từ static class được lồng bên trong nó:
class LapTop {
String model;
public LapTop(String model) {
this.model = model;
}
static class USB{
int usb3 = 2;
int usbC = 1;
int getSoCongUSB(){
if (LapTop.this.model.equals("2020")) {
return 4;
} else {
return usb3 + usbC;
}
}
}
}
public class Main {
public static void main(String[] args) {
LapTop.USB usb = new LapTop.USB();
System.out.println("Tổng số cổng USB: " + usb.getSoCongUSB());
}
}
Khi chúng ta chạy chương trình trên, nó sẽ báo lỗi:
error: non-static variable this cannot be referenced from a static context
Điều này là do, không tồn tại tham chiếu nào của class LapTop
được lưu trữ trong LapTop.this
Chương VI. Phần 1.3. Những điểm cần nhớ về nested class trong Java
-
Java coi class bên trong (inner class) như một thành viên thông thường của một class. Chúng giống như các phương thức và các biến được khai báo bên trong một class.
-
Vì, class bên trong là thành viên của class bên ngoài, bạn có thể áp dụng bất kỳ công cụ sửa đổi truy cập nào như
private
, protected
cho class bên trong của bạn, (Điều này không thể có trong các class thông thường).
-
Vì class bên trong là thành viên kèm theo của class bên ngoài, nên bạn có thể sử dụng ký hiệu
.
để truy cập class bên trong và các thành viên của nó.
-
Sử dụng class lồng nhau sẽ làm cho code của bạn dễ đọc hơn và đóng gói code tốt hơn.
-
Các class lồng nhau không phải static (inner class) có quyền truy cập vào các thành viên khác của class bên ngoài, ngay cả khi chúng được khai báo là
private
.
Chương VI. Phần 2. Nested Static class trong Java.
Tiếp tục với chủ đề Nested class, phần này bạn sẽ được học về Nested Static class qua các ví dụ.
Sau phần này bạn cũng sẽ hiểu được sự khác nhau của static class khác với inner class.
Như đã học trong phần trên, trong Java, chúng ta có thể có một class bên trong một class khác.
Các class như thế được gọi là các class lồng nhau. Trong Java, các lớp lồng nhau có hai loại:
-
Nessted non-static class (Hay còn gọi là Inner class)
Nếu bạn chưa hiểu rõ về inner class thì hãy xem lại phần trước nhé.
Trong phần này, chúng ta sẽ học về các Static class lồng nhau.
Chương VI. Phần 2.1. Cú pháp Nested Static class
Chúng ta sử dụng từ khóa static để làm cho class lồng nhau của chúng ta thành Nested Static Class.
Lưu ý!
> Trong Java, chỉ Nested class mới được phép là static.
Giống như các class thông thường, các static class lồng nhau có thể bao gồm cả các trường và phương thức static và non-static.
Ví dụ:
class DongVat {
static class DongVatCoVu {
// các thành viên static và non-static của DongVatCoVu
}
// Thành viên của DongVat
}
Các Static Nested Class được liên kết với class bên ngoài.
Để truy cập Static Nested Class, chúng ta không cần các đối tượng của class bên ngoài.
Ví dụ về Static Nested Class:
class DongVat {
// inner class
class BoSat {
public void hienThiThongTin() {
System.out.println("Loài bò sát");
}
}
// static class
static class DongVatCoVu {
public void hienThiThongTin() {
System.out.println("Động vật có vú");
}
}
}
class Main {
public static void main(String[] args) {
// Tạo đối tượng của class bên ngoài
DongVat dongVat = new DongVat();
// Đối tượng của non-static class
DongVat.BoSat ran = dongVat.new BoSat();
ran.hienThiThongTin();
// Đối tượng của static nested class
DongVat.DongVatCoVu cauVang = new DongVat.DongVatCoVu();
cauVang.hienThiThongTin();
}
}
Khi chạy chương trình, kết quả nhận được là:
Loài bò sát
Động vật có vú
Trong ví dụ trên, chúng ta có hai class lồng nhau là BoSat
và DongVatCoVu
nằm bên trong class DongVat
.
Để tạo một đối tượng của non-static class là BoSat
, chúng ta cần sử dụng đối tượng của class DongVat
là dongVat
như thế này:
DongVat.BoSat ran = dongVat.new BoSat();
Nhưng, khi tạo đối tượng của static nested class thì không cần, gọi trực tiếp qua tên class DongVat
là được.
DongVat.DongVatCoVu cauVang = new DongVat.DongVatCoVu();
Chương VI. Phần 2.2. Cách truy cập thành viên của class bên ngoài thông qua static nested class.
Trong Java, các Static Nested Class được liên kết với class bên ngoài.
Đây là lý do tại sao các Static Nested Class chỉ có thể truy cập các thành viên static (biến static và phương thức static) của class bên ngoài.
Hãy xem những gì sẽ xảy ra nếu chúng ta cố gắng truy cập các biến và phương thức non-static của class bên ngoài.
class DongVat {
static class DongVatCoVu {
public void hienThiThongTin() {
System.out.println("Động vật có vú");
}
}
class BoSat {
public void hienThiThongTin() {
System.out.println("Loài bò sát");
}
}
public void an() {
System.out.println("Đang ăn...");
}
}
class Main {
public static void main(String[] args) {
DongVat dongVat = new DongVat();
DongVat.BoSat ran = dongVat.new BoSat();
ran.hienThiThongTin();
DongVat.DongVatCoVu cauVang = new DongVat.DongVatCoVu();
cauVang.hienThiThongTin();
cauVang.an();
}
}
Khi chạy chương trình, chúng ta nhận được thông báo:
Main.java:27: error: cannot find symbol
dongVat.an();
^
symbol: method an()
location: variable cauVang of type DongVatCoVu
1 error
compiler exit status 1
Trong ví dụ trên, chúng ta đã tạo một phương thức nont-static là an()
bên trong class DongVat
.
Bây giờ, nếu chúng ta cố gắng truy cập an()
bằng cách sử dụng đối tượng cauVang
, trình biên dịch sẽ hiển thị một lỗi.
Đó là bởi vì cauVang
là một đối tượng của một static class nên chúng ta không thể truy cập các phương thức non-static thông qua cauVang
.
Chương VI. Phần 2.3. Static Top-Level Class trong Java
Như đã đề cập ở trên, chỉ các class được lồng bên trong mới có thể là static
. Chúng ta không thể viết class cao nhất là static
.
Hãy ví dụ nếu chúng ta cố gắng tạo một class cao nhất là static.
static class DongVat {
public static void hienThiThongTin() {
System.out.println("Động vật nào đó...");
}
}
class Main {
public static void main(String[] args) {
DongVat.hienThiThongTin();
}
}
Kết quả:
Main.java:1: error: modifier static not allowed here
static class DongVat {
^
1 error
compiler exit status 1
Trong ví dụ trên, chúng ta đã cố gắng tạo một class DongVat
là static
. Nhưng vì Java không cho phép class cao nhất là static
, nên chúng ta sẽ gặp lỗi.
Chương VI. Phần 3. Class Ẩn Danh (Anonymous Class) trong Java
Trong phần này, bạn sẽ được học về Class ẩn danh (hay gọi là Anonymous Class) trong Java thông qua các vị dụ thực tế.
Trong Java, một class có thể chứa một class khác, class bên trong được gọi là Nested class (Hay inner class).
Và, bạn có thể tạo một Nested class mà không cần đặt tên cho nó.
Một Nested class không có tên được gọi là Anonymous Class.
Một Anonymous Class phải được định nghĩa bên trong một class khác.
Do đó, nó còn được gọi là Anonymous Inner Class.
Cú pháp của Anonymous Class là:
// Cú pháp định nghĩa Anonymous Class
class ClassBenNgoai {
// Định nghĩa Anonymous Class
doiTuong = new Type(danhSachThamSo) {
// Phần thân của Anonymous Class
};
}
Ở đây, Type
có thể là:
-
Một Class mà Anonymous Class kế thừa
-
Một Interface mà Anonymous Class kế thừa
Đoạn code trên sẽ tạo một đối tượng doiTuong
của Anonymous Class tại thời điểm runtime.
Lưu ý!
> Anonymous Class được chấm dứt bằng dấu ;
Chương VI. Phần 3.1. Ví dụ về Anonymous Class trong Java.
Chúng ta sẽ cùng làm một vài ví dụ để bạn hiểu hơn về Anonymous Class trong Java.
Đầu tiên, hãy làm ví dụ về Anonymous Class kế thừa một class.
class HinhDaGiac {
public void hienThiThongTin() {
System.out.println("Hình đa giác");
}
}
class VDAnonymousClass {
public void taoClass() {
// Tạo Anonymous Class kế thừa HinhDaGiac
HinhDaGiac h1 = new HinhDaGiac() {
// Ghi đè phương thức
public void hienThiThongTin() {
System.out.println("Bên trong Anonymous Class");
}
};
h1.hienThiThongTin();
}
}
class Main {
public static void main(String[] args) {
VDAnonymousClass vd = new VDAnonymousClass();
vd.taoClass();
}
}
Khi chạy chương trình, kết quả là:
Bên trong Anonymous Class
Trong ví dụ trên, chúng ta đã tạo ra một class HinhDaGiac
. Nó có một phương thức là hienThiThongTin()
.
Sau đó, chúng ta đã tạo một Anonymous Class kế thừa class HinhDaGiac
và ghi đè phương thức hienThiThongTin()
.
Khi chúng ta chạy chương trình, một đối tượng h1
của Anonymous Class được tạo ra. Sau đó, đối tượng này gọi đến phương thức hienThiThongTin()
của Anonymous Class.
Ví dụ thứ hai, Anonymous Class kế thừa một interface.
interface HinhDaGiac {
public void hienThiThongTin() {
System.out.println("Hình đa giác");
}
}
class VDAnonymousClass {
public void taoClass() {
// Tạo Anonymous Class kế thừa interface
HinhDaGiac h1 = new HinhDaGiac() {
// Ghi đè phương thức
public void hienThiThongTin() {
System.out.println("Bên trong Anonymous Class");
}
};
h1.hienThiThongTin();
}
}
class Main {
public static void main(String[] args) {
VDAnonymousClass vd = new VDAnonymousClass();
vd.taoClass();
}
}
Khi chạy chương trình, kết quả nhận được là:
Bên trong Anonymous Class
Trong ví dụ trên, chúng ta đã tạo một Anonymous Class kế thừa một interface.
Và cách hoạt động của nó cũng tương tự như ở ví dụ Anonymous Class kế thừa class vậy.
Chương VI. Phần 3.2. Ưu điểm của Anonymous Class
Trong các Anonymous Class, các đối tượng được tạo bất cứ khi nào cần thiết. Ví dụ:
doiTuong = new classA() {
public void hienThiThongTin() {
System.out.println("Ghi đè phương thức hienThiThongTin().");
}
};
Trong ví dụ này, một đối tượng của Anonymous Class đã được tạo tự động khi chúng ta cần ghi đè phương thức hienThiThongTin()
trong classA
.
Anonymous Class giúp chúng ta viết code súc tích hơn.
Chương VI. Phần 4. Java Singleton
Trong phần này, chúng ta sẽ học về Singleton Design pattern và cách áp dụng nó trong Java với sự trợ giúp của các ví dụ.
Singleton là một mẫu thiết kế (Design pattern) chứ không phải là một tính năng dành riêng cho Java. Nó đảm bảo rằng chỉ có một đối tượng được tạo ra từ một class nào đó.
Một Design pattern giống như thư viện code của chúng ta bao gồm các kỹ thuật lập trình khác nhau được các lập trình viên trên khắp thế giới chia sẻ.
Chương VI. Phần 4.1. Quy tắc tạo ra Java Singleton
Đây là quy tắc để tạo ra Singleton trong Java:
-
Tạo một constructor là
private
để giới hạn việc tạo đối tượng bên ngoài class
-
Tạo một thuộc tính
private
tham chiếu đến đối tượng singleton.
-
Tạo một phương thức
public static
cho phép chúng ta tạo và truy cập đối tượng chúng ta đã tạo. Bên trong phương thức, chúng ta sẽ tạo một điều kiện hạn chế chúng ta tạo nhiều hơn một đối tượng.
Đây là một ví dụ về cách tạo ra Java Singleton:
class ViDuSingleton {
// Trường private tham chiếu đến đối tượng
private static ViDuSingleton doiTuongSingle;
private ViDuSingleton() {
// private constructor của class SingletonExample
}
public static ViDuSingleton getDoiTuong() {
// Viết code cho phép chúng ta chỉ tạo 1 đối tượng duy nhất
// Truy cập đối tượng khi chúng ta cần
}
}
Trong ví dụ trên:
-
private static ViDuSingleton doiTuongSingle;
- là một tham chiếu đến đối tượng của class.
-
private ViDuSingleton()
- Một private constructor giới hạn việc tạo các đối tượng bên ngoài class.
-
public static ViDuSingleton getDoiTuong()
- Phương thức này trả về tham chiếu đến đối tượng duy nhất của lớp. Vì phương thức static, nó có thể được truy cập bằng tên class.
Chương VI. Phần 4.2. Cách sử dụng Singleton Class
Singletons có thể được sử dụng trong khi làm việc với cơ sở dữ liệu.
Chúng có thể được sử dụng để tạo một nhóm kết nối để truy cập cơ sở dữ liệu trong khi sử dụng lại cùng một kết nối cho tất cả các máy khách.
Ví dụ:
class Database {
private static Database dbObject;
private Database() {
}
public static Database getInstance() {
// Tạo đối tượng nếu nó chưa được tạo
if(dbObject == null) {
dbObject = new Database();
}
// Trả về đối tượng singleton
return dbObject;
}
public void getConnection() {
System.out.println("Bạn đã kết nối tới cơ sở dữ liệu.");
}
}
class Main {
public static void main(String[] args) {
Database db1;
// Tham chiếu đến đối tượng của Database
db1 = Database.getInstance();
db1.getConnection();
}
}
Khi chạy chương trình, kết quả nhận được là:
Bạn đã kết nối tới cơ sở dữ liệu.
Trong ví dụ trên:
Chúng ta đã tạo ra một class singleton là Database
.
dbOject
là một trường của class. Nó tham chiếu đến đối tượng của class Database
.
private constructor Database()
ngăn chặn việc tạo đối tượng bên ngoài class.
Phương thức static getInstance()
trả về thể hiện của class với bên ngoài.
Trong class Main
, chúng ta có biến db1
. Chúng ta đang gọi getInstance()
bằng db1
để lấy đối tượng duy nhất của Database
.
Vì Database
chỉ có thể có một đối tượng, tất cả các máy khách có thể truy cập cơ sở dữ liệu thông qua một kết nối duy nhất.
Điều quan trọng cần lưu ý là:
> Chỉ có một vài tình huống (như đăng nhập) là sử dụng singleton. Kết nối cơ sở dữ liệu thường không nên sử dụng singleton.
Và nếu bạn không biết chắc chắc nên sử dụng Java Singleton hay không thì tốt nhất bạn không nên sử dụng.
> NOTE: Nếu bạn là người mới, hãy chú ý học thật tốt, hiểu cặn kẽ một ngôn ngữ (Không lên học lan man). Bởi vì bản chất lập trình là giống nhau, ngôn ngữ chỉ là công cụ. Học tốt một ngôn ngữ thì chuyển đổi hay nâng cấp ngôn ngữ khác cũng dễ dàng hơn.
> Đăng ký HỌC LẬP TRÌNH (Full Stack) ngay nếu bạn thực sự nghiêm túc với nghề lập trình.
Còn tiếp...
📌 Mạng xã hội của NIIT-ICT Hà Nội