آموزش مقدماتی جاوا (بخش سوم) – از صفر تا صد

۱۷۳ بازدید
آخرین به‌روزرسانی: ۰۶ شهریور ۱۴۰۲
زمان مطالعه: ۹ دقیقه
آموزش مقدماتی جاوا (بخش سوم) – از صفر تا صد

در بخش قبلی این سری مطالب آموزش مقدماتی با برخی از مفاهیم اساسی جاوا آشنا شدیم. در این بخش نیز برخی مفاهیم دیگر از این زبان مهم برنامه‌نویسی را با هم مرور می‌کنیم. اگر شما به تازگی با این زبان آشنا شده‌اید، می‌توانید مطالعه این سری مطالب راهنما را از بخش اول آن آغاز کنید. اگر در زمینه این زبان صاحب تجربه هستید، می‌توانید با مطالعه این مطالب، محفوظات خود را یک بار دیگر مرور کنید. پس تا پایان این نوشته نیز با ما همراه باشید.

نخ (Thread)

منظور از نخ یک برنامه اجرایی کوچک است. هر پردازش می‌تواند شامل چند نخ باشد و در این صورت یک پردازش سبک خواهد بود. نخ‌ها را می‌توان به دو روش مورد استفاده قرار داد:

  1. بسط دادن کلاس Thread
  2. پیاده‌سازی اینترفیس Runnable
1public class Test extends Thread {
2public void run(){
3// logic
4}
5Thread t1 = new Test();
6// Thread t1 = new Thread(new Test()); FOR RUNNABLE
7t1.Start();
8}

روش پیاده‌سازی اینترفیس Runnable ترجیح بیشتری دارد، زیرا دامنه‌ای از وراثت چندگانه وجود خواهد داشت. متد ()start به فراخوانی متد ()run می‌پردازد؛ اما نمی‌توانیم مستقیماً ()run را فراخوانی کنیم، زیرا نخ جدیدی ایجاد نمی‌کند. ()start موجب می‌شود که نخ کنونی به اجرای run() بپردازد. این وضعیت شبیه به فراخوانی متد مستقیم بدون هیچ نخی است.

  • ()Object.wait موجب می‌شود که نخ تا زمانی که notify فراخوانی شود، مسدود شود.
  • ()Object.notify موجب می‌شود که یک نخ منفرد که در حالت انتشار روی آن شیء است بیدار شود و در حالت runnable قرار گیرد.
  • ()Object.notifyAll همه نخ‌هایی را که در حالت انتشار روی آن شیء هستند، بیدار می‌کند.
  • ()Thread.yield نخ جاری را در حالت runnable قرار می‌دهد و نخ بعدی را می‌گیرد.
  • ()Thread.sleep نخ را به مدت چند میلی‌ثانیه روی حالت خواب/تعلیق قرار می‌دهد. می‌توان زمانی را به عنوان پارامتر به این متد ارسال کرد.

نخ Demon یک نخ با اولویت پایین مانند نخ garbage collection است که همواره در پس‌زمینه اجرا می‌شود. برای اجتناب از بروز بن‌بست باید اطمینان پیدا کنیم که کدی را همگام‌سازی نکرده‌ایم که موجب مسدودسازی فراخوانی شود.

Mutex و Semaphore

این ابزارها برای همگام‌سازی نخ‌ها استفاده می‌شوند. یک Semaphore یک نوع بسیار بی‌قید از شیء lockable است. یک Semaphore مفروض دارای شماره بیشینه از پیش تعریف شده و شماره کنونی مشخصی است. Lock انحصار متقابل ایجاد می‌کند؛ اما موجب حل مشکلاتی مانند ایجاد صف یا مرتب‌سازی مثلاً در مورد وظایف چاپ یا مشکلات محصولات مصرفی نمی‌شود. در این موارد باید از Semaphore استفاده کنیم.

1public class Semaphore {
2int value;
3// No of users that can use Resource
4public Semaphore(int init) {
5if (init < 0) {
6init = 0;
7}
8value = init;
9}
10// Acquiring Resource
11public synchronized void down() {
12while (value == 0) {
13try {
14wait();
15} catch (InterruptedException e) {
16}
17value —;
18}
19}
20// Releasing Resource
21public synchronized void up() {
22value++;
23notify();
24}
25}

ما کلاس Semaphore را پیش از متد ()start مقداردهی می‌کنیم. در زمان اجرا ابتدا ()semaphoreObject.down را فراخوانی می‌کنیم تا قفل کند و در زمان کامل شدن آن، ()semaphoreObject.up را فراخوانی می‌کنیم.

