کلاس و شیء در جاوا؛ آموزش شیگرایی به زبان ساده
کلاس در جاوا نقشه است و شیء چیزی است که از روی آن نقشه ساخته میشود. در این آموزش ساده، …
حلقه در جاوا ابزاری است برای تکرار یک کار بدون نوشتن دوباره کد. در این آموزش ساده، for، for-each، while و do-while را با مثالهای اجراشده یاد میگیرید، تفاوتشان را در یک جدول میبینید و با حلقه تودرتو، break، continue و اشتباهات رایج مبتدیها آشنا میشوید.
آخرین بهروزرسانی: ۱۸ آبان ۱۴۰۵ — منابع بررسیشده در این روز: صفحه پروژه JDK و JDK 25، فصل ۱۴ مشخصات زبان جاوا ۲۵، آموزشهای رسمی اوراکل و dev.java و مرجع Iterable؛ همه مثالها با JDK 25.0.5 اجرا شدند.
⚡ پاسخ کوتاه
اگر تعداد تکرار را میدانید یا به شماره خانه نیاز دارید، از for استفاده کنید. برای خواندن همه عضوهای آرایه یا لیست، for-each سادهترین راه است. وقتی پایان حلقه به یک اتفاق بستگی دارد، while و وقتی بدنه باید دستکم یک بار اجرا شود، do-while را انتخاب کنید. همیشه مطمئن شوید چیزی در بدنه، شرط را به سمت پایان میبرد.
📑 فهرست مطالب
فرض کنید میخواهید عددهای یک تا صد را چاپ کنید. میتوانید صد خط System.out.println بنویسید، اما این کار هم خستهکننده است و هم اگر بعداً بخواهید تا هزار بشمارید، باید همهچیز را از نو بنویسید. حلقه دقیقاً برای همین ساخته شده است: یک دستور یا یک بلوک کد را تا وقتی شرطی برقرار است، دوباره و دوباره اجرا میکند. به همین دلیل حلقه در جاوا یکی از اولین ابزارهایی است که هر برنامهنویس تازهکار باید خوب یاد بگیرد. حلقهها همهجا هستند. وقتی فهرست سفارشهای یک فروشگاه را جمع میزنید، وقتی خطهای یک فایل را یکییکی میخوانید یا وقتی از کاربر میپرسید «ادامه میدهید؟» و تا جواب منفی نگیرید سؤال را تکرار میکنید، در واقع از حلقه استفاده میکنید. اگر هنوز با ساختار کلی برنامه جاوا آشنا نیستید، اول مقدمهای بر جاوا را بخوانید و بعد به این آموزش برگردید.
تقریباً هر حلقهای سه بخش دارد. اول، نقطه شروع؛ مثلاً یک شمارنده که از صفر یا یک شروع میشود. دوم، شرط ادامه؛ سؤالی که قبل یا بعد از هر دور پرسیده میشود و اگر جوابش «درست» باشد، حلقه یک دور دیگر میچرخد. سوم، تغییر؛ کاری که باعث میشود حلقه بالاخره به پایان برسد، مثل یکی اضافه کردن به شمارنده. اگر بخش سوم را فراموش کنید، شرط همیشه درست میماند و برنامه در حلقه بینهایت گیر میکند. بیشتر خطاهای مبتدیها در حلقه از همین سه بخش میآید. در جاوا چهار شکل اصلی حلقه داریم: for معمولی، for-each یا همان for پیشرفته، while و do-while. هر کدام برای موقعیت خاصی راحتتر است، اما همه یک کار را انجام میدهند: تکرار. در ادامه هر کدام را با مثال ساده میبینیم. همه مثالها با JDK 25 (نسخه پشتیبانی بلندمدت، Temurin 25.0.5) روی سیستم خودمان کامپایل و اجرا شدهاند و خروجیهایی که میبینید، خروجی واقعی همان اجراست.
شرط ادامه هر حلقه یک عبارت بولی است؛ یعنی جوابش فقط true یا false است. همان عبارتهایی که در دستور if مینویسید، اینجا هم کاربرد دارند؛ مثل i <= 10 یا name.isEmpty(). برای آشنایی کامل با if، else و switch، مقاله تصمیمگیری در زبان جاوا را ببینید. اینجا فقط از شرطهای ساده استفاده میکنیم تا تمرکز روی خود حلقه بماند. آرایه هم مجموعهای از مقدارهای همنوع است که با شماره خانه، یعنی اندیس، به آنها دسترسی دارید. اندیس از صفر شروع میشود و آخرین خانه length منهای یک است. حلقهها و آرایهها تقریباً همیشه با هم میآیند، چون برای دیدن همه خانههای آرایه باید از اول تا آخر آن را پیمایش کنید. جزئیات ساخت و کار با آرایه را در آرایهها در زبان جاوا توضیح دادهایم.
حلقه for برای وقتی است که از قبل میدانید چند بار باید تکرار کنید؛ مثلاً ده بار، یا به تعداد خانههای یک آرایه. هر سه جزء حلقه، یعنی شروع، شرط و تغییر، در یک خط و داخل پرانتز نوشته میشوند. همین باعث میشود با یک نگاه بفهمید حلقه از کجا شروع میشود و کجا تمام میشود.
for (int i = 1; i <= 5; i++) {
System.out.print(i + " ");
}
// output: 1 2 3 4 5
این خط را اینطور بخوانید: «متغیر i را با یک شروع کن؛ تا وقتی i کوچکتر یا مساوی پنج است، بدنه را اجرا کن؛ بعد از هر دور یکی به i اضافه کن.» ترتیب اجرا مهم است. بخش شروع فقط یک بار اجرا میشود. بعد شرط بررسی میشود. اگر درست بود، بدنه اجرا میشود و سپس بخش تغییر. دوباره شرط بررسی میشود و این چرخه تا وقتی شرط نادرست شود ادامه پیدا میکند.

