سلام! بیایید در مورد نوع دیگری از کلاس های تودرتو صحبت کنیم. من در مورد کلاس های محلی (روش-کلاس های داخلی محلی) صحبت می کنم. قبل از غواصی، ابتدا باید جایگاه آنها را در ساختار طبقات تودرتو به خاطر بسپاریم.
از نمودار ما، میتوان دید که کلاسهای محلی زیرگونهای از طبقات داخلی هستند که در مطالب قبلی
به تفصیل درباره آن صحبت کردیم . با این حال، کلاس های محلی تعدادی ویژگی مهم و تفاوت با کلاس های داخلی معمولی دارند. نکته اصلی در اعلان آنها است: یک کلاس محلی فقط در یک بلوک کد اعلام می شود. اغلب، این اعلان در داخل برخی از متدهای کلاس خارجی است. به عنوان مثال، ممکن است به شکل زیر باشد:

public class PhoneNumberValidator {
public void validatePhoneNumber(String number) {
class PhoneNumber {
private String phoneNumber;
public PhoneNumber() {
this.phoneNumber = number;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
// ...number validation code
}
}
مهم!اگر جاوا 7 را نصب کرده باشید، این کد وقتی در IDEA جایگذاری شود، کامپایل نمی شود. در پایان درس در مورد دلایل این موضوع صحبت خواهیم کرد. به طور خلاصه، نحوه کار کلاس های محلی به شدت به نسخه زبان بستگی دارد. اگر این کد برای شما کامپایل نمیشود، میتوانید نسخه زبان IDEA را به جاوا 8 تغییر دهید یا کلمه را final
به پارامتر متد اضافه کنید تا به این صورت باشد: validatePhoneNumber(final String number)
. پس از آن، همه چیز کار خواهد کرد. این یک برنامه کوچک است که شماره تلفن را تأیید می کند. روش آن validatePhoneNumber()
یک رشته را به عنوان ورودی می گیرد و تعیین می کند که آیا شماره تلفن است یا خیر. و در داخل این متد PhoneNumber
کلاس محلی خود را اعلام کردیم. ممکن است منطقی بپرسید که چرا. چرا دقیقاً یک کلاس را در یک متد اعلام می کنیم؟ چرا از یک کلاس داخلی معمولی استفاده نمی کنید؟ درست است، ما می توانستیم PhoneNumber
کلاس را یک کلاس درونی کنیم. اما راه حل نهایی به ساختار و هدف برنامه شما بستگی دارد. بیایید مثال خود را از یک درس در مورد کلاس های داخلی به یاد بیاوریم:
public class Bicycle {
private String model;
private int maxWeight;
public Bicycle(String model, int maxWeight) {
this.model = model;
this.maxWeight = maxWeight;
}
public void start() {
System.out.println("Let's go!");
}
public class HandleBar {
public void right() {
System.out.println("Steer right!");
}
public void left() {
System.out.println("Steer left!");
}
}
}
HandleBar
در آن، ما یک کلاس داخلی از دوچرخه ساختیم . تفاوت در چیست؟ اول از همه، نحوه استفاده از کلاس متفاوت است. کلاس HandleBar
در مثال دوم موجودیت پیچیده تری نسبت به PhoneNumber
کلاس در مثال اول است. اول، HandleBar
دارای متدهای عمومی right
و left
(اینها تنظیم کننده/گیرنده نیستند). دوم اینکه نمیتوان از قبل پیشبینی کرد که کجا ممکن است به آن و Bicycle
طبقه بیرونی آن نیاز داشته باشیم. حتی در یک برنامه ممکن است ده ها مکان و روش مختلف وجود داشته باشد. اما با PhoneNumber
کلاس، همه چیز بسیار ساده تر است. برنامه ما بسیار ساده است. این فقط یک هدف دارد: بررسی اینکه آیا یک شماره یک شماره تلفن معتبر است یا خیر. در بیشتر موارد، ما PhoneNumberValidator
حتی یک برنامه مستقل نیست، بلکه بخشی از منطق مجوز برای یک برنامه بزرگتر است. به عنوان مثال، وبسایتهای مختلف معمولاً هنگام ثبت نام کاربران، شماره تلفن را درخواست میکنند. اگر به جای اعداد، موارد مزخرف را وارد کنید، وب سایت یک خطا گزارش می دهد: "این شماره تلفن نیست!" توسعه دهندگان چنین وب سایتی (یا بهتر است بگوییم مکانیسم مجوز کاربر آن) می توانند چیزی شبیه به ما را PhoneNumberValidator
در کد خود قرار دهند. به عبارت دیگر، ما یک کلاس خارجی با یک متد داریم که در یک مکان در برنامه استفاده می شود و هیچ جای دیگر. و اگر از آن استفاده شود، هیچ چیز در آن تغییر نخواهد کرد: یک روش کار خود را انجام می دهد - و بس. در این حالت، از آنجایی که تمام منطق در یک روش جمع شده است، بسیار راحت تر و مناسب تر خواهد بود که یک کلاس اضافی را در آن کپسوله کنیم. هیچ روشی برای خود ندارد جز یک گیرنده و تنظیم کننده. در واقع ما فقط به داده های سازنده نیاز داریم. در روش های دیگر دخالتی ندارد. بر این اساس، دلیلی وجود ندارد که اطلاعات مربوط به آن را خارج از تنها روش مورد استفاده قرار دهیم. ما همچنین مثالی زدیم که در آن یک کلاس محلی در یک متد اعلان شده است، اما این تنها گزینه نیست. می توان آن را به سادگی در یک بلوک کد اعلام کرد:
public class PhoneNumberValidator {
{
class PhoneNumber {
private String phoneNumber;
public PhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
}
public void validatePhoneNumber(String phoneNumber) {
// ...number validation code
}
}
یا حتی در for
حلقه!
public class PhoneNumberValidator {
public void validatePhoneNumber(String phoneNumber) {
for (int i = 0; i < 10; i++) {
class PhoneNumber {
private String phoneNumber;
public PhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
// ...some logic
}
// ...number validation code
}
}
اما چنین مواردی بسیار نادر است. در بیشتر موارد، اعلان در داخل متد اتفاق می افتد. بنابراین، ما اعلامیه ها را کشف کردیم، و همچنین در مورد "فلسفه" صحبت کردیم :) کلاس های محلی چه ویژگی ها و تفاوت های اضافی در مقایسه با کلاس های داخلی دارند؟ یک شی از یک کلاس محلی نمی تواند خارج از متد یا بلوکی که در آن اعلان شده است ایجاد شود. تصور کنید که ما به generatePhoneNumber()
روشی نیاز داریم که یک شماره تلفن تصادفی تولید کند و یک PhoneNumber
شی را برگرداند. در شرایط فعلی ما نمی توانیم چنین متدی را در کلاس اعتبار سنجی خود ایجاد کنیم:
public class PhoneNumberValidator {
public void validatePhoneNumber(String number) {
class PhoneNumber {
private String phoneNumber;
public PhoneNumber() {
this.phoneNumber = number;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
// ...number validation code
}
// Error! The compiler does not recognize the PhoneNumber class
public PhoneNumber generatePhoneNumber() {
}
}
یکی دیگر از ویژگی های مهم کلاس های محلی، امکان دسترسی به متغیرهای محلی و پارامترهای متد است. در صورتی که فراموش کردید، متغیری که در یک متد اعلام شده است به عنوان متغیر "محلی" شناخته می شود. یعنی اگر به دلایلی String usCountryCode
داخل متد یک متغیر محلی ایجاد کنیم، می توانیم از کلاس محلی به آن دسترسی داشته باشیم. با این حال، ظرافت های زیادی وجود دارد که به نسخه زبان مورد استفاده در برنامه بستگی دارد. در ابتدای درس به این نکته اشاره کردیم که ممکن است کد یکی از مثال ها در جاوا 7 کامپایل نشود، یادتان هست؟ حال بیایید دلایل این موضوع را در نظر بگیریم :) در جاوا 7، یک کلاس محلی می تواند به متغیر محلی یا پارامتر متد دسترسی داشته باشد که آنها به صورت متد اعلام شده باشند:validatePhoneNumber()
PhoneNumber
final
public void validatePhoneNumber(String number) {
String usCountryCode = "+1";
class PhoneNumber {
private String phoneNumber;
// Error! The method parameter must be declared as final!
public PhoneNumber() {
this.phoneNumber = number;
}
public void printUsCountryCode() {
// Error! The local variable must be declared as final!
System.out.println(usCountryCode);
}
}
// ...number validation code
}
در اینجا کامپایلر دو خطا ایجاد می کند. و اینجا همه چیز مرتب است:
public void validatePhoneNumber(final String number) {
final String usCountryCode = "+1";
class PhoneNumber {
private String phoneNumber;
public PhoneNumber() {
this.phoneNumber = number;
}
public void printUsCountryCode() {
System.out.println(usCountryCode);
}
}
// ...number validation code
}
اکنون می دانید که چرا کدهای ابتدای درس کامپایل نمی شوند: در جاوا 7، یک کلاس محلی فقط به final
پارامترهای متد و final
متغیرهای محلی دسترسی دارد. در جاوا 8، رفتار کلاس های محلی تغییر کرده است. در این نسخه از زبان، یک کلاس محلی نه تنها به final
متغیرها و پارامترهای محلی، بلکه به آنهایی که هستند نیز دسترسی دارد effective-final
. Effective-final
متغیری است که مقدار آن از زمان اولیه تغییر نکرده است. به عنوان مثال، در جاوا 8، ما می توانیم به راحتی usCountryCode
متغیر را در کنسول نمایش دهیم، حتی اگر نباشد final
. مهم این است که ارزش آن تغییر نمی کند. در مثال زیر، همه چیز همانطور که باید کار می کند:
public void validatePhoneNumber(String number) {
String usCountryCode = "+1";
class PhoneNumber {
public void printUsCountryCode() {
// Java 7 would produce an error here
System.out.println(usCountryCode);
}
}
// ...number validation code
}
اما اگر مقدار متغیر را بلافاصله پس از مقداردهی اولیه تغییر دهیم، کد کامپایل نخواهد شد.
public void validatePhoneNumber(String number) {
String usCountryCode = "+1";
usCountryCode = "+8";
class PhoneNumber {
public void printUsCountryCode() {
// Error!
System.out.println(usCountryCode);
}
}
// ...number validation code
}
جای تعجب نیست که یک کلاس محلی زیرگونه ای از مفهوم طبقه درونی است! آنها همچنین دارای ویژگی های مشترک هستند. یک کلاس محلی به تمام فیلدها و متدهای کلاس خارجی (حتی خصوصی) دسترسی دارد: هم ثابت و هم غیراستاتیک. به عنوان مثال، بیایید یک String phoneNumberRegex
فیلد ثابت به کلاس اعتبارسنجی خود اضافه کنیم:
public class PhoneNumberValidator {
private static String phoneNumberRegex = "[^0-9]";
public void validatePhoneNumber(String phoneNumber) {
class PhoneNumber {
// ......
}
}
}
اعتبار سنجی با استفاده از این متغیر ثابت انجام خواهد شد. این روش بررسی می کند که آیا رشته ارسال شده دارای کاراکترهایی است که با عبارت منظم " " مطابقت ندارند [^0-9]
(یعنی هر کاراکتری که رقمی از 0 تا 9 نباشد). ما به راحتی می توانیم از کلاس محلی به این متغیر دسترسی پیدا کنیم PhoneNumber
. به عنوان مثال، یک گیرنده بنویسید:
public String getPhoneNumberRegex() {
return phoneNumberRegex;
}
کلاس های محلی شبیه به کلاس های داخلی هستند، زیرا نمی توانند هیچ عضو ثابتی را تعریف یا اعلام کنند. کلاس های محلی در متدهای استاتیک فقط می توانند به اعضای استاتیک کلاس احاطه کننده ارجاع دهند. برای مثال، اگر یک متغیر (فیلد) از کلاس احاطه کننده را به عنوان ثابت تعریف نکنید، کامپایلر جاوا این خطا را ایجاد می کند: "متغیر غیر استاتیک نمی تواند از یک زمینه ایستا ارجاع شود." کلاس های محلی ثابت نیستند، زیرا به اعضای نمونه در بلوک محصور دسترسی دارند. در نتیجه، آنها نمی توانند حاوی اکثر انواع اعلان های ایستا باشند. شما نمی توانید یک رابط را در داخل یک بلوک اعلام کنید: اینترفیس ها ذاتا ایستا هستند. این کد کامپایل نمی کند:
public class PhoneNumberValidator {
public static void validatePhoneNumber(String number) {
interface I {}
class PhoneNumber implements I{
private String phoneNumber;
public PhoneNumber() {
this.phoneNumber = number;
}
}
// ...number validation code
}
}
اما اگر یک رابط در داخل یک کلاس خارجی اعلان شود، PhoneNumber
کلاس می تواند آن را پیاده سازی کند:
public class PhoneNumberValidator {
interface I {}
public static void validatePhoneNumber(String number) {
class PhoneNumber implements I{
private String phoneNumber;
public PhoneNumber() {
this.phoneNumber = number;
}
}
// ...number validation code
}
}
مقداردهی اولیه استاتیک (بلوک های راه اندازی) یا رابط ها را نمی توان در کلاس های محلی اعلام کرد. اما کلاس های محلی می توانند اعضای ثابت داشته باشند، مشروط بر اینکه متغیرهای ثابت باشند ( static final
). و حالا شما در مورد کلاس های محلی می دانید، مردم! همانطور که می بینید، آنها تفاوت های زیادی با طبقات داخلی معمولی دارند. ما حتی مجبور شدیم ویژگیهای نسخههای خاصی از زبان را بررسی کنیم تا بفهمیم چگونه کار میکنند :) در درس بعدی، در مورد کلاسهای داخلی ناشناس - آخرین گروه از کلاسهای تودرتو صحبت خواهیم کرد. امیدوارم در مطالعه موفق باشی! :)
GO TO FULL VERSION