Mutex اختصاری برای «Semaphore منحصراً متقابل» (Mutual Exclusion Semaphore) است. در واقع این یک نوع از شیء lockable است که در هر زمان دقیقاً از سوی یک نخ می‌تواند مالکیت شود. تنها نخی که مالک قفل است می‌تواند قفل را در حالت mutex باز کند. زمانی که mutex قفل می‌شود، هر تلاشی برای به دست آوردن قفل ناموفق بوده یا مسدود می‌شود، حتی اگر آن تلاش از سوی خود نخ انجام گیرد.

مدیریت خطا

خطا یا Error یک شکست پیوند دینامیک یا شکست در کد ماشین است که بروز آن نامحتمل است.

استثناهای بررسی نشده استثناهای زمان اجرا مانند nullPointerException هستند که در برنامه اجرایی JVM پدید می‌آیند.

1public class MyException extends Exception {
2private String errorCode = “Unkown_Exception”;
3public MyException(String message, String errorCode){
4super(message);
5this.errorCode = errorCode;
6}
7Public String getErrorCode(){
8Return this.errorCode;
9}
10}

کلیدواژه throw برای ارسال یک استثنا به «محیط زمان اجرا» (runtme enviroment) و جهت مدیریت آن استفاده می‌شود. زمانی که یک استثنا در یک متد ارسال می‌شود و مدیریت نمی‌شود، باید از کلیدواژه throws در امضای متد استفاده کنیم، تا برنامه فراخوانی کننده بداند که چه استثناهایی می‌تواند از سوی متد ارسال شود.

کلاس Observable و اینترفیس Observer

یک الگوی طراحی به نام Observer وجود دارد که برای اطلاع‌رسانی به چند شیء (Observer) در مواردی که اتفاقی برای یک وهله (Observable) رخ می‌دهد مفید است. این کلاس در Ajax برای اطلاع‌رسانی به چند شیء در موارد اتفاق افتادن یک رویداد (link onclick) استفاده می‌شود. با استفاده از این دو می‌توان کارهای زیر را انجام داد:

  1. یک کلاس Observable داشته باشیم.
  2. چند کلاس Observer داشته باشیم.
  3. Observer-ها را با استفاده از متد addObserver در یک شیء Observable ثبت کنیم.
  4. زمانی که می‌خواهیم به همه Observer-ها اطلاع دهیم که اتفاقی افتاده است، می‌توانیم از notifyObserver استفاده کنیم.

JDBC

JDBC یک API یعنی مجموعه‌ی از کلاس‌ها و اینترفیس‌ها در جاوا است که برای اتصال و اجرای کوئری پایگاه داده استفاده می‌شود. این API از درایورهای jdbc برای اتصال به پایگاه داده استفاده می‌کند. درایور JDBC یک کامپوننت نرم‌افزاری است که به اپلیکیشن جاوا امکان می‌دهد با پایگاه داده تعامل پیدا کند. سه گزاره JDBC وجود دارند:

  1. Statement: کوئری هر بار کامپایل می‌شود.
  2. PreparedStatement: کوئری تنها یک بار کامپایل می‌شود و از این رو عملکرد بهتری ارائه می‌کند.
  3. CallableStatement: رویه‌ها و تابع‌ها با این گزاره اجرا می‌شوند.

کلاس DriverManager به مدیریت درایورهای ثبت‌شده می‌پردازد. شیء ResultSet نماینده ردیفی از یک جدول است. اینترفیس ResultSetMetaData نیز اطلاعات جدول را از قبیل تعداد کل ستون‌ها، نام ستون، نوع ستون و موارد دیگر بازگشت می‌دهد.

نمونه کد آن چنین است:

1@Resource(mappedName = “jdbc/DarmAircom”)
2private javax.sql.DataSource dataSource;
3Connection connection = dataSource.getConnection();
4CallableStatement statement = connection.prepareCall(“call ABC.ABC_PKG.PROCEDURE_NAME(?,?,?,?,?,?,?,?,?,?,?,?,?));
5statement.execute();
6statement.close();
7connection.close();

خواندن و نوشتن فایل

