آموزش Django REST Framework؛ ساخت API با جنگو قدمبهقدم
با Django REST Framework میتوانید روی پروژه جنگو یک API تمیز و امن بسازید. در این آموزش قدمبهقدم، یک API …
کلاس در جاوا نقشه است و شیء چیزی است که از روی آن نقشه ساخته میشود. در این آموزش ساده، با مثال کتاب و حساب بانکی یاد میگیرید کلاس تعریف کنید، با new شیء بسازید، فیلد و متد بنویسید و با کپسولهسازی و Access Modifier از دادهها محافظت کنید؛ همه مثالها با JDK 25 اجرا شدهاند.
آخرین بهروزرسانی: ۱۸ آبان ۱۴۰۵ — منابع بررسیشده در این روز: صفحه JDK 25، فصلهای ۶ و ۸ مشخصات زبان جاوا ۲۵، آموزشهای رسمی اوراکل و dev.java درباره کلاس و شیء و پیشنهاد ۳۹۵ درباره record؛ همه مثالها با JDK 25.0.5 اجرا شدند.
⚡ پاسخ کوتاه
کلاس تعیین میکند هر شیء چه اطلاعاتی (فیلد) و چه رفتاری (متد) دارد. با new از روی کلاس شیء میسازید و متغیر شما در واقع ارجاعی به آن شیء است. فیلدها را private کنید و فقط از طریق متدهایی که قانونها را بررسی میکنند اجازه تغییر بدهید؛ این همان کپسولهسازی است.
📑 فهرست مطالب
برنامهنویسی شیگرا یا OOP روشی برای مرتب کردن کد است. به جای اینکه دادهها را یک جا و کارهایی که روی آن دادهها انجام میشود را جای دیگر بنویسیم، هر دو را کنار هم در یک واحد میگذاریم. به این واحد «شیء» میگوییم. هر شیء چیزهایی را درباره خودش میداند و کارهایی را بلد است انجام دهد. یک مثال روزمره بزنیم. یک حساب بانکی را در نظر بگیرید. حساب، صاحب و موجودی دارد؛ اینها اطلاعات آن هستند. همچنین میشود به آن پول واریز کرد یا از آن برداشت کرد؛ اینها کارهایش هستند. در شیگرایی، اطلاعات و کارهای حساب را در یک جا تعریف میکنیم. نتیجه این است که هر کس بخواهد با حساب کار کند، فقط از همان کارهای تعریفشده استفاده میکند و نمیتواند موجودی را هر طور خواست دستکاری کند. جاوا از اول با تکیه بر همین ایده طراحی شده است. حتی سادهترین برنامه جاوا هم داخل یک کلاس نوشته میشود. اگر تازه شروع کردهاید، مقدمهای بر جاوا را بخوانید تا با JDK و نحوه اجرای برنامه آشنا شوید. مثالهای این مقاله را با JDK 25، نسخه پشتیبانی بلندمدت (Temurin 25.0.5)، کامپایل و اجرا کردهایم و خروجیها واقعیاند.
سادهترین تعریف این است: کلاس نقشه است و شیء چیزی است که از روی نقشه ساخته میشود. نقشه یک خانه میگوید خانه چند اتاق دارد و در کجاست، اما خودش خانه نیست و نمیتوان در آن زندگی کرد. از روی یک نقشه میتوان ده خانه ساخت که هر کدام صاحب و رنگ و وسایل خودش را دارد. در جاوا هم کلاس میگوید هر شیء چه اطلاعاتی دارد و چه کارهایی بلد است، و شیء نمونه واقعیای است که در حافظه ساخته میشود. به همین دلیل به شیء «نمونه» یا instance هم میگویند. وقتی میگوییم «یک نمونه از کلاس BankAccount»، یعنی یک حساب مشخص با صاحب و موجودی خودش. دو شیء از یک کلاس ساختار یکسانی دارند، اما مقدارهایشان مستقل است. اگر به حساب سارا پول واریز کنید، موجودی حساب رضا تغییری نمیکند.
| موضوع | کلاس | شیء |
|---|---|---|
| تعریف | نقشه و قالب | نمونه ساختهشده از قالب |
| تعداد | یک تعریف | هر تعداد که لازم باشد |
| زمان ایجاد | هنگام نوشتن کد | هنگام اجرا با new |
| مقدار فیلدها | فقط نوع فیلدها را تعیین میکند | مقدار مخصوص خودش را دارد |
| مثال | BankAccount | حساب سارا و حساب رضا |

