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

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

در بخش قبلی این سری آموزش مقدماتی جاوا با برخی مفاهیم ابتدایی زبان برنامه‌نویسی جاوا آشنا شدیم. در این نوشته نیز برخی مفاهیم دیگر جاوا توضیح داده شده‌اند.

Immutable

هر کلاسی که حالت شیء آن پس از ایجاد وهله نتواند تغییر یابد به نام Immutable شناخته می‌شود.

  • رشته و همه کلاس‌های پوششی آن و همچنین کلاس enum نمونه‌هایی از کلاس Immutable محسوب می‌شوند.
  • کلاس‌های Immutable از نظر ارث‌بری، thread-safe هستند.

روش thread-safe ساختن یک کلاس به صورت زیر است:

  1. ابتدا مطمئن شوید که کلاس نمی‌تواند override شود. به این منظور کلاس را به صورت final دربیاورید.
  2. همه فیلدهای آن خصوصی باشند.
  3. هیچ متدی وجود نداشته باشد که بتواند حالت شیء را تغییر دهد. از این رو نباید از متدهای setter استفاده کرد.
  4. از یک کپی defensive یا کلون استفاده کنید.

BigDecimal نیز از نظر فنی Immutable محسوب نمی‌شود، زیرا یک کلاس final نیست.

String ،StringBuffer و StringBuilder

«رشته» (String) به صورت Immutable است، چون ما نمی‌توانیم یک شیء رشته را تغییر دهیم. هر بار که مقدار جدیدی به یک رشته انتساب می‌دهید، در واقع یک شیء جدید رشته در پشته ایجاد می‌شود و اشاره‌گر به آن شیء جدید اشاره می‌کند.

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

چرا رشته در جاوا Immutable است؟

دلایل مختلفی برای این حالت وجود دارد که در ادامه به برخی از آن‌ها اشاره می‌کنیم.

  • مفهومی به نام pool رشته وجود دارد: هنگامی که رشته‌ای ایجاد می‌شود، در صورتی که رشته از قبل در pool موجود باشد، به جای ایجاد یک شیء جدید و بازگرداندن ارجاع به آن، یک ارجاع به رشته موجود بازگشت می‌یابد.
  • Hashcode باید کش شود: اگر رشته Immutable نباشد، فرد می‌تواند hashcode آن را تغییر دهد و از این رو دیگر مناسب cash نخواهد بود.
  • امنیت: رشته به طور گسترده‌ای به عنوان یک پارامتر برای بسیاری از کلاس‌های جاوا مانند اتصال شبکه، فایل‌های باز شده و غیره استفاده می‌شود. از این رو اگر Immutable نباشد، ممکن است موجب شود که این نخ‌ها در معرض دستکاری بخش‌های دیگر کد قرار گیرند.

مقایسه رشته

1String a = “abcd”;
2String b = “abcd”;
3System.out.println(a == b); // True
4System.out.println(a.equals(b)); // True
5String c = new String(“abcd”);
6String d = new String(“abcd”);
7System.out.println(c == d); // False
8System.out.println(c.equals(d)); // True

دقت کنید که == به بررسی مکان‌های حافظه می‌پردازد؛ در حالی که ()equals مقدارها را مقایسه می‌کند و استفاده از سازنده باعث ایجاد یک شیء اضافی غیرضروری می‌شود. از این رو در صورتی که می‌خواهید صرفاً یک رشته ایجاد کنید، بهتر است از گیومه‌های جفتی (“”) استفاده کنید.

  • String چند متد به نام‌های ()concat(), trim(), substring و ()replace دارد.
  • StringBuffer به صورت mutable است. همچنین StringBuilder نیز mutable است.
  • StringBuilder همگام سازی شده نیست.
  • متد ()intern مقدار ارجاعی یک رشته، یعنی آدرس آن است.