چندین الگو برای خواندن و نوشتن فایل‌ها در زبان جاوا وجود دارد که در ادامه هر کدام را توضیح می‌دهیم:

  • ByteStream: برای خواندن و نوشتن داده‌های باینری استفاده می‌شود و در آن از جریان بایت بهره گرفته شده است.
  • CharacterStreams: به جای بایت با کاراکترها کار می‌کند.
  • FileInputStream: شامل بایت ورودی از یک فایل است و یک جریان ورودی را پیاده‌سازی می‌کند.
  • FileOutputStream: این متد برای نوشتن داده‌ها در یک فایل استفاده می‌شود و همچنین یک جریان خروجی را پیاده‌سازی می‌کند.

خواندن از فایل

کلاس FileReader ابزاری عمومی برای خواندن کاراکترها از یک فایل است. کلاس BufferedReader می‌تواند پیرامون Reader-هایی مانند FileReader قرار گیرد تا ورودی را بافر کرده و کارایی را بهبود بخشد.

1public static void readFromFile(String fileName) throws IOException {
2int total = 0;
3BufferedReader in = new BufferedReader( new FileReader(fileName));
4for ( String s = in.readLine(); s != null; s = in.readLine() ) {
5// GOT EACH LINE
6}
7in.close();
8}

نام فایل می‌تواند چیزی مانند C:/Users/chellapilla_m/Desktop/task.txt باشد.

نوشتن در یک فایل

در کد نمونه زیر روش نوشتن در یک فایل با استفاده از FileOutputStream ارائه شده است.

1File fout = new File(file_location_string);
2FileOutputStream fos = new FileOutputStream(fout);
3BufferedWriter out = new BufferedWriter(new OutputStreamWriter(fos));
4out.write(“something”);
5Using FileWriter:
6FileWriter fstream = new FileWriter(file_location_string);
7BufferedWriter out = new BufferedWriter(fstream);
8out.write(“something”);

FileOutputStream به منظور نوشتن جریان‌هایی از بایت‌های خام مانند داده‌های تصویر طراحی شده است. برای نوشتن جریان‌هایی از کاراکترها باید از FileWriter استفاده کنید.

الگوهای طراحی

در این بخش به بررسی الگوهای طراحی رایج در اپلیکیشن‌های جاوا می‌پردازیم.

الگوی سینگلتون

الگوی «سینگلتون» (Singleton) برای هر بارگذار کلاس در JVM تنها یک بار وهله‌سازی می‌شود. Constructor باید دسترسی خصوصی داشته باشد و از این رو نمی‌تواند خارج از کلاس وهله‌سازی شود. تنها راه برای وهله‌سازی یک وهله از طریق متد ()getInstance یا دسترسی عمومی است. این متد می‌تواند یک متد استاتیک باشد. کلاس SessionFactory و کلاس logger نمونه‌هایی از کلاس‌های سینگلتون هستند.

1public class Singleton {
2private static Singleton instance = new Singleton();
3private singleton() {}
4public static Singleton getInstance(){
5return instance;
6}
7}

الگوی Factory

الگوی Factory یکی از رایج‌ترین الگوهای طراحی در جاوا محسوب می‌شود. در الگوی Factory ما یک شیء را بدون افشای منطق ایجاد به کلاینت و با ارجاع به شیء جدیداً ایجاد شده با استفاده از یک اینترفیس مشترک ایجاد می‌کنیم. به مثال زیر توجه کنید:

1interface Dog {
2public void speak ();
3}
4class Poodle implements Dog {
5public void speak() {
6System.out.println(“The poodle says \”arf\””);
7}
8}
9class Rottweiler implements Dog {
10public void speak() {
11System.out.println(“The Rottweiler says (in a very deep voice) \”WOOF!\””);
12}
13}
14class DogFactory {
15public static Dog getDog(String criteria) {
16if ( criteria.equals(“small”) )
17return new Poodle();
18else if ( criteria.equals(“big”) )
19return new Rottweiler();
20return null;
21}
22}
23public class JavaFactoryPatternExample {
24public static void main(String[] args) {
25Dog dog = DogFactory.getDog(“small”);
26dog.speak();
27dog = DogFactory.getDog(“big”);
28dog.speak();
29}
30}

الگوی Adaptor

استفاده از الگوی Adaptor در مواردی است که بخواهیم دو اینترفیس نامرتبط با یکدیگر همکاری کنند. برای نمونه به کد زیر توجه کنید:

1public interface SocketAdapter {
2public Volt get120Volt();
3public Volt get12Volt();
4public Volt get3Volt();
5}

الگوی Builder