بیایید یک کلاس خیلی ساده برای کتاب بنویسیم. هر کتاب عنوان و تعداد صفحه دارد و میتواند اطلاعات خودش را چاپ کند. کلاس با کلمه class شروع میشود و بعد نام آن میآید. قرارداد رایج در جاوا این است که نام کلاس با حرف بزرگ شروع شود. هر کلاس عمومی هم معمولاً در فایلی با همان نام قرار میگیرد؛ یعنی کلاس Book در فایل Book.java.
public class Book {
String title;
int pages;
void printInfo() {
System.out.println(title + " (" + pages + " pages)");
}
}
دو خط اول بدنه، فیلدها هستند؛ یعنی متغیرهایی که اطلاعات هر کتاب را نگه میدارند. printInfo یک متد است؛ یعنی کاری که هر کتاب بلد است انجام دهد. توجه کنید که تا اینجا هیچ کتابی ساخته نشده است. ما فقط گفتهایم «اگر کتابی ساختیم، این شکلی خواهد بود».
برای ساختن شیء از کلمه new استفاده میکنیم. new در حافظه جایی برای شیء تازه میسازد و یک ارجاع، یعنی نشانی آن شیء، را برمیگرداند. ما این ارجاع را در یک متغیر نگه میداریم تا بعداً بتوانیم با شیء کار کنیم.
Book first = new Book();
first.title = "Java Basics";
first.pages = 320;
Book second = new Book();
second.title = "Clean Code";
second.pages = 464;
first.printInfo();
second.printInfo();
Book empty = new Book();
System.out.println(empty.title + " / " + empty.pages);
// output:
// Java Basics (320 pages)
// Clean Code (464 pages)
// null / 0
با علامت نقطه به فیلدها و متدهای هر شیء دسترسی داریم. first.printInfo() یعنی «ای کتاب اول، اطلاعاتت را چاپ کن». خط آخر خروجی هم نکته مهمی دارد: اگر به فیلدها مقدار ندهید، جاوا مقدار پیشفرض میگذارد. برای عددها صفر، برای boolean مقدار false و برای انواع ارجاعی مثل String مقدار null. این مقدارهای پیشفرض فقط برای فیلدها هستند. متغیرهای محلی داخل متدها مقدار پیشفرض ندارند و اگر بدون مقداردهی از آنها استفاده کنید، کامپایلر خطا میدهد. برای مرور انواع داده، انواع داده در جاوا را ببینید.
این یکی از مهمترین نکتهها برای مبتدیهاست. متغیری مثل first خود کتاب نیست؛ نشانی کتاب را نگه میدارد. اگر بنویسید Book alias = first، شیء تازهای ساخته نمیشود. حالا دو متغیر به یک کتاب اشاره میکنند و تغییر از طریق یکی، از طریق دیگری هم دیده میشود. در بخش کپسولهسازی همین را با حساب بانکی امتحان میکنیم. اگر متغیری به هیچ شیئی اشاره نکند، مقدارش null است. صدا زدن متد روی null خطای معروف NullPointerException را ایجاد میکند. هر وقت این خطا را دیدید، اول بپرسید: «این متغیر کجا قرار بود با new یا از جای دیگری مقدار بگیرد؟»
فیلدها وضعیت شیء را نگه میدارند و متدها رفتار آن را تعریف میکنند. یک قاعده خوب این است که متدها روی همان دادههای شیء خودشان کار کنند. مثلاً متد deposit باید موجودی همان حساب را تغییر دهد، نه حساب دیگری را. اگر متدی اصلاً به فیلدهای شیء نیازی ندارد، شاید جای درستش جای دیگری باشد یا باید static باشد. متدهایی که روی یک شیء مشخص صدا زده میشوند، متد نمونه نام دارند. متدهای static به کلاس تعلق دارند، نه به یک شیء خاص؛ مثل Math.max. تفاوت این دو را در متدهای استاتیک و نمونه در جاوا مفصل توضیح دادهایم و نحوه صدا زدن انواع متد را در فراخوانی متد در جاوا میبینید. اینجا فقط کافی است بدانید متدهای کلاسهای این آموزش، متد نمونهاند.
داخل متدها و سازندهها، this به شیئی اشاره میکند که متد روی آن صدا زده شده است. بیشترین کاربرد this وقتی است که نام پارامتر با نام فیلد یکی است. مثلاً در this.owner = owner، سمت چپ فیلد شیء است و سمت راست پارامتری که به سازنده داده شده. بدون this، جاوا فکر میکند هر دو طرف همان پارامترند و فیلد مقدار نمیگیرد.
در مثال کتاب، اول شیء را ساختیم و بعد فیلدها را یکییکی پر کردیم. این روش دو مشکل دارد: ممکن است مقداردهی یک فیلد فراموش شود، و شیء برای مدتی در حالت ناقص میماند. سازنده یا constructor این مشکل را حل میکند. سازنده شبیه متد است، اما همنام کلاس است و نوع بازگشتی ندارد. وقتی new را صدا میزنید، سازنده اجرا میشود. اگر هیچ سازندهای ننویسید، جاوا یک سازنده پیشفرض بدون پارامتر برای کلاس میسازد؛ برای همین new Book() کار کرد. به محض اینکه خودتان سازندهای با پارامتر بنویسید، آن سازنده پیشفرض دیگر خودکار ساخته نمیشود. در این مقاله فقط همین مقدار درباره سازنده لازم داریم. سازندههای چندگانه، زنجیره سازندهها با this و جزئیات بیشتر، موضوع یک آموزش جداگانه است.
در کلاس Book هر کسی میتوانست بنویسد first.pages = -50. هیچ چیزی جلوی این مقدار بیمعنی را نمیگرفت. کپسولهسازی یعنی فیلدها را پنهان کنیم و فقط از طریق متدهایی که قوانین را رعایت میکنند، اجازه تغییر بدهیم. اینطوری شیء همیشه در حالت درست میماند و اگر روزی قانونی عوض شد، فقط یک جا را تغییر میدهیم. حالا همان حساب بانکی را درست بنویسیم. فیلدها private هستند؛ یعنی فقط کد داخل خود کلاس به آنها دسترسی دارد. واریز فقط مبلغ مثبت را میپذیرد و برداشت اگر موجودی کافی نباشد، انجام نمیشود. برای مبلغ از long استفاده کردهایم تا مثال ساده بماند. در برنامههای مالی واقعی، نوع داده پول و گرد کردن را با دقت بیشتری طراحی کنید.
public class BankAccount {
private final String owner;
private long balance;
public BankAccount(String owner, long openingBalance) {
if (openingBalance < 0) {
throw new IllegalArgumentException("opening balance cannot be negative");
}
this.owner = owner;
this.balance = openingBalance;
}
public void deposit(long amount) {
if (amount <= 0) {
throw new IllegalArgumentException("amount must be positive");
}
balance += amount;
}
public boolean withdraw(long amount) {
if (amount <= 0 || amount > balance) {
return false;
}
balance -= amount;
return true;
}
public long getBalance() {
return balance;
}
public String getOwner() {
return owner;
}
@Override
public String toString() {
return owner + ": " + balance;
}
}

