Giải pháp chuyển đổi số trọn gói cho doanh nghiệp Việt

Các Câu Lệnh Nhảy (break/continue & label) Trong Vòng Lặp - Lập trình Java

Lập trình Java

← Toàn bộ tài liệu


Bài 10: Các Câu Lệnh Nhảy (break/continue & label) Trong Vòng Lặp

Bài 10/56 Xem dạng sách

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);
}

Sơ đồ quan hệ các bảng

Table Name: {{table.name}}
Num Name Type
{{field_index+1}}
{{field.name}} {{field.type}}

Hỏi đáp

Gửi với tên {{comment_full_name}}

Chưa có câu hỏi nào cho bài này.

Zalo 0936.006.058 Hotline 0936.006.058