بنابراین ()S1.intern() == S2.intern تنها در حالتی برقرار خواهد بود که (S1.equals(S2 برقرار باشد.

سریال‌سازی

فرایند ذخیره‌سازی یک شیء از طریق تبدیل کردن آن به صورت یک توالی از بایت‌ها و سپس ساخت مجددش از آن توالی، به نام «سریال‌سازی» شناخته می‌شود. سریال‌سازی از طریق پیاده‌سازی اینترفیس serializable قابل اجرا است. این یک اینترفیس marker است. فیلدهایی که به صورت transient علامت‌گذاری شده‌اند، نمی‌توانند سریال‌سازی شوند.

serialVersionUID یک شماره نسخه اضافه می‌کند تا مطمئن شویم که شیء سریال‌سازی شده زمانی که دوباره سریال‌زدایی (deserialized) می‌شود تغییری نیافته است. سریال‌سازی به صورت داخلی از ()writeObject و ()readObject استفاده می‌کند که می‌توانند override و سفارشی‌سازی شوند.

در مورد Externalization یعنی استفاده از اینترفیس externalizable باید از متدهای ()readExternal و ()writeExternal استفاده کنیم.

Comparator و Comparable

اینترفیس Comparable به خودش امکان مقایسه با شیء دیگر با استفاده از متد ()CompareTo را می‌دهد. اینترفیس Comparator برای مقایسه دو شیء مختلف استفاده می‌شود و در آن از متد ()compare استفاده می‌شود.

ساختار متد چنین است:

1CompareTo(Object obj)
2Compare(Object obj1، Object obj2)

Comparator کنترل بیشتری فراهم می‌سازد.

(Collections.sort(list برای Comparable و (()Collections.sort(list، new comparatorObject برای comparator استفاده می‌شوند.

Comparable با استفاده از یک کلاس پیاده‌سازی می‌شود تا شیئی بتواند خودش را با اشیای دیگر مقایسه کند.

1class HDTV implements Comparable<HDTV> {
2private int size;
3private String brand;
4public HDTV(int size, String brand) {
5this.size = size;
6this.brand = brand;
7}
8// .. getters & setters
9@Override
10public int compareTo(HDTV tv) {
11if (this.getSize() > tv.getSize())
12return 1;
13else if (this.getSize() < tv.getSize())
14return -1;
15else
16return 0;
17}}
18public class Main {
19public static void main(String[] args) {
20HDTV tv1 = new HDTV(55, “Samsung”);
21HDTV tv2 = new HDTV(60, “Sony”);
22if (tv1.compareTo(tv2) > 0) {
23System.out.println(tv1.getBrand() + “ is better.);
24} else {
25System.out.println(tv2.getBrand() + “ is better.);
26}
27}}

در برخی موقعیت‌ها ممکن است نخواهیم یک کلاس را تغییر دهیم و آن را comparable بکنیم.

1class SizeComparator implements Comparator<HDTV> {
2@Override
3public int compare(HDTV tv1, HDTV tv2) {
4int tv1Size = tv1.getSize();
5int tv2Size = tv2.getSize();
6if (tv1Size > tv2Size) {
7return 1;
8} else if (tv1Size < tv2Size) {
9return -1;
10} else {
11return 0;
12}
13}}
14public class Main {
15public static void main(String[] args) {
16HDTV tv1 = new HDTV(55, “Samsung”);
17HDTV tv2 = new HDTV(60, “Sony”);
18HDTV tv3 = new HDTV(42, “Panasonic”);
19ArrayList<HDTV> al = new ArrayList<HDTV>();
20al.add(tv1);
21al.add(tv2);
22al.add(tv3);
23Collections.sort(al, new SizeComparator());
24for (HDTV a : al) {
25System.out.println(a.getBrand());
26}
27}}

Collection

کلکسیون یا Collection یک ساختمان داده است که در آن می‌توان اشیا را ذخیره‌سازی کرد و چرخه‌هایی روی آن‌ها تعریف کرد. ساختمان داده خود یک موضوع گسترده است و قصد نداریم در این نوشته به بررسی آن بپردازیم. به این منظور می‌توانید از راهنمای «آموزش ساختمان داده — مجموعه مقالات جامع وبلاگ فرادرس» کمک بگیرید.

در این بخش ایده‌های ابتدایی ساختمان‌های داده را در کتابخانه collection جاوا بررسی می‌کنیم.

Collections

  • Collection یک اینترفیس است که به وسیله آن می‌توان مجموعه، لیست و صف (queue) را بسط داد.
  • کلاس collections متدهای کاربردی استاتیک دارد که روی مجموعه‌ها استفاده می‌شود.
  • آرایه‌ها در مقایسه با ArrayList یا Vector سریع‌تر هستند و در صورتی که اندازه آرایه را از پیش بدانیم استفاده از آن‌ها ترجیح دارد. با این حال آرایه برخلاف لیست نمی‌تواند افزایش داشته باشد.
  • ArrayList و Vector ساختمان‌های داده خاصی هستند که به صورت درونی از Array و برخی متدهای دیگر مانند ()add()، remove و غیره استفاده می‌کنند تا امکان افزایش و کاهش اندازه را ارائه کنند.
  • ArrayList از جستجوی مبتنی بر اندیس به وسیله متدهای ()indexOf و ()lastIndexOf پشتیبانی می‌کند.
  • Vector همگام‌سازی (synchronization) شده و از این رو threadsafe است؛ اما بهتر است از ArrayList به همراه کد زیر برای همگام‌سازی استفاده کنیم:
1List mylist = Collections.synchronizedList(mylist);
2// Single lock for the entire list
  • اینترفیس Iterator برای تعریف چرخه روی collection صرفاً در جهت رو به جلو استفاده می‌شود.
  • ListIterator به بسط Iterator پرداخته و امکان پیمایش در هر دو جهت را فراهم می‌کند.

کلاس Collection به صورت fail first است، یعنی اگر یک نخ، مقداری را در حالی که نخ دیگر مشغول پیمایش collection است تغییر دهد، موجب بروز یک خطا به صورت ConcurentModificationException می‌شود. حتی اگر از SynchronizedList/SynchronizedMap استفاده کنیم، نیز این خطا همچنان پدید می‌آید، زیرا این ساختارها به صورت مشروط threadsafe هستند، یعنی عملگرهای منفرد آن threadsafe هستند؛ اما کل عملیات threadsafe نیست. بنابراین یا باید از Synchronize یا از ConcurentHashMap و یا CopyOnWriteArrayList استفاده کنیم. ConcurentHaspMap، CopyOnWriteArrayList و CopyOnWriteArraySet به صورت threadsafe یا synchronized هستند.

HashMap

HashMap بر مبنای مفهوم «هش کردن» (Hashing) عمل می‌کند.

  • هش کردن در ساده‌ترین شکل خود روشی برای انتساب یک کد یکتا به هر متغیر/شیء پس از به‌کارگیری هر فرمول/الگوریتم روی مشخصات آن است.
  • HashMap یک «مدخل» (Entry) کلاس درونی دارد که برای ذخیره‌سازی نگاشت کلید و مقدار استفاده می‌شود.

مقدار hash با استفاده از کد هش کلید و با فراخوانی متد ()hashCode آن محاسبه می‌شود. مقدار هش برای محاسبه اندیس در آرایه جهت ذخیره‌سازی شیء Entry استفاده می‌شود. طراحان JDK به درستی فرض کرده‌اند که ممکن است تابع‌های ()hashCode ضعیفی نوشته شده باشند که مقدار کد هش بسیار بالا یا پایینی را بازگشت دهند. از این رو تابع ()hash دیگری نیز وجود دارد و اگر کد هش شیء را به این تابع ()hash ارسال کنید، مقدار هش در بازه اندازه اندیس آرایه بازگشت می‌یابد.

MapReduce

MapReduce دو مؤلفه کلیدی دارد که شامل Map و Reduce است. Map تابعی است که روی یک مجموعه از مقادیر ورودی اعمال می‌شود و مجموعه‌ای از جفت‌های کلید/مقدار را محاسبه می‌کند. Reduce تابعی است که این نتایج را می‌گیرد و تابع دیگری را روی نتیجه تابع map اعمال می‌کند.

استفاده از Collections.EMPTY_LIST/EMPTY_SET به جای null یک رویه مناسب محسوب می‌شود.

1List testList = Collections. EMPTY_LIST;

بهترین رویه استفاده از شیء immutable به عنوان کلید برای Map است.

(Arrays.asList(T… a یک مقدار java.util.Arrays.ArrayList بازگشت می‌دهد و نه یک مقدار java.util.ArrayList. در واقع این فقط یک view از آرایه اصلی است.

مقایسه کردن ArrayList

1Collection<String> listOne = new ArrayList(Arrays.asList(“a”,”b”, “c”,”g”));
2Collection<String> listTwo = new ArrayList(Arrays.asList(“a”,”b”,”d”, “e”));
3List<String> sourceList = new ArrayList<String>(listOne);
4List<String> destinationList = new ArrayList<String>(listTwo);
5sourceList.removeAll( listTwo ); // Result: [c, g]
6destinationList.removeAll( listOne ); // Result: [d, e]

تفاوت بین اینترفیس ITERATOR و ENUMERATION

ITERATOR در واقع یک متد اضافه می‌کند که Enumeration ندارد و آن ()remove است. ITERATOR با استفاده از مفاهیم خوش‌تعریف، به فراخوانی کننده امکان حذف عناصر از collection زیرین را در طی تکرارها می‌دهد.

چگونه می‌توانیم ترتیب TreeMap را معکوس کنیم؟

بدین منظور باید از ()Collections.reverseOrder استفاده کنیم.

1Map tree = new TreeMap(Collections.reverseOrder());

آیا می‌توان عناصر ناهمگون را به TreeMap اضافه کرد؟

کلکسیون‌های مرتب‌سازی شده امکان افزودن عناصر ناهمگون را نمی‌دهند، چون comparable نیستند. اما در صورتی که کلاس اینترفیس comparable را پیاده‌سازی کرده باشد، امکان چنین کاری وجود دارد.

تفاوت بین int[] x و []int x چیست؟

هیچ تفاوتی وجود ندارد. هر دو مورد روش‌های مناسبی برای اعلان آرایه هستند.

سلسله مراتب سربار حافظه به صورت زیر است:

ArrayList < LinkedList < HashTable < HashMap < HashSet

متد ()Contains از جستجوی خطی استفاده می‌کند.

HashMap امکان داشتن یک کلید تهی و چندین مقدار تهی را می‌دهد، HashTable امکان داشتن هیچ کلید یا مقدار تهی را نمی‌دهد.

LinkedHashMap می‌تواند برای جلوگیری از تصادم برای LRU Cache نیز استفاده شود. دلیل بهتر بودن ConcurentHashMap نسبت به HashTable این است که HashMap را می‌توان با استفاده از کلید و همچنین بر اساس مقدار مرتب‌سازی کرد.

Guava

Guava به کتابخانه‌های اصلی گوگل برای جاوا گفته می‌شود که کلاس‌ها و اینترفیس‌های اضافی دیگری برای collection دارد.

  1. MultiSet و UniqueList اضافه شده‌اند. لیست مرتب است و امکان داشتن موارد تکراری وجود دارد؛ اما UniqueList با این که مرتب است، اما موارد تکراری مجاز نیستند. در Set نیز امکان داشتن موارد تکراری مجاز نیست و همچنین مرتب هم نیست؛ در حالی که MultiSet مرتب نیست و موارد تکراری مجاز هستند.
  2. ImmutableList، ImmutableSet، ImmutableSortedSet، ImmutableMap کلاس‌های اضافه شده هستند.
  3. کلاس‌های ImmutableMultiSet، HashMultiSet، LinkedHashMultiSet، TreeMultiSet، EnumMultiSet و MultiMap نیز اضافه شده‌اند.
  4. در کلاس MultiMap ما رابطه‌های چند به چندِ کلید به مقدار را داریم در حالی که در جاوا این رابطه به صورت یک به چند است.

نسخه‌های جاوا

اینک که درک مناسبی از مفاهیم اساسی جاوا کسب کردیم، به طور اجمالی به بررسی تاریخچه و تکامل نسخه‌های مختلف جاوا می‌پردازیم. در فهرست زیر نسخه‌های مختلف جاوا و امکاناتی که هر کدام ارائه کرده‌اند را معرفی کرده‌ایم.

  • نسخه 1.4: سیستم‌های I/O، بهبود عملکرد و مقیاس‌پذیری اضافه شدند.
  • نسخه 1.5: ساختمان‌های داده ژنریک یعنی collections، Iterator و غیره اضافه شدند، حلقه‌ها بهبود یافتند، autoboxing، unboxing، annotations، enums و ایمپورت‌های استاتیک نیز اضافه شدند.
  • نسخه 1.6: پشتیبانی کامل از ویندوز ویستا، افزایش سرعت، بهبودهای در سمت کامپایلر و پردازش xml وب‌سرویس‌ها اضافه شد.
  • نسخه 1.7: امکان استفاده از رشته در گزاره‌های سوئیچ، استثناهای چندگانه در یک بلوک catch و موارد دیگر اضافه شدند.
  • نسخه 1.8: موارد زیر در این نسخه اضافه شده‌اند.

عبارت‌های لامبدا: منظور از عبارت‌های لامبدا متدهایی هستند که اعلان نمی‌شوند یعنی modifier دسترسی، اعلان مقدار بازگشتی و نام ندارند. بدین ترتیب در زمان مورد نیاز برای اعلان و نوشتن متد جداگانه برای گنجاندن کلاس صرفه‌جویی می‌شود.

این عبارت‌ها این امکان را در اختیار ما قرار داده‌اند که با کارکردهای مختلف به صورت یک آرگومان متد رفتار کنیم و یا به عبارت دیگر با کد مانند داده رفتار کنیم.

1MathOperation addition = (int a, int b) -> a + b;
2addition(a,b);

API های تاریخ/زمان: زمان جاری به وسیله کلاس clock نمایش می‌یابد. این کلاس مجرد است و از این رو نمی‌توان وهله‌هایی از آن ساخت. متد استاتیک زمان جاری را بازگشت خواهد داد.

1import javax.time.Clock;
2Clock clock = Clock.systemUTC();
3Clock clock = Clock.systemDefaultZone();
4ZoneId zone = ZoneId.of(“Europe/Berlin”); 
5// ZoneId zone = ZoneId.systemDefault(); you can also use this
6Clock clock = Clock.system(zone);
7import javax.time.LocalDate;
8LocalDate date = LocalDate.now();

Stream: منظور از stream یک شیء یکبار مصرف است. زمانی که این شیء پیمایش شد دیگر نمی‌توان مجدداً آن را پیمایش کرد. stream-ها را می‌توان در هنگام پیمایش کردن، به طور همزمان filter، map یا reduce کرد. در مثال زیر روش استفاده از یک stream متوالی را می‌بینید:

1List <Person> people = list.getStream.collect(Collectors.toList());
2Using a parallel stream:
3List <Person> people = list.getStream.parallel().collect(Collectors.toList());

برای مطالعه بخش سوم این مطلب روی لینک زیر کلیک کنید:

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

==

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

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