چند نکته در این کد هست. اول، owner با final تعریف شده؛ یعنی بعد از سازنده دیگر عوض نمیشود. دوم، برای balance متد set عمومی نگذاشتهایم؛ موجودی فقط با واریز و برداشت تغییر میکند. این خیلی بهتر از یک setBalance است که هر عددی را بپذیرد. سوم، متد toString را بازنویسی کردهایم تا وقتی شیء را چاپ میکنیم، به جای یک متن نامفهوم، صاحب و موجودی را ببینیم.
کلمههایی مثل private و public را Access Modifier یا سطح دسترسی مینامند. جاوا چهار سطح دارد. قاعده ساده برای شروع: فیلدها را private کنید و فقط متدهایی را public کنید که واقعاً قرار است بیرون از کلاس استفاده شوند. هر چیزی که پنهان است را بعداً راحتتر میتوانید تغییر دهید.
| سطح دسترسی | داخل همان کلاس | همان بسته (package) | زیرکلاس در بسته دیگر | همه جا |
|---|---|---|---|---|
| private | ✅ | — | — | — |
| بدون کلمه (پیشفرض) | ✅ | ✅ | — | — |
| protected | ✅ | ✅ | ✅ | — |
| public | ✅ | ✅ | ✅ | ✅ |
خیلی از آموزشها برای هر فیلد یک get و یک set میسازند. این کار در ظاهر کپسولهسازی است، اما اگر setter هر مقداری را بدون بررسی بپذیرد، عملاً با فیلد public فرقی ندارد. قبل از ساختن setter بپرسید: آیا واقعاً لازم است این مقدار از بیرون عوض شود؟ اگر لازم است، چه قانونی باید رعایت شود؟ گاهی یک متد با معنای روشن، مثل deposit یا rename، خیلی بهتر از setX است.
حالا کلاس حساب را در عمل امتحان کنیم. دو حساب میسازیم، به یکی واریز میکنیم و از دیگری بیش از موجودی برداشت میکنیم. بعد یک ارجاع دوم به حساب سارا میسازیم و نشان میدهیم که هر دو متغیر به یک شیء اشاره دارند. در پایان، چند حساب را در یک آرایه میگذاریم و با حلقه for-each مجموع موجودیها را حساب میکنیم.
public class Main {
public static void main(String[] args) {
BankAccount sara = new BankAccount("Sara", 500);
BankAccount reza = new BankAccount("Reza", 200);
sara.deposit(300);
boolean ok = reza.withdraw(1000);
System.out.println(sara); // Sara: 800
System.out.println(reza); // Reza: 200
System.out.println("withdraw ok? " + ok);
BankAccount alias = sara; // same object, second reference
alias.deposit(100);
System.out.println(sara.getBalance()); // 900
BankAccount[] accounts = { sara, reza, new BankAccount("Mina", 50) };
long total = 0;
for (BankAccount a : accounts) {
total += a.getBalance();
}
System.out.println("total = " + total);
}
}
// output:
// Sara: 800
// Reza: 200
// withdraw ok? false
// 900
// total = 1150
برداشت هزار از حساب رضا انجام نشد و موجودیاش دویست ماند، چون کلاس اجازه نداد. واریز از طریق alias هم موجودی سارا را به نهصد رساند، چون alias و sara یک شیء هستند. حلقه پایانی هم نشان میدهد چطور روی فهرستی از شیءها کار میکنیم. اگر با حلقهها هنوز راحت نیستید، آموزش حلقهها در جاوا را ببینید.