لازم نیست شمارنده همیشه یکییکی زیاد شود. میتوانید از عدد بزرگ شروع کنید و کم کنید، یا با گام دو و سه جلو بروید. فقط حواستان باشد شرط و جهت تغییر با هم جور باشند. اگر شمارنده را کم میکنید ولی شرط را i < 100 نوشتهاید و از ده شروع کردهاید، حلقه هیچوقت تمام نمیشود.
for (int i = 10; i > 0; i -= 2) {
System.out.print(i + " ");
}
// output: 10 8 6 4 2
متغیری که در بخش شروع for تعریف میکنید، مثل i در مثالهای بالا، فقط داخل همان حلقه وجود دارد. اگر بعد از بسته شدن آکولاد بخواهید از i استفاده کنید، کامپایلر خطا میدهد. این رفتار خوبی است، چون جلوی تداخل نامها را میگیرد و میتوانید در حلقه بعدی دوباره از نام i استفاده کنید. اگر واقعاً به مقدار نهایی شمارنده بعد از حلقه نیاز دارید، آن را بیرون و قبل از حلقه تعریف کنید. برای مرور نوعها و دامنه متغیرها، متغیرها در جاوا را ببینید.
خیلی وقتها اصلاً به شماره خانه نیاز ندارید و فقط میخواهید روی همه عضوها بچرخید. برای این کار، جاوا حلقه for پیشرفته یا for-each را دارد. این حلقه هر بار یک عضو را برمیدارد و در متغیری که تعریف کردهاید میگذارد. دیگر خبری از شمارنده، شرط و خطای «یکی کمتر یا یکی بیشتر» نیست.
int[] scores = {17, 12, 19, 15};
int sum = 0;
for (int score : scores) {
sum += score;
}
System.out.println("sum = " + sum);
// output: sum = 63
علامت دونقطه را «در» بخوانید: «برای هر score در scores». for-each روی آرایهها و هر چیزی که رابط Iterable را پیاده کرده باشد کار میکند؛ یعنی روی ArrayList، HashSet و بیشتر مجموعههای جاوا هم میتوانید از آن استفاده کنید. برای ساختن متن در حلقه، به جای جمع کردن رشتهها با علامت بهعلاوه، معمولاً StringBuilder در جاوا انتخاب بهتری است.
for-each برای خواندن عالی است، اما محدودیتهایی دارد. اول، به اندیس دسترسی ندارید؛ پس اگر بخواهید بدانید عضو فعلی خانه چندم است، باید از for معمولی استفاده کنید. دوم، اگر به متغیر حلقه مقدار تازه بدهید، خانه آرایه عوض نمیشود، چون متغیر فقط یک کپی از مقدار است. سوم، اگر هنگام پیمایش یک ArrayList عضوی را با remove حذف کنید، معمولاً با خطای ConcurrentModificationException روبهرو میشوید. برای حذف در حین پیمایش، از Iterator یا متد removeIf استفاده کنید.
گاهی نمیدانید حلقه چند بار باید بچرخد. مثلاً میخواهید یک عدد را آنقدر نصف کنید تا به یک برسد، یا تا وقتی کاربر عدد صفر وارد نکرده، ورودی بگیرید. در این حالت while طبیعیتر است. while فقط یک شرط دارد و قبل از هر دور آن را بررسی میکند. اگر شرط از اول نادرست باشد، بدنه حتی یک بار هم اجرا نمیشود.
int n = 40;
int steps = 0;
while (n > 1) {
n = n / 2;
steps++;
}
System.out.println("steps = " + steps);
// output: steps = 5
در این مثال نقطه شروع و تغییر بیرون از پرانتز و جدا از شرط نوشته شدهاند. همین آزادی، while را انعطافپذیر میکند، اما خطر هم دارد: اگر خط n = n / 2 را فراموش کنید، شرط هیچوقت نادرست نمیشود. هر بار که while مینویسید، از خودتان بپرسید «چه چیزی در بدنه باعث میشود شرط بالاخره false شود؟»
do-while شبیه while است، با یک تفاوت مهم: شرط را بعد از اجرای بدنه بررسی میکند. یعنی بدنه همیشه دستکم یک بار اجرا میشود. در مثال زیر شرط از همان اول نادرست است، ولی خط چاپ یک بار اجرا میشود. دقت کنید که بعد از پرانتز while در این حلقه، نقطهویرگول لازم است.
int k = 100;
do {
System.out.println("k = " + k);
k++;
} while (k < 3);
// output: k = 100
بهترین جای do-while منوهایی است که باید دستکم یک بار نمایش داده شوند. برنامه زیر منو را نشان میدهد، عددی از کاربر میگیرد و تا وقتی کاربر صفر وارد نکرده، دوباره منو را نمایش میدهد. ما این برنامه را با ورودیهای ۱، ۱ و ۰ اجرا کردیم: دو بار Hello و در پایان Bye چاپ شد.
import java.util.Scanner;
public class MenuDemo {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int choice;
do {
System.out.println("1) Say hello 0) Exit");
choice = in.nextInt();
if (choice == 1) {
System.out.println("Hello!");
}
} while (choice != 0);
System.out.println("Bye");
}
}
اگر کاربر به جای عدد، حرف وارد کند، nextInt خطای InputMismatchException میدهد. در برنامه واقعی باید ورودی را بررسی کنید، اما برای یادگیری حلقه همین نسخه ساده کافی است.
اگر بین حلقهها مردد هستید، این جدول کمک میکند. قاعده ساده این است: وقتی تعداد را میدانید یا به اندیس نیاز دارید، for؛ وقتی فقط میخواهید همه عضوها را بخوانید، for-each؛ وقتی پایان حلقه به یک اتفاق بستگی دارد، while؛ و وقتی بدنه باید دستکم یک بار اجرا شود، do-while.
| حلقه | زمان بررسی شرط | مناسب برای | نکته مهم |
|---|---|---|---|
| for | قبل از هر دور | تعداد تکرار معلوم یا نیاز به اندیس | شروع، شرط و تغییر در یک خط |
| for-each | خودکار تا پایان مجموعه | خواندن همه عضوهای آرایه یا لیست | به اندیس دسترسی ندارد |
| while | قبل از هر دور | پایان وابسته به یک اتفاق | ممکن است اصلاً اجرا نشود |
| do-while | بعد از هر دور | منو و ورودی که باید دستکم یک بار گرفته شود | بدنه حداقل یک بار اجرا میشود |

