Được chỉnh sửa ngày 07/12/2022.
Chào mừng các bạn đến với bài học Java số 10, bài học về các câu lệnh nhảy trong Java. Bài học này nằm trong chuỗi bài học lập trình ngôn ngữ Java của Yellow Code Books..
Mình xin giải thích một chút ý nghĩa của bài hôm nay. Bài học hôm nay đáng lý ra phải nằm ở bài 9 – Bài nói về các câu lệnh lặp – Vì kiến thức của bài hôm nay có liên quan các câu lệnh lặp đó. Tuy nhiên mình nghĩ nếu nói về kiến thức này ở bài trước sẽ làm cho bài học bị dài ra, vừa khó để các bạn nhớ, vừa khó để thực hành, lại khó cho mình khi phải viết một bài quá dài. Chính vì vậy mình tách ra thành một bài học hôm nay.
Chúng ta sẽ nói đến 2 câu lệnh nhảy trong bài hôm nay, đó là break và continue. Trong khi có một câu lệnh nhảy khác là return sẽ được trao đổi ở bài viết về Phương Thức sau nhé.
Sở dĩ Java dùng từ “nhảy” không phải vì nó làm cho các vòng lặp của bạn thêm nhảy nhót. Nhảy ở đây là nhảy ra khỏi vòng lặp, hay nhảy đến một lần lặp tiếp theo, bỏ qua các câu lệnh còn lại bên trong thân vòng lặp đó. Một số tài liệu khác gọi đây là các câu lệnh điều khiển, nhưng mình thích từ nhảy hơn, tiếng Việt nghe hơi chuối nhưng tiếng Anh họ gọi là jump statements, nghe rõ nghĩa hơn.
Chúng ta cùng xem qua nhé.
break – Câu Lệnh Dừng
Bạn hiểu nghĩa break là đập vỡ cũng đúng, nhưng trong tình huống này nó có nghĩa là dừng thì hay hơn.
Theo đúng tên gọi, khi câu lệnh này xuất hiện ở đâu đó trong vòng lặp, chúng sẽ làm phá vỡ, hay dừng vòng lặp đó dù cho vẫn còn các câu lệnh khác bên trong vòng lặp chưa được xử lý.
Nếu bạn còn nhớ, ở bài 8 chúng ta cùng nói qua câu lệnh break này cho cấu trúc switch case đúng không nào. Vâng, break ở switch case và break ở câu lệnh lặp cũng có công dụng tương tự như nhau thôi.
Bài Thực Hành Số 1
Bài thực hành này muốn bạn in ra console tất cả các số nguyên tố từ 1 đến 10.000.
Khoan! Có gì đó sai sai! In số nguyên tố đã được thực hành ở bài trước rồi mà! Và in số nguyên tố thì có liên quan gì đến break!?!
Bạn yên tâm, in số nguyên tố ở bài trước chỉ là một giải thuật “gà”. Trong lập trình, đặc biệt là lập trình các thuật toán, sẽ luôn luôn hoan nghênh các bạn có những giải thuật cải tiến sao cho ứng dụng chạy nhanh, mượt. Muốn được như vậy thì các thuật toán của các bạn phải gọn, bắt máy tính làm việc ít hơn, số lượng vòng lặp giảm thiểu,… Có lẽ chúng ta sẽ nói đến chủ đề này ở bài khác.
Quay lại bài thực hành, chúng ta sẽ cải tiến giải thuật tìm số nguyên tố, kết hợp với câu lệnh break để dừng việc kiểm tra sớm một khi đã biết số đó không phải là số nguyên tố.
Bạn đã biết số nguyên tố là số chỉ chia hết cho 1 và chính nó. Để làm vậy, với cách cũ bạn duyệt qua các số hạng từ 1 đến chính nó, đếm xem có bao nhiêu số hạng mà nó chia hết cho, nếu đếm thấy 2 số hạng thì nó đúng là số nguyên tố. Với cách này bạn luôn phải cho vòng lặp chạy hết tất cả các số hạng. Cụ thể, với việc kiểm tra số 10.000, thì ứng dụng của bạn sẽ phải lặp 10.000 lần để đếm. Cách này mình đo thấy ứng dụng tốn 376 ms (mili giây) để chạy, tức là gần nửa giây. Bạn có thể áp dụng break vào việc giảm số lần lặp bằng 2 cách sau.
- Đừng cho vòng lặp chạy từ 1 đến chính nó, mà hãy cho vòng lặp chạy từ số 2 đến chính nó giảm đi 1 đơn vị, tức là chạy trong khoảng [2, chính nó). Hễ tìm được một số nào đó trong khoảng này mà chính nó chia hết, thì gọi lệnh break ngay để kết thúc lặp, không cần lặp nữa vì chắc chắn nó không phải số nguyên tố (ngoài số 1 với chính nó ra thì nó còn chia hết cho ít nhất một số khác rồi). Với giải thuật này thì giả sử để kiểm tra số 10.000, ứng dụng của bạn chỉ cần chạy đến số hạng 2 là đã break rồi. Cách này tốn 167 ms để in ra hết các số nguyên tố, chỉ tốn một nửa thời gian so với cách thứ nhất. Ôi cool quá!
- Cách này hay hơn nữa, theo nghiên cứu (chưa rõ từ nguồn nào) thì bạn chỉ cần kiểm tra trong khoảng 2 đến căn bậc hai của chính nó, tức là cho vòng lặp chạy trong khoảng [2, căn bậc 2 chính nó]. Nếu tìm thấy một số trong khoảng này mà nó chia hết cho, thì nó không phải là số nguyên tố. Trong Java, hàm lấy căn bậc hai một số là
Math.sqrt(số_thực);
. Giải thuật thứ ba này chỉ mất có 15 ms để chạy thôi nhé, nhanh gấp 25 lần so với cách thứ nhất.
Mình áp dụng cách chạy nhanh nhất trên đây vào code bên dưới, bạn áp dụng cách nào? Hãy thử code nhé.
for (int number = 2; number <= 10000; number++) { boolean isPrime = true; // Thay vì biến count, dùng biến này để kiểm tra số nguyên tố for(int j = 2; j <= Math.sqrt(number); j++) { if (number % j == 0) { // Chỉ cần một giá trị được tìm thấy trong khoảng này, // thì number không phải số nguyên tố isPrime = false; break; // Thoát ngay và luôn khỏi for (vòng for bên ngoài vẫn chạy) } } if (isPrime) { // Nếu isPrime còn giữ được sự "trong trắng" đến cùng thì đó là số nguyên tố System.out.println(number); } }
continue – Câu Lệnh Bỏ Qua
Lại một lần nữa nghĩa và công dụng của từ này bị lẫn lộn. Bạn giỏi tiếng Anh nên hiểu nghĩa của continue là tiếp tục cũng không sai, nhưng tình huống này chúng ta hiểu là bỏ qua.
Khác với dừng trên kia – Dừng là dừng luôn không quay lại nó nữa – Còn bỏ qua có nghĩa là bỏ các câu lệnh còn lại bên trong vòng lặp để thực hiện một chu trình lặp mới. Câu lệnh này không làm dừng vòng lặp như break, mà chỉ thực hiện vòng lặp mới, nhưng vòng lặp cũng có thể bị dừng bởi continue khi mà việc thực hiện vòng lặp mới sẽ kiểm tra và thấy điều_kiện_lặp không còn thỏa.
Có một lưu ý nhỏ khi bạn dùng continue cho for và while/do while như sau.
- Với while/do while: bởi vì continue sẽ tự thực hiện một vòng lặp mới nên có khả năng biến đếm sẽ bị bỏ qua nếu bạn để câu lệnh tăng biến đếm ở sau continue, như vậy sẽ có trường hợp bạn bị rơi vào vòng lặp vô tận, điều này dễ gặp ở while và do while lắm đấy nhé.
- Với for: khi sử dụng với for thì bạn yên tâm, câu lệnh continue ở vòng lặp này sẽ bao gồm việc tăng biến đếm (nếu bạn có định nghĩa việc tăng biến đếm này ở thành phần thứ ba của for) rồi mới thực hiện vòng lặp mới. Chính vì vậy nên việc dùng continue trong for sẽ an toàn hơn.
Bài Thực Hành Số 2
Bài này bạn sẽ phải in ra console ngược lại với bài thực hành ở trên, kết hợp với continue. Bạn hãy viết chương trình in ra console tất cả các số KHÔNG PHẢI LÀ SỐ NGUYÊN TỐ trong khoảng từ 1 đến 100.
Chúng ta sẽ áp dụng giải thuật tìm số nguyên tố, hễ biết đó là số nguyên tố thì sẽ dùng continue “lướt” qua lần lặp mới, các câu lệnh in ra console sẽ để bên dưới câu lệnh continue để đảm bảo in ra những gì không thỏa điều kiện của continue. Code chúng ta như sau.
for (int number = 1; number <= 100; number++) { if (number == 1) { // 1 không phải số nguyên tố, in ra rồi biến System.out.println(number); continue; } boolean isPrime = true; // Biến kiểm tra số nguyên tố for(int j = 2; j <= Math.sqrt(number); j++) { if (number % j == 0) { // Chỉ cần một giá trị được tìm thấy trong khoảng này, // thì number không phải số nguyên tố isPrime = false; break; // Thoát ngay và luôn khỏi for (vòng for bên ngoài vẫn chạy) } } if (isPrime) { // Nếu isPrime cho biết đây là số nguyên tố // bỏ qua câu lệnh dưới đây mà qua lần lặp kế continue; } System.out.println(number); }
Label – Gán Nhãn Cho Vòng Lặp
Mục này mình mới thêm vào sau này, ở giai đoạn đầu tiên của bài viết mình không nói đến việc gán nhãn cho vòng lặp. Tại sao mình lại bỏ qua nó? Thứ nhất bạn nên hiểu rằng Java gần như đã bỏ khái niệm goto của C++. Với kiến thức về goto thì bạn có thêm một tùy chọn cho việc điều khiển luồng trong ứng dụng, nó cho phép bạn rẽ nhánh xử lý đến bất kỳ nơi nào bạn chỉ định. Điều này gây vô số khó khăn trong việc kiểm soát luồng chạy của ứng dụng, rất khó để tìm kiếm và gỡ lỗi nếu có. Nên với Java, ít có nơi nào nói kỹ nên gần như chúng ta quên mất khái niệm goto này. Thứ hai, nếu không biết gì về kiến thức goto chúng ta vẫn xây dựng hoàn chỉnh các ứng dụng mong muốn.
Tuy nhiên với một vài tình huống nhất định, nhất là khi sử dụng các vòng lặp lồng vào nhau, có khi bạn mong muốn được dừng hay bỏ qua vòng lặp con để về một vòng lặp bên ngoài bất kỳ, khi đó goto phát huy tác dụng. Java hỗ trợ cho chúng ta kỹ thuật này cho tình huống này, nó được gọi là label, gán nhãn cho vòng lặp, giúp bạn gán một nhãn bất kỳ cho vòng lặp, sau đó break hay continue sẽ được chỉ định để dừng hoặc bỏ qua vòng lặp hiện tại để về đúng nhãn mà bạn đã gán.
Bạn hãy nhìn kỹ cách sử dụng nhãn ở bài thực hành dưới đây. Mình dùng nhãn chung với lệnh continue, nhưng bạn hoàn toàn có thể hiểu và áp dụng tương tự với break. Trong trường hợp bạn còn mông lung về việc gán nhãn và sử dụng nhãn, thì mình khuyên bạn không nên có gắng dùng nó mà làm chi nhé.
Bài Thực Hành Số 3
Bài này chúng ta lại tiếp tục cải tiến cho việc in ra console tất cả các số nguyên tố từ 1 đến 10.000 mà Bài Thực Hành Số 1 trên kia dường như làm khá tốt rồi.
Ý tưởng của Bài Thực Hành Số 1 là cứ mỗi số cần kiểm tra (ở vòng lặp ngoài cùng), cờ isPrime sẽ được khai báo lại là true rồi vào vòng lặp bên trong sẽ xác định và set cờ isPrime là false hay không. Ra khỏi vòng lặp con mà isPrime vẫn còn là true thì sẽ in số đó ra vì nó chính là số nguyên tố.
Bài thực hành này chúng ta không cần dùng đến cờ isPrime luôn, mà ở vòng lặp trong, nếu biết đó là số nguyên tố, lệnh continue kết hợp với nhãn sẽ dẫn luồng xử lý về lại vòng lặp for được gán nhãn. Mời bạn xem code và gác diễn giải ngay trong code.
Nếu bạn muốn biết việc so sánh thời gian xử lý, thì Bài Thực Hành Số 1 cũng đã là một giải thuật quá hay rồi, nên bài thực hành này cũng chỉ giúp giảm thời gian thêm một chút xíu thôi, cụ thể mình đo và thấy thời gian xử lý của bài này là 10 ms.
// Đặt nhãn cho vòng for ngoài cùng là loop_root // Quy luật đặt tên nhãn giống như đặt tên biến vậy loop_root: for (int number = 2; number <= 10000; number++) { for (int j = 2; j <= Math.sqrt(number); j++) { if (number % j == 0) { // Chỉ cần một giá trị được tìm thấy trong khoảng này, // thì number không phải số nguyên tố continue loop_root; // Bỏ qua các câu lệnh xử lý khác mà về lại với nơi được gán nhãn loop_root và thực hiện tiếp vòng lặp } } // Cuối cùng nếu đến được đây thì in number ra vì nó là số nguyên tố System.out.println(number); }
Kết Luận
Bạn vừa cùng mình đi qua các câu lệnh còn lại có ảnh hưởng đến cấu trúc lặp mà các bạn vừa học qua. Bạn hãy cân nhắc sử dụng các câu lệnh break và continue của bài hôm nay, một trong những ứng dụng của chúng như bạn được làm quen thông qua các bài thực hành trên đây, đó là cải thiện tốc độ và hiệu năng của các vòng lặp, giúp các vòng lặp biết cách “dừng đúng lúc”.
Cảm ơn bạn đã đọc các bài viết của Yellow Code Books. Bạn hãy ủng hộ blog bằng cách:
– Đánh giá 5 sao ở mỗi bài viết nếu thấy thích.
– Comment bên dưới mỗi bài viết nếu có thắc mắc.
– Để lại địa chỉ email của bạn ở thanh bên phải để nhận được thông báo sớm nhất khi có bài viết mới.
– Chia sẻ các bài viết của Yellow Code Books đến nhiều người khác.
– Ủng hộ blog theo hướng dẫn ở thanh bên phải để blog ngày càng phát triển hơn.
Bài Kế Tiếp
Chúng ta sẽ cùng nhau tìm hiểu về mảng trong Java nhé.
Những bài giảng về Java rất hay và mang một chút hài hước. Mong anh/chị hãy soạn thêm thật nhiều bài giảng hơn nữa!!
Cảm ơn bạn Danh Mạnh nhé!
cho em hỏi phần làm bài tập ở đâu ạ???
Phần làm bài tập nào hả bạn?
à!!! mình thấy rồi!!! thanks nha
ví dụ thực hành1 bị sai phải không bạn?
Mình mới xem sơ qua thì chưa thấy ví dụ có gì bất thường, có gì bạn chỉ ra giúp mình xem sai ở chỗ nào nhé
ad có thể cho em biết thên trang web để làm bài tập ko?
Thông tin này hơi khó, mình cũng chưa thu thập được trang web nào đưa ra nhiều bài tập Java để các bạn thực hành cả, bạn nào có nguồn nào hay về bài tập Java thì nhớ chia sẻ với nhé.
Anh ơi cho em hỏi với eclipse thì làm sao để xem tốc độ chạy của chương trình là bao nhiêu ms được ạ? Em thấy nó không hiện.
À không có công cụ xem tốc độ chạy của chương trình là bao nhiêu ms đâu bạn. Trước khi chạy một đoạn code nào đó mình gọi đến hàm System.currentTimeMillis(), hàm này giúp trả về một đơn vị thời gian tính theo mini giây. Sau đó sau khi kết thúc đoạn code cần đo mình lại gọi System.currentTimeMillis() thêm một lần nữa. Lấy kết quả của 2 lần gọi hàm trên trừ cho nhau sẽ ra được chênh lệch thời gian mà một đoạn code đã chạy.
if (number == 1) {
// 1 không phải số nguyên tố, in ra rồi biến
System.out.println(number);
continue;
}
mình thấy phần này không cần thiết vì vòng lặp 2 chỉ chạy từ j = 4 nên nó chắc chắn sẽ bỏ qua 1, 2, 3 rồi.
if (number == 1) {
// 1 không phải số nguyên tố, in ra rồi biến
System.out.println(number);
continue;
}
mình thấy phần này không cần thiết vì vòng lặp 2 chỉ chạy từ j = 4 nên nó chắc chắn sẽ bỏ qua 1, 2, 3 rồi.
Thực ra mình cũng chưa hiểu ý bạn lắm. Thông tin nào cho bạn biết là vòng lặp 2 chạy từ j = 4 thế. Bạn có thể chỉ ra thêm thông tin giúp mình được không.
ở bài thực hành 2 vì sao lấy số không phải số nguyên tố mà vẫn dùng break ạ?
À cài đoạn break đó là một bộ code đi chung với nhau để TÌM SỐ NGUYÊN TỐ. Bắt đầu từ set cờ isPrime là true, vào for, thấy không phải nguyên tố thì set isPrime bằng false rồi break.
Còn đoạn lấy số không phải số nguyên tố là đoạn kiểm tra sau cái bộ code TÌM SỐ NGUYÊN TỐ mà bạn đang thắc mắc nữa nhé. Bạn thử cho 1 con số nào đó rồi chạy bằng mắt các bước trong bài thực hành, chỉ vài số là bạn hiểu.
anh cho em hỏi lúc anh mới học, anh có cảm thấy khó không ạ? Em mới học vòng lặp mà hơi khó hiểu r, anh cho em hỏi thêm bài tập phần này anh dùng web nào để luyện?
Mình hiểu vòng lặp thường gây khó khăn cho các bạn mới làm quen với lập trình nói chung, không riêng gì với ngôn ngữ Java đâu. Nhưng mà thực sự nếu bạn thường xuyên code, thì với việc được sử dụng nhiều đến vòng lặp, bạn sẽ nhanh chóng hiểu và nhớ cặn kẽ thôi. Nhớ là phải thường xuyên code nhé, có thể code thông qua project của riêng bạn hay thông qua các hướng dẫn từ bất kỳ trang web nào cũng được.