علامت == برای شیءها بررسی میکند که دو متغیر به یک شیء اشاره کنند. یعنی دو حسابی که صاحب و موجودی یکسان دارند اما جدا ساخته شدهاند، با == برابر نیستند. اگر بخواهید برابری را بر اساس محتوا تعریف کنید، باید متد equals و همراه آن hashCode را بازنویسی کنید. این همان دلیلی است که رشتهها را با equals مقایسه میکنیم، نه با ==.
گاهی کلاس فقط برای نگه داشتن چند مقدار است؛ مثل یک نقطه با مختصات x و y. از جاوا ۱۶، record این کار را خیلی کوتاه میکند. با یک خط، جاوا فیلدهای نهایی، سازنده، متدهای خواندن، equals، hashCode و toString را خودش میسازد.
public record Point(int x, int y) { }
Point p = new Point(3, 4);
System.out.println(p); // Point[x=3, y=4]
System.out.println(p.x() + p.y()); // 7
System.out.println(p.equals(new Point(3, 4))); // true
record تغییرناپذیر است و برای دادههای ساده عالی است. اما برای شیئی مثل حساب بانکی که وضعیتش در طول زمان عوض میشود و قوانین دارد، کلاس معمولی انتخاب درستتری است.
کلاس و شیء پایه شیگراییاند، اما شیگرایی فقط همین نیست. وراثت به کلاسها اجازه میدهد رفتار مشترک را از کلاس دیگری بگیرند و چندریختی اجازه میدهد یک متد روی انواع مختلف شیء رفتار متناسب داشته باشد. این دو موضوع را در آموزشهای بعدی سری جاوا جداگانه پوشش میدهیم. اینترفیس و کلاس انتزاعی هم ابزارهایی برای تعریف قرارداد مشترک بین کلاسها هستند. اینترفیس میگوید یک کلاس چه کارهایی را باید بتواند انجام دهد و کلاس انتزاعی میتواند بخشی از پیادهسازی مشترک را هم داشته باشد. تفاوت این دو و زمان استفاده از هر کدام را در تفاوت Interface و Abstract Class در جاوا با مثال توضیح دادهایم. اگر به طراحی کلاسهای تمیز علاقه دارید، اصول SOLID به زبان ساده قدم خوب بعدی است.
اول، فراموش کردن new. نوشتن Book b; فقط یک متغیر خالی میسازد و استفاده از آن قبل از مقداردهی خطا میدهد. دوم، اشتباه گرفتن کپی شیء با کپی ارجاع. همانطور که دیدیم، Book b2 = b1 شیء تازه نمیسازد. سوم، public کردن همه فیلدها. این کار در ابتدا راحت است، اما بعداً هر جای برنامه میتواند داده را خراب کند و پیدا کردن مقصر سخت میشود. چهارم، فراموش کردن this در سازنده وقتی نام پارامتر و فیلد یکی است. نتیجه این است که فیلد مقدار پیشفرض میگیرد و برنامه بدون خطای کامپایل، رفتار عجیبی نشان میدهد. پنجم، کلاسهای بزرگ همهکاره. اگر یک کلاس هم داده را نگه میدارد، هم فایل مینویسد، هم ایمیل میفرستد و هم گزارش میسازد، احتمالاً باید به چند کلاس کوچکتر تقسیم شود.
تمرین اول: کلاس Student بسازید که نام و معدل دارد. معدل باید بین صفر تا بیست باشد و اگر عدد نامعتبر داده شد، تغییر نکند. یک متد isPassed هم بنویسید که اگر معدل حداقل ده بود، true برگرداند. تمرین دوم: آرایهای از پنج دانشجو بسازید و با حلقه for-each، بالاترین معدل و نام صاحب آن را پیدا کنید. تمرین سوم: به کلاس BankAccount متدی برای انتقال پول به حساب دیگر اضافه کنید که فقط وقتی موجودی کافی است، انجام شود. فکر کنید این متد باید true یا false برگرداند یا در صورت خطا استثنا پرتاب کند، و دلیل انتخابتان را بنویسید.
کلاس تعریف و نقشه است و مشخص میکند هر شیء چه فیلدها و متدهایی دارد. شیء نمونه واقعی ساختهشده از کلاس در حافظه است که مقدارهای خودش را دارد. از یک کلاس میتوان هر تعداد شیء مستقل ساخت.
تا شیء کنترل دادههایش را در دست داشته باشد. با private کردن فیلدها، هر تغییری باید از متدهای کلاس بگذرد و آن متدها میتوانند قوانین را بررسی کنند. این کار جلوی مقدارهای نامعتبر را میگیرد و تغییر پیادهسازی در آینده را راحتتر میکند.
نه. فقط برای چیزهایی که واقعاً لازم است از بیرون خوانده یا تغییر داده شوند، متد بسازید. setter بدون بررسی، کپسولهسازی را عملاً بیاثر میکند. متدهای معنادار مثل deposit معمولاً طراحی بهتری هستند.
this به همین شیئی اشاره میکند که متد یا سازنده روی آن در حال اجراست. رایجترین کاربردش جدا کردن فیلد از پارامتر همنام است، مثل this.owner = owner. در متدهای static، this وجود ندارد، چون آن متدها به شیء خاصی تعلق ندارند.
برای دادههای ساده و تغییرناپذیر، record کد را خیلی کوتاهتر میکند. اما اگر شیء وضعیت متغیر و قوانین رفتاری دارد، مثل حساب بانکی، کلاس معمولی مناسبتر است. record از جاوا ۱۶ به صورت نهایی در زبان وجود دارد.
نظرات 0
برای ارسال نظر وارد حساب کاربری شوید.
هنوز نظری ثبت نشده؛ اولین نفر باشید.