وقتی داده دوبعدی دارید، مثل جدول، ماتریس یا صندلیهای یک سالن، یک حلقه کافی نیست. حلقه بیرونی سطرها را میشمارد و حلقه درونی ستونهای هر سطر را. برای هر یک دور حلقه بیرونی، حلقه درونی کامل اجرا میشود. پس اگر بیرونی سه بار و درونی چهار بار بچرخد، بدنه درونی دوازده بار اجرا میشود.
for (int row = 1; row <= 3; row++) {
for (int col = 1; col <= 4; col++) {
System.out.print(row * col + "\t");
}
System.out.println();
}
// output:
// 1 2 3 4
// 2 4 6 8
// 3 6 9 12
حلقه تودرتو قدرتمند است، اما تعداد کارها را ضرب میکند. اگر هر دو حلقه روی هزار عضو بچرخند، بدنه یک میلیون بار اجرا میشود. پس قبل از نوشتن حلقه سوم و چهارم داخل هم، از خودتان بپرسید آیا راه سادهتری وجود دارد. برای خوانایی هم نامهای گویا مثل row و col از i و j بهترند.
گاهی وسط حلقه میفهمید که دیگر لازم نیست ادامه دهید؛ مثلاً عددی را که دنبالش بودید پیدا کردهاید. دستور break در این حالت بلافاصله از حلقه بیرون میرود. continue کار دیگری میکند: بقیه بدنه را در همین دور رد میکند و مستقیم سراغ دور بعد میرود.
for (int i = 1; i <= 7; i++) {
if (i % 2 == 0) {
continue;
}
System.out.print(i + " ");
}
// output: 1 3 5 7
int[] values = {4, 8, -3, 6};
for (int v : values) {
if (v < 0) {
System.out.println("first negative: " + v);
break;
}
}
// output: first negative: -3
continue و break کد را کوتاه میکنند، ولی اگر زیاد و پراکنده استفاده شوند، دنبال کردن مسیر برنامه سخت میشود. معمولاً یک break برای «پیدا شد، تمام» کاملاً خواناست. اگر در یک حلقه چند continue و break دارید، شاید بهتر باشد بخشی از کد را به یک متد جدا ببرید.
break معمولی فقط از نزدیکترین حلقه بیرون میرود. اگر در حلقه درونی چیزی را پیدا کردید و میخواهید از هر دو حلقه خارج شوید، میتوانید به حلقه بیرونی برچسب بدهید و break را با همان برچسب بنویسید. این امکان مفید است، اما کم از آن استفاده کنید. اغلب، بردن جستوجو به یک متد و برگرداندن نتیجه با return خواناتر است.
int[][] grid = {{1, 2, 3}, {4, 42, 6}, {7, 8, 9}};
search:
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
if (grid[r][c] == 42) {
System.out.println("found at " + r + "," + c);
break search;
}
}
}
// output: found at 1,1
اینها خطاهایی است که تقریباً هر کسی در هفتههای اول یادگیری جاوا تجربه میکند. شناختنشان کمک میکند سریعتر پیدایشان کنید. اول، خطای «یکی کم یا یکی زیاد». اگر آرایه پنج خانه دارد، اندیسها از صفر تا چهارند. شرط i <= arr.length یک دور اضافه میچرخد و خطای ArrayIndexOutOfBoundsException میگیرید. شرط درست i < arr.length است. دوم، حلقه بینهایت ناخواسته. فراموش کردن i++ در while، یا تغییر شمارنده در جهت اشتباه، رایجترین دلیل است. اگر برنامه بیپاسخ ماند و خروجی تمام نشد، اول سراغ شرط و تغییر بروید. سوم، نقطهویرگول بیجا. اگر بعد از پرانتز for یا while نقطهویرگول بگذارید، حلقه بدنهای خالی پیدا میکند و بلوکی که زیرش نوشتهاید فقط یک بار اجرا میشود. کامپایلر هم معمولاً خطایی نمیدهد، پس پیدا کردنش سخت است. چهارم، مقایسه رشتهها با علامت ==. در شرط حلقهای که ورودی متنی میگیرد، برای مقایسه محتوای رشته از equals استفاده کنید. == فقط بررسی میکند که دو متغیر به یک شیء اشاره کنند یا نه. پنجم، تغییر مجموعه در حین پیمایش با for-each. همانطور که گفتیم، حذف از ArrayList داخل for-each معمولاً خطا میدهد. برای این کار removeIf یا Iterator را به کار ببرید.

