برنامه نویسی ۸۰۰ بازدید

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

نخ (Thread)

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

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

روش پیاده‌سازی اینترفیس 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 استفاده کنیم.

public class Semaphore {
int value;
// No of users that can use Resource
public Semaphore(int init) {
if (init < 0) {
init = 0;
}
value = init;
}
// Acquiring Resource
public synchronized void down() {
while (value == 0) {
try {
wait();
} catch (InterruptedException e) {
}
value —;
}
}
// Releasing Resource
public synchronized void up() {
value++;
notify();
}
}

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

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

مدیریت خطا

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

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

public class MyException extends Exception {
private String errorCode = “Unkown_Exception”;
public MyException(String message, String errorCode){
super(message);
this.errorCode = errorCode;
}
Public String getErrorCode(){
Return this.errorCode;
}
}

کلیدواژه 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 نیز اطلاعات جدول را از قبیل تعداد کل ستون‌ها، نام ستون، نوع ستون و موارد دیگر بازگشت می‌دهد.

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

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

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

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

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

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

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

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

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

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

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

File fout = new File(file_location_string);
FileOutputStream fos = new FileOutputStream(fout);
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(fos));
out.write(“something”);
Using FileWriter:
FileWriter fstream = new FileWriter(file_location_string);
BufferedWriter out = new BufferedWriter(fstream);
out.write(“something”);

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

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

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

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

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

public class Singleton {
private static Singleton instance = new Singleton();
private singleton() {}
public static Singleton getInstance(){
return instance;
}
}

الگوی Factory

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

interface Dog {
public void speak ();
}
class Poodle implements Dog {
public void speak() {
System.out.println(“The poodle says \”arf\””);
}
}
class Rottweiler implements Dog {
public void speak() {
System.out.println(“The Rottweiler says (in a very deep voice) \”WOOF!\””);
}
}
class DogFactory {
public static Dog getDog(String criteria) {
if ( criteria.equals(“small”) )
return new Poodle();
else if ( criteria.equals(“big”) )
return new Rottweiler();
return null;
}
}
public class JavaFactoryPatternExample {
public static void main(String[] args) {
Dog dog = DogFactory.getDog(“small”);
dog.speak();
dog = DogFactory.getDog(“big”);
dog.speak();
}
}

الگوی Adaptor

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

public interface SocketAdapter {
public Volt get120Volt();
public Volt get12Volt();
public Volt get3Volt();
}

الگوی Builder

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

چند نکته مهم

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

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

public 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 است. این کلیدواژه به منظور کمک به تست کردن و دیباگ کردن استفاده می‌شود.

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

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

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

==

بر اساس رای ۱ نفر
آیا این مطلب برای شما مفید بود؟
شما قبلا رای داده‌اید!
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.

«میثم لطفی» در رشته‌های ریاضیات کاربردی و مهندسی کامپیوتر به تحصیل پرداخته و شیفته فناوری است. وی در حال حاضر علاوه بر پیگیری علاقه‌مندی‌هایش در رشته‌های برنامه‌نویسی، کپی‌رایتینگ و محتوای چندرسانه‌ای، در زمینه نگارش مقالاتی با محوریت نرم‌افزار با مجله فرادرس همکاری دارد.