در این مقاله ی آموزشی، به بررسی کار با آبجکت های String در جاوا می پردازیم و با ایجاد یک اپلیکیشن جاوا، نکاتی را متذکر می شویم. این یک مقاله ی جامع و کامل، درباره ی آبجکت String در جاوا است و سعی کرده ایم با زبانی ساده، نکات آن را بیان کنیم.
تعریف و مقایسه ی آبجکت های String
در فصل 1 آموختید که، به یک دنباله از کاراکترها که در داخل یک جفت دابل کوتیشن قرار دارند، رشته(ی لیترال) گفته می شود. شما از رشته های(لیترال) زیادی مانند "First Java application"
استفاده کرده اید. و مقادیری را در آبجکت های String انتساب داده اید و از آنها در داخل متدهایی مثل println()
و showMessageDialog()
استفاده کرده اید.
یک رشته ی لیترال، درواقع یک آبجکت بدون نام یا یک آبجکت ناشناس از کلاس Stirng است و یک متغیر از نوع String درواقع یک آبجکت دارای نام، از همان کلاس است. کلاس String در java.lang.String قرار دارد که به طور اتوماتیک در هر برنامه ای که می نویسیم، اضافه(import) می شود.
نکته: ما در هر عنوان متد main()
که می نویسیم، یک آرایه ی String به نام args را تعریف می کنیم. در فصل بعد، نکات بیشتری را در مورد آرایه ها خواهید آموخت.
قرارداد: وقتی که می نویسیم: یک آبجکت String، منظور، یک آبجکت از کلاس String است. زیرا String یک کلاس در جاوا است و می توانیم از آن، اشیاء یا آبجکت های زیادی ایجاد کنیم.
وقتی که یک آبجکت String تعریف می کنیم، خودِ رشته، یعنی سری کاراکترهایی که در داخل آبجکت String قرار گرفته اند، از شناسه ای که از آن برای رجوع به این کاراکترها استفاده می شود، متفاوت است. ما می توانیم یک آبجکت String با استفاده از کلمه ی کلیدی new و کانستراکتور String ایجاد کنیم؛ همانطور که می توانیم یک آبجکت(شیء) از هر نوع دیگری را ایجاد کنیم. بعنوان مثال، در دستور زیر، یک آبجکت(شیء) به نام aGreeting تعریف کرده ایم و آن را از نوع String قرار داده ایم و مقدار اولیه ی "Hello"
را در String قرار داده ایم:
String aGreeting = new String("Hello");
متغیر aGreeting یک ارجاع به آبجکت String را ذخیره می کند. این متغیر، مکانی که آبجکت String در حافظه(memory) ذخیره شده است را ثبت می کند. وقتی که ما متغیر aGreeting را تعریف و مقداردهی اولیه(initialize) می کنیم، این متغیر به مقدار String ای که مقداردهی اولیه شده است، پیوند داده می شود.
چونکه رشته ها در برنامه ها خیلی ساده تعریف می شوند، جاوا یک میانبر ارائه داده است و ما می توانیم با استفاده از دستور زیر، یک آبجکت String تعریف کنیم که حاوی عبارت "Hello" است و در آن کلمه ی کلیدی new از قلم انداخته شده است و به طور صریح کانستراکتور(constructor) این کلاس فراخوانی نشده است:
String aGreeting = "Hello";
نکته: اگر دو آبجکت String را تعریف کنیم و برای هردو آبجکت، یک مقدار مشابه را مقداردهی اولیه(initialize) کنیم، این مقدار تنها یک بار در حافظه ذخیره می شود و مرجع های(references) این دو آبجکت، آدرس مشابهی را در حافظه نگهداری می کنند. چونکه رشته ی کاراکتر تنها یک بار ذخیره شده است، در استفاده از حافظه صرفه جویی می شود.
مقایسه ی مقادیر رشته ها در جاوا
در زبان برنامه نویسی جاوا، String یک کلاس است و هر String ای که ایجاد شود، یک آبجکت(شیء) از این کلاس محسوب می شود. نام یک متغیر String یک مرجع(reference) است؛ یعنی نام یک متغیر String به جای اینکه به یک مقدار خاصی اشاره کند، به یک موقعیت در حافظه اشاره می کند؛
اما وقتی که ما یک متغیر از یک نوعِ اصلی و اولیه مانند int x = 10;
تعریف می کنیم، آدرسی در حافظه که x در آن قرار دارد، مقدار 10 را نگهداری می کند. اگر ما بعداً یک مقدار جدید را در x انتساب دهیم، این مقدار جدید در آدرس حافظه ی انتساب داده شده، جایگزین مقدار قدیمی می شود. بعنوان مثال، اگر بنویسیم x = 45;
آنگاه 45 جایگزین 10 در آدرس x می شود.
برعکس، وقتی که ما یک String مانند String aGreeting = "Hello";
تعریف می کنیم، متغیر aGreeting کاراکترهای "Hello"
را نگهداری نمی کند. بلکه به جای آن، یک آدرس از حافظه که این کاراکترها در آن ذخیره شده اند را نگهداری می کند.
سمت چپ از تصویر 7.5 یک نمودار از حافظه ی رایانه را نشان می دهد؛ اگر aGreeting در آدرسِ حافظه 10876 ذخیره شود و رشته ی(String) "Hello"
در آدرس حافظه ی 26040 ذخیره شود، وقتی که ما به aGreeting رجوع می کنیم، در حقیقت به آدرس این کاراکترهایی که می خواهیم از آن استفاده کنیم، دسترسی پیدا می کنیم. در مثال درون تصویر 7.5، موقعیت حافظه که در ابتدا در آدرس 32564 قرار دارد، هنوز مورد استفاده قرار نگرفته است و حاوی یک مقدار زباله می باشد.
نکته: ما نمی توانیم آدرسی در حافظه که یک مقدار در آن ذخیره شده است را انتخاب کنیم. آدرس هایی از قبیل 10876 توسط سیستم عامل انتخاب می شوند.
اگر ما یک مقدار جدید مثل aGreeting = "Bonjour";
را به متغیر aGreeting انتساب دهیم، آدرسی که در متغیر aGreeting نگهداری می شد، تغییر می کند. اکنون aGreeting یک آدرس جدید را در خود نگهداری می کند که کاراکترهای "Bonjour" در آن ذخیره شده اند.
همان طور که در سمت راست تصویر 7.5 نشان داده شده است، عبارت “Bonjour” کاملاً یک آبجکت جدید است که در مکان خودش ایجاد شده است. رشته ی "Hello" هنوز در حافظه(memory) قرار دارد، اما aGreeting دیگر آدرس آن را در خود نگهداری نمی کند.
در نهایت، یک بخش از سیستم جاوا، به نام زباله روب(garbage collector) کاراکترهای “Hello” را دور می اندازد. بنابراین، درواقع رشته ها هرگز تغییر نمی کنند؛ بلکه به جای آن، رشته های جدیدی ایجاد می شوند و مرجع های رشته ها(String references)، آدرس های جدید را نگهداری می کنند. رشته ها و دیگر آبجکت هایی که نمی توانند تغییر کنند، تغییرناپذیر(immutable) هستند.
(تصویر 7.5 : تعریف محتواهای متغیر aGreeting و پس از یک انتساب)
توضیح شماره های درون تصویر بالا:
شماره 1: آدرس 10876 که به صورت aGreeting نام گذاری شده است.
شماره 2: aGreeting آدرسی که "Hello" در آن ذخیره شده است را نگهداری می کند.
شماره 3: آدرس 10876 که به صورت aGreeting نام گذاری شده است.
شماره 4: aGreeting آدرسی که "Bonjour" در آن ذخیره شده است را نگهداری می کند.
چونکه مرجع های رشته، آدرس های حافظه را نگهداری می کنند، اگر مقایسه های ساده ای را بین آنها انجام دهیم، اغلب نتایج گمراه کننده ای را به دست می آوریم. بعنوان مثال، در تصویر 7.1 اپلیکیشن TryToCompareStrings را دوباره فراخوانی کنید. در این مثال، جاوا متغیرهای رشته ای aName و anotherName را نابرابر ارزیابی می کند؛ زیرا اگرچه این متغیرها حاوی سری کاراکترهای مشابهی هستند، اما یکی از آنها مستقیماً انتساب داده شده است و دیگری از کیبورد دریافت شده است و در یک ناحیه ی مختلف در حافظه ذخیره شده است.
وقتی که ما رشته ها را با عملگر == مقایسه کنیم، درواقع داریم آدرس های حافظه ی آنها را با یکدیگر مقایسه می کنیم، نه مقدار آنها را. علاوه بر این، وقتی که ما سعی کنیم رشته ها را با استفاده از علامت کوچکتری(<
) یا بزرگتری(>
) مقایسه کنیم، برنامه حتی کامپایل هم نخواهد شد.
خوشبختانه، کلاس String تعدادی متد مفید به ما ارائه می دهد. متد equals()
که به کلاس String تعلق دارد، محتوای دو آبجکت String را ارزیابی می کند، تا مشخص شود که آیا آنها برابر(equivalent) هستند یا خیر. اگر این آبجکت ها محتوای یکسانی داشته باشند، متد مذکور مقدار true را برمی گرداند. بعنوان مثال، تصویر 7.6 یک اپلیکیشن CompareStrings را نشان می دهد، که با اپلیکیشن TryToCompareStrings در تصویر 7.1 یکسان است؛ به جز مقایسه ای که پس زمینه ی آن را با رنگ آبی نشان داده ایم.
(تصویر 7.6 : اپلیکیشن CompareStrings)
وقتی که یک کاربر اپلیکیشن CompareStrings را اجرا می کند و برای نام مورد نظر، مقدار "Carmen"
را وارد می کند، خروجی آن مانند تصویر 7.7 نشان داده می شود؛ که نشان می دهد محتوای رشته ها برابر هستند. متد equals()
که به کلاس String تعلق دارد، تنها اگر محتوای دو رشته(String) با یکدیگر برابر باشند، مقدار true را برمی گرداند. بنابراین یک رشته که به صورت "Carmen "
(با یک فاصله ی سفید پس از n) باشد، با یک رشته که به صورت "Carmen"
( بدون یک فاصله سفید پس از n) باشد، برابر نخواهد بود.
(تصویر 7.7 : خروجی اپلیکیشن CompareStrings)
نکته: از نظر فنی، متد equals()
یک مقایسه ی الفبایی با رشته ها(Strings) انجام نمی دهد. بلکه یک مقایسه ی وابسته به لغت نویسی(lexicographical) را انجام می دهد. یعنی یک مقایسه بر اساس مقدار یونیکد(Unicode) کاراکترها را انجام می دهد.
تمام رشته هایی که در تصویر 7.6 تعریف شده اند(aName و anotherName) یک آبجکت(شیء) از نوع String (رشته) هستند، بنابراین هریک از این رشته ها به متد equals()
، در کلاس String دسترسی دارند. اگر بررسی کنید که متد equals()
چگونه در اپلیکیشن نشان داده شده در تصویر 7.6 مورد استفاده قرار گرفته است، می توانید کمی در مورد نحوه نگارش این متد توسط سازندگان جاوا اطلاع پیدا کنید:
- چونکه ما از متد
equals()
با یک آبجکت String استفاده می کنیم و این متد از محتوای یکتای آن آبجکت برای انجام یک مقایسه استفاده می کند، می توانیم بگوییم که این متد استاتیک نیست. - چونکه می توانیم متد
equals()
در یک دستور if به کار ببریم، می توانیم بگوییم که این متد یک مقدار بولین را برمی گرداند. - چونکه در فراخوانی این متد، بین پرانتزهای آن یک String قرار می دهیم، متوجه می شویم که متد
equals()
یک آرگومان رشته ای(String argument) را دریافت می کند.
بنابراین، عنوان متد equals()
که در داخل کلاس String قرار دارد، باید مشابه با عبارت زیر باشد:
public boolean equals(String s)
تنها چیزی که در مورد متد بالا نمی دانید، نام محلی به کار رفته برای آرگومان، یعنی s است. این نام می تواند s یا هر شناسه ی قانونی دیگری در جاوا باشد.
وقتی که ما از یک متد از پیش نوشته شده مانند equals()
استفاده می کنیم، نمی دانیم کدهای درون آن چگونه هستند. بعنوان مثال ما نمی دانیم آیا متد equals()
کاراکترهای درون رشته ها(Strings) را از چپ به راست مقایسه می کند، یا از راست به چپ. تنها چیزی که می دانیم، این است که در این متد، اگر دو رشته کاملاً با یکدیگر برابر باشند، مقدار true برگردانده می شود و اگر برابر نباشند، مقدار false برگردانده می شود.
در اپلیکیشن تصویر 7.6 چونکه هردوی aName و anotherName رشته(String) هستند، آبجکت aName می تواند با دستور aName.equals(anotherName)
متد equals()
را فراخوانی کند. یا اینکه آبجکت anotherName می تواند با دستور anotherName.equals(aName)
متد equals()
را فراخوانی کند.
متد equals() هم می تواند در آرگومان خود، یک آبجکت String را بعنوان متغیر بپذیرد، یا می تواند یک رشته ی لیترال(یعنی یک رشته ی ساده) را بپذیرد. متد equalsIgnoreCase()
که به کلاس String تعلق دارد، مشابه با متد equals()
است. همان طور نام این متد نشان می دهد، این متد وقتی که داریم بررسی می کنیم که آیا دو رشته با یکدیگر برابر هستند یا نه، حساسیت به حروف بزرگ و کوچک را نادیده می گیرد. بنابراین، اگر یک رشته را به صورت aName = "Carmen";
تعریف کنیم، آنگاه دستور aName.equals("caRMen")
برابر با false خواهد بود، اما ارزش دستور aName.equalsIgnoreCase("caRMen")
برابر با true خواهد بود.
از این متد وقتی استفاده می شود که کاربر پاسخی را در ورودی وارد می کند و ما آن را در برنامه دریافت می کنیم. ما نمی توانیم پیش بینی کنیم که یک کاربر، وقتی که دارد داده ای را وارد می کند، از کلید Shift یا Caps Lock برای نوشتن حروف بزرگ استفاده کرده است یا نه.
وقتی که از متد compareTo()
برای مقایسه ی دو رشته(String) استفاده کنیم، این متد در قالب یک مقدار صحیح(integer) اطلاعات اضافه ای را در اختیار کاربر قرار می دهد.
وقتی که ما از متد compareTo()
برای مقایسه ی دو آبجت رشته(String) استفاده کنیم، این متد تنها اگر دو رشته ی مورد نظر به یک مقدار مشابه اشاره کنند، 0 را برمی گرداند.
اگر بین این دو رشته تفاوتی وجود داشته باشد، و اگر آبجکتی که داریم فراخوانی می کنیم از آرگومان متد compareTo()
کمتر باشد، یک عدد منفی برگردانده می شود؛ و اگر آبجکتی که داریم فراخوانی می کنیم، بیشتر از آرگومان متد مذکور باشد، یک عدد مثبت برگردانده می شود. رشته ها، بسته به مقادیر یونیکد(Unicode values) خود، به صورت کمتر یا بیشتر در نظر گرفته می شوند.
بنابراین، "a" کمتر از "b" و "b" کمتر از "c" است. بعنوان مثال، اگر aName برابر با "Roger" باشد، آنگاه دستور aName.compareTo("Robert");
عدد 5 را برمی گرداند. این عدد، مثبت است؛ و نشان می دهد که "Roger" بیشتر از "Robert" است. این عدد، به این معنی نیست که "Roger" کاراکترهای بیشتری نسبت به "Robert" دارد؛ بلکه به این معنی است که "Roger" بر اساس حروف الفبا "بیشتر" از "Robert" است. این مقایسه به صورت زیر حاصل می شود:
- حرف R در "Roger" و حرف R در "Robert" مورد مقایسه قرار می گیرند و معلوم می شود که با یکدیگر برابر هستند.
- حرف o در "Roger" و حرف o در "Robert" مورد مقایسه قرار می گیرند و معلوم می شود که برابر هستند.
- حرف g در "Roger" و حرف b در "Robert" مقایسه می شوند و با یکدیگر متفاوت هستند. مقدار عددی g منهای مقدار عددی b برابر با 5 می شود(زیرا g در الفبا، به مقدار 5 حرف، پس از b قرار دارد). بنابراین متد
compareTo()
مقدار 5 را برمی گرداند.
اغلب برای ما مهم نیست که مقدار برگردانده شده توسط متد compareTo()
چیست؛ و فقط می خواهیم مشخص کنیم که این مقدار مثبت است یا منفی.
بعنوان مثال، می توانیم به صورت زیر، یک تست بزنیم تا مشخص شود که آیا aWord از نظر الفبایی کمتر از anotherWord است یا خیر:
if(aWord.compareTo(anotherWord) < 0)...
اگر aWord یک متغیر رشته ای باشد که به مقدار "hamster" اشاره می کند و anotherWord نیز یک متغیر رشته ای باشد که به مقدار "iguana" اشاره کند، مقایسه ی زیر مقدار true را برمی گرداند:
if(aWord.compareTo(anotherWord) < 0)
دو عبات صحیح و یک عبارت اشتباه
موضوع: تعریف و مقایسه ی آبجکت های String
- برای ایجاد یک آبجکت String باید از کلمه ی کلیدی new استفاده کنیم و به صراحت، کانستراکتور این کلاس را فراخوانی کنیم.
- وقتی که ما رشته ها را با عملگر == مقایسه می کنیم، درواقع آدرس های حافظه ی آنها را مقایسه می کنیم؛ نه مقادیر آنها را.
- وقتی که ما رشته ها را با متد
equals()
مقایسه می کنیم، درواقع مقادیر آنها را با یکدیگر مقایسه می کنیم، نه آدرس های حافظه را.
پاسخ:
عبارت شماره 1 اشتباه است. ما می توانیم با، یا بدون استفاده از کلمه ی کلیدی new یک رشته را ایجاد کنیم؛ بدون اینکه به صراحت، کانستراکتور رشته را مشخص کنیم.