یک سؤال رایج این است که کدام حلقه سریعتر است. ما برای این مقاله هیچ آزمون سرعتی اجرا نکردهایم و عددی هم ادعا نمیکنیم. چیزی که میشود با اطمینان گفت این است که در بیشتر برنامههای روزمره، انتخاب بین for و while و for-each تفاوت محسوسی ایجاد نمیکند و انتخاب باید بر اساس خوانایی باشد. کاری که داخل بدنه انجام میدهید، مثلاً خواندن از پایگاه داده یا ساختن رشتههای بزرگ، معمولاً خیلی بیشتر از نوع حلقه اهمیت دارد. اگر واقعاً نگران سرعت هستید، اول کد درست و خوانا بنویسید، بعد با ابزار مناسب اندازه بگیرید. در جاوا اندازهگیری دقیق کار سادهای نیست، چون ماشین مجازی هنگام اجرا کد را بهینه میکند و زمان دورهای اول با دورهای بعد فرق دارد. برای چنین آزمونهایی ابزار JMH ساخته شده است. اندازهگیری با یک System.currentTimeMillis ساده معمولاً نتیجه قابل اعتمادی نمیدهد.
وقتی با حلقه راحت شدید، قدم بعدی کار با شیءهاست. در برنامههای واقعی معمولاً روی فهرستی از شیءها حلقه میزنید؛ مثلاً روی همه حسابهای بانکی، همه دانشجوها یا همه سفارشها. در آموزش کلاس و شیء در جاوا دقیقاً همین را میبینید: ساختن چند شیء و پیمایش آنها با for-each. بعد از آن، برای طراحی قراردادهای مشترک بین کلاسها، تفاوت Interface و Abstract Class را بخوانید.
برای اینکه مطالب در ذهنتان بماند، این تمرینها را خودتان بنویسید. سعی کنید قبل از دیدن راهحل، دستکم یک بار خودتان امتحان کنید. تمرین اول: جمع رقمهای یک عدد را حساب کنید. راهنمایی: باقیمانده تقسیم بر ده، رقم آخر را میدهد و تقسیم صحیح بر ده، رقم آخر را حذف میکند. چون نمیدانید عدد چند رقم دارد، while انتخاب مناسبی است. تمرین دوم: یک مثلث ستارهای با چهار سطر چاپ کنید که سطر اول یک ستاره و سطر چهارم چهار ستاره داشته باشد. میتوانید با حلقه تودرتو یا با متد repeat رشته این کار را انجام دهید.
int number = 4096;
int digitSum = 0;
while (number > 0) {
digitSum += number % 10;
number /= 10;
}
System.out.println("digit sum = " + digitSum);
// output: digit sum = 19
for (int i = 1; i <= 4; i++) {
System.out.println("*".repeat(i));
}
تمرین سوم: برنامهای بنویسید که از کاربر عدد بگیرد و تا وقتی عدد منفی وارد نکرده، عددها را جمع کند و در پایان مجموع را نمایش دهد. کدام حلقه را انتخاب میکنید و چرا؟ تمرین چهارم: در یک آرایه دوبعدی، بزرگترین عدد و جای آن را پیدا کنید.
while شرط را قبل از اجرای بدنه بررسی میکند؛ پس اگر شرط از اول نادرست باشد، بدنه اصلاً اجرا نمیشود. do-while شرط را بعد از بدنه بررسی میکند و بدنه همیشه دستکم یک بار اجرا میشود. برای منو و گرفتن ورودی تا وقتی معتبر باشد، do-while طبیعیتر است.
وقتی فقط میخواهید همه عضوهای آرایه یا مجموعه را بخوانید و به اندیس نیاز ندارید. for-each کوتاهتر است و خطای اندیس ندارد. اگر به شماره خانه نیاز دارید، میخواهید خانهای را تغییر دهید یا از آخر به اول بروید، for معمولی را انتخاب کنید.
مطمئن شوید چیزی داخل بدنه، شرط را به سمت نادرست شدن میبرد. هنگام اشکالزدایی، مقدار شمارنده یا متغیر شرط را در هر دور چاپ کنید تا ببینید واقعاً عوض میشود یا نه. اگر عمداً حلقه بیپایان مینویسید، مسیر خروج با break را واضح نگه دارید.
حلقههای for، while و do-while از نسخههای اولیه جاوا وجود داشتهاند و for-each از جاوا ۵ اضافه شده است؛ پس در هر نسخه جدیدی کار میکنند. ما مثالها را با JDK 25 که نسخه پشتیبانی بلندمدت است اجرا کردهایم. طبق صفحه پروژه JDK، تا امروز نسخه ۲۷ هم منتشر شده است، اما برای یادگیری، یک نسخه پشتیبانی بلندمدت انتخاب امنی است.
نظرات 0
برای ارسال نظر وارد حساب کاربری شوید.
هنوز نظری ثبت نشده؛ اولین نفر باشید.