الگوی Builder بسطی از الگوی Factory است که در آن کلاس Builder یک شیء پیچیده را در چند مرحله ایجاد می‌کند.

چند نکته مهم

کلاس java.util.date نمایش‌دهنده تاریخ و زمان روز است. کلاس java.sql.Date صرفاً تاریخ را نمایش می‌دهد و مکمل java.sql.Time است که تنها زمان روز را نشان می‌دهد؛ اما بسطی از java.util.Date نیز محسوب می‌شود. «همگام‌سازی» (Synchronization) به قابلیت کنترل دسترسی چندین نخ به منابع مشترک گفته می‌شود.

متدهای استاتیک همگام‌سازی شده یک lock روی کلاس «Class» دارد به طوری که وقتی یک نخ وارد متد استاتیک همگام‌سازی‌شده می‌شود، خود کلاس از سوی thread monitor قفل می‌شود و هیچ نخ دیگری نمی‌تواند هیچ متد همگام‌سازی‌شده استاتیک دیگر را روی آن کلاس وارد کند. این وضعیت مخالف متدهای وهله‌ای است، چون نخ‌های چندگانه می‌توانند به «متدهای وهله‌ای همگام‌سازی شده یکسان» به صورت همزمان برای وهله‌های مختلف دسترسی داشته باشند:

1public synchronized void synchronizedMethod() {}
  • یک Dump از نخ در واقع فهرست کاملی از نخ‌های فعال است.
  • «نشت نخ» (Thread Leak) زمانی رخ می‌دهد که یک اپلیکیشن ارجاع خود به شیء نخ را به طرز صحیحی آزاد نکرده باشد. به دلیل این واقعیت، برخی نخ‌ها در فرایند Garbage Collection جمع‌آوری نمی‌شوند و تعداد نخ‌های استفاده نشده در طی زمان افزایش می‌یابد.
  • یک Pool نخ به مجموعه‌ای از نخ‌ها گفته می‌شود که روی آن‌ها می‌توان یک وظیفه زمان‌بندی را انجام داد. بدین ترتیب دیگر نیازی به ایجاد نخ جدید برای هر وظیفه وجود ندارد.
  • Constructor-ها نمی‌توانند همگام‌سازی شوند، زیرا نخ‌های دیگر نمی‌توانند شیئی را که پیش از پایان یافتن نخ ایجاد شده است مشاهده کنند.
  • متد Run و کلاس Runnable می‌توانند همگام‌سازی شوند. اگر متد run را همگام‌سازی کنید، در این صورت قفل روی شیء Runnable می‌تواند پیش از اجرای متد run اشغال شود.
  • یک کلاستر به گروهی از رایانه‌ها گفته می‌شود که هر یک مستقلاً نرم‌افزاری را اجرا می‌کنند. کلاسترسازی جهت ایجاد دسترس‌پذیری بالا برای یک نرم‌افزار سرور ضروری است. هدف اصلی کلاسترسازی رسیدن به دسترس‌پذیری 100 درصدی و یا خاموشی صفر در خدمات‌دهی است.
  • «توازن بار» (Load Balancing) یک تکنیک ساده برای توزیع بار کاری در میان چندین رایانه یا کلاستر است.
  • Fail Over به معنی این است که وقتی یک رایانه از کار می‌افتد به رایانه دیگری سوئیچ کنیم.
  • اپلیکیشن‌های JEE از مفهوم توزیع وب اپلیکیشن‌ها برای ارائه session-failover و ایجاد توزان بار استفاده می‌کنند. این کار از طریق افزودن تگ distributable به صورت distributable/ در فایل web.xml صورت می‌گیرد.
  • جاوا همواره از «ارسال با مقدار» (pass-by-value) استفاده می کد. نکته‌ای که درک آن دشوار است این است که جاوا اشیا را با ارجاع ارسال می‌کند ولی خود این ارجاع‌ها با مقدار ارسال می‌شوند.
  • در جاوا متدهای ()Arrays.sort از «مرتب‌سازی ادغامی» (merge sort) یا مرتب‌سازی سریع تنظیم‌شده، بسته به انواع داده و برای پیاده‌سازی کارآمد سوئیچ از مرتب‌سازی درجی در مواردی که آرایه‌ای کمتر از هفت عنصر داشته باشد استفاده می‌کنند. Arrays.sort از سوی کلاس‌های Collection به صورت غیرمستقیم استفاده می‌شود.
  • مقدار 0xDEADBEEF یا «dead beef» عموماً برای نشان دادن از کار افتادن نرم‌افزار یا بن‌بستی در سیستم‌های جاسازی شده مورد استفاده قرار می‌گیرد. DEADBEEF در ابتدا برای نشانه‌گذاری نواحی جدیداً تخصیص یافته از حافظه که هنوز مقداردهی اولیه نشده بودند استفاده می‌شد. زمانی که مشغول اسکن کردن dump حافظه هستیم، به راحتی می‌توانیم DEADBEEF را مشاهده کنیم. DEADBEEF از سوی سیستم‌های IBM RS/6000، Mac OS روی پردازنده‌های PowerPC 32 بیتی و Commodoro Amgia به عنوان یک مقدار جادویی دیباگ مورد استفاده قرار می‌گیرد. روی سولاریس از شرکت Sun Microsystems موجب آزادسازی حافظه می‌شود. روی OpenVM که بر روی پردازنده‌های Alpha اجرا می‌شود DEAD_BEEF را می‌توان با فشردن CTRL-T مشاهده کرد. کنسول DEC Alpha SRM یک پردازش زمینه دارد که خطاهای حافظه را به دام می‌اندازد و از سوی PS به عنوان «BeefEater waiting on 0xdeadbeef» شناسایی می‌شود.

برخی پرسش‌های رایج در مورد جاوا و پاسخ‌های مربوطه

در این بخش برخی از سؤالاتی که غالباً برای افراد مبتدی پیش می‌آید به همراه پاسخ‌هایشان گردآوری کرده‌ایم.

  • سؤال: چرا باید تابعی به نام ()main داشته باشیم؟
    • اجرای برنامه از متد ()main آغاز می‌شود.
  • سؤال: چرا متد main عمومی است؟
    • به این منظور که از سوی JVM قابل دسترس باشد و JVM بتواند اجرای برنامه را از خارج از کلاس آغاز کند.
  • سؤال: چرا متد main استاتیک است؟
    • به این دلیل که بدون نیاز به هر شیئی بتوان برنامه را اجرا کرد.
  • سؤال: چرا از []String args استفاده می‌کنیم؟
    • پارامترها مانند آرگومان‌های خط فرمان هستند و تنها طراحان جاوا آن را اجباری ساخته‌اند. این کار هیچ مزیت خاصی ندارد.
  • سؤال: کاربرد out در ()System.out.println چیست؟
    • Out شیئی از کلاس PrintStream و یک عضو داده استاتیک از کلاس System است که تابع ()println را فراخوانی می‌کند.
  • سؤال: Ranodm چگونه در جاوا پیاده‌سازی شده است؟
    • کلاس java.util.Random یک تولیدکننده‌ی java.util.Random خطی (LGG) را پیاده‌سازی می‌کند. فرمول آن چنین است:
      number(i+1) = (a * number(i) + c) mod m

      m ،c و a ثابت هستند و i «بذر» (seed) آغازین انتخابی است.

  • سؤال: regex در جاوا چیست؟
    • یک عبارت منظم یا regex در جاوا الگوی جستجوی رشته‌ها را تعریف می‌کند:
      private static final String EMAIL_PATTERN =
      “^[_A-Za-z0–9-\\+]+(\\.[_A-Za-z0–9-]+)*@”
      + “[A-Za-z0–9-]+(\\.[A-Za-z0–9]+)*(\\.[A-Za-z]{2,})$”;
      Pattern pattern = Pattern.compile(EMAIL_PATTERN);
      Matcher matcher = pattern.matcher(inputString);
      boolean result = matcher.matches();

      کلیدواژه assert برای ایجاد یک assertion  استفاده می‌شود. assertion  یک گزاره است که برنامه‌نویس باور دارد همواره مقدارش در آن نقطه از برنامه True است. این کلیدواژه به منظور کمک به تست کردن و دیباگ کردن استفاده می‌شود.

  • سؤال: تفاوت بین کلاس‌های تو در تو و داخلی چیست؟
    • کلاس‌های داخلی همان کلاس‌های تو در تو هستند که استاتیک نیستند.
  • سؤال: اینترفیس تو در تو چگونه است؟
    • هر اینترفیسی که درون یک کلاس یا یک اینترفیس دیگر اعلان شود یک اینترفیس تو در تو محسوب می‌شود و به صورت پیش‌فرض استاتیک است.

برای مطالعه بخش بعدی این مطلب، لطفاً به لینک زیر مراجعه کنید:

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۰ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
madhupathy
نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *