۳۰ آذر ۱۴۰۳

Techboy

اخبار و اطلاعات روز تکنولوژی

وراثت در جاوا، قسمت ۱: کلمه کلیدی extends

از کلمه کلیدی extensions جاوا برای استخراج یک کلاس فرزند از کلاس والد، فراخوانی سازنده‌ها و متدهای کلاس والد، روش‌های نادیده گرفتن و غیره استفاده کنید.

از کلمه کلیدی extensions جاوا برای استخراج یک کلاس فرزند از کلاس والد، فراخوانی سازنده‌ها و متدهای کلاس والد، روش‌های نادیده گرفتن و غیره استفاده کنید.

جاوا از استفاده مجدد از کلاس از طریق وارث و ترکیب پشتیبانی می کند. این آموزش دو قسمتی به شما می آموزد که چگونه از وراثت در برنامه های جاوا خود استفاده کنید.

آنچه در این آموزش جاوا خواهید آموخت

نیمه اول این مقدمه برای وراثت جاوا به شما می آموزد که چگونه از کلمه کلیدی extends برای استخراج یک کلاس فرزند از کلاس والد، فراخوانی سازنده ها و متدهای کلاس والد، و نادیده گرفتن متدها استفاده کنید:

  • ارث در جاوا چیست؟
  • ارث تکی و ارث چندگانه
  • نحوه استفاده از کلمه کلیدی extensions در جاوا
  • درک سلسله مراتب کلاس جاوا
  • زمان استفاده از نادیده گرفتن روش در مقابل بارگذاری بیش از حد روش

وارثت در جاوا چیست؟

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

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

jw inheritance p1 fig1

شکل ۱. یک جفت سلسله مراتب ارثی ریشه در دسته وسایل نقلیه رایج دارند

ارث تکی و ارث چندگانه

مثال در شکل ۱ وراثت منفرد را نشان می‌دهد که در آن یک دسته فرزند حالت و رفتارها را از یک دسته والدین مستقیم به ارث می‌برد. در مقابل، ارث بری چندگانه یک دسته فرزند را قادر می‌سازد تا حالت و رفتار را از دو یا چند دسته والد فوری به ارث ببرد. سلسله مراتب در شکل ۲ وراثت چندگانه را نشان می دهد.

jw inheritancep1 fig2

شکل ۲. هاورکرافت ارث بری از دسته های وسایل نقلیه زمینی و آبی را ضرب می کند

دسته ها بر اساس کلاس ها توصیف می شوند. جاوا از وراثت منفرد از طریق class extension پشتیبانی می‌کند، که در آن یک کلاس مستقیماً فیلدها و متدهای قابل دسترسی را از کلاس دیگر با گسترش آن کلاس به ارث می‌برد. اما جاوا از وراثت چندگانه از طریق پسوند کلاس پشتیبانی نمی کند.

هنگام مشاهده سلسله مراتب وراثت، می توانید به راحتی با وجود یک الگوی الماس، چندین وراثت را تشخیص دهید. شکل ۲ این الگو را در زمینه وسیله نقلیه، وسیله نقلیه زمینی، وسیله نقلیه آبی و هاورکرافت نشان می دهد.

نحوه استفاده از کلمه کلیدی extensions در جاوا

جاوا از پسوند کلاس از طریق کلمه کلیدی extends پشتیبانی می کند. در صورت وجود، extends یک رابطه والد-فرزند بین دو کلاس را مشخص می کند. در زیر از extends برای ایجاد رابطه بین کلاس‌های Vehicle و Car و سپس بین Account و استفاده می‌کنم. SavingsAccount:

class Vehicle
{
   // member declarations
}
class Car extends Vehicle
{
   // inherit accessible members from Vehicle
   // provide own member declarations
}
class Account
{
   // member declarations
}
class SavingsAccount extends Account
{
   // inherit accessible members from Account
   // provide own member declarations
}

کلمه کلیدی extends بعد از نام کلاس و قبل از نام کلاس دیگر مشخص می شود. نام کلاس قبل از extends فرزند را مشخص می‌کند و نام کلاس بعد از extends والدین را مشخص می‌کند. تعیین نام چند کلاس بعد از extends غیرممکن است زیرا جاوا از وراثت چندگانه مبتنی بر کلاس پشتیبانی نمی‌کند.

این مثال‌ها روابط is-a را کدگذاری می‌کنند: خودرو یک تخصصی خودرو و SavingsAccount است a تخصصی حساب. خودرو و حساب به‌عنوان کلاس‌های پایه، کلاس‌های والد، یا ابر کلاس‌ها شناخته می‌شوند. خودرو و SavingsAccount به‌عنوان کلاس‌های مشتق شده، کلاس‌های کودک، یا زیر کلاس‌ها شناخته می‌شوند.

کلاس های پایانی

شما ممکن است کلاسی را اعلام کنید که نباید تمدید شود. به عنوان مثال به دلایل امنیتی در جاوا از کلمه کلیدی final برای جلوگیری از گسترش برخی کلاس ها استفاده می کنیم. به سادگی یک سرصفحه کلاس را با final پیشوند کنید، مانند final class Password. با توجه به این اعلان، اگر کسی بخواهد Password را گسترش دهد، کامپایلر خطایی را گزارش خواهد کرد.

کلاس‌های فرزند فیلدها و روش‌های قابل دسترسی را از کلاس‌های والد خود و دیگر نیاکان خود به ارث می‌برند. با این حال، آنها هرگز سازنده ها را به ارث نمی برند. در عوض، کلاس های فرزند سازنده های خود را اعلام می کنند. علاوه بر این، آنها می توانند زمینه ها و روش های خود را برای متمایز کردن آنها از والدین خود اعلام کنند. فهرست ۲ را در نظر بگیرید.

class Account
{
   private String name;

   private long amount;

   Account(String name, long amount)
   {
      this.name = name;
      setAmount(amount);
   }

   void deposit(long amount)
   {
      this.amount += amount;
   }

   String getName()
   {
      return name;
   }

   long getAmount()
   {
      return amount;
   }

   void setAmount(long amount)
   {
      this.amount = amount;
   }
}

فهرست ۲ یک کلاس حساب بانکی عمومی را توصیف می کند که دارای نام و مبلغ اولیه است که هر دو در سازنده تنظیم شده اند. همچنین به کاربران امکان سپرده گذاری را می دهد. (شما می توانید با واریز مقادیر منفی پول برداشت کنید، اما ما این احتمال را نادیده می گیریم.) توجه داشته باشید که نام حساب باید هنگام ایجاد حساب تنظیم شود.

نماینده مقادیر ارز

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

فهرست ۳ یک کلاس فرزند SavingsAccount ارائه می دهد که کلاس والد حساب آن را گسترش می دهد.

class SavingsAccount extends Account
{
   SavingsAccount(long amount)
   {
      super("savings", amount);
   }
}

کلاس SavingsAccount بی اهمیت است زیرا نیازی به اعلام فیلدها یا روش های اضافی ندارد. با این حال، سازنده ای را اعلام می کند که فیلدها را در سوپرکلاس Account خود مقداردهی اولیه می کند. راه‌اندازی زمانی اتفاق می‌افتد که سازنده Account از طریق کلیدواژه super جاوا و به دنبال آن یک لیست آرگومان پرانتز شده فراخوانی شود.

چه زمانی و کجا با super() تماس بگیرید

همانطور که this() باید اولین عنصر سازنده ای باشد که سازنده دیگری را در همان کلاس فراخوانی می کند، super() نیز باید اولین عنصر سازنده باشد. که سازنده ای را در سوپرکلاس خود فراخوانی می کند. اگر این قانون را زیر پا بگذارید، کامپایلر یک خطا را گزارش می کند. کامپایلر همچنین در صورت تشخیص فراخوانی super() در یک متد، خطا را گزارش خواهد کرد. فقط در یک سازنده super() را فراخوانی کنید.

فهرست ۴ حساب را با کلاس CheckingAccount بیشتر گسترش می‌دهد.

class CheckingAccount extends Account
{
   CheckingAccount(long amount)
   {
      super("checking", amount);
   }

   void withdraw(long amount)
   {
      setAmount(getAmount() - amount);
   }
}

CheckingAccount کمی بیشتر از SavingsAccount است زیرا یک روش withdraw() را اعلام می‌کند. به تماس‌های این روش با setAmount() و getAmount() توجه کنید که CheckingAccount از Account به ارث می‌برد. شما نمی توانید مستقیماً به فیلد مقدار در حساب دسترسی داشته باشید زیرا این فیلد خصوصی اعلام شده است (فهرست ۲ را ببینید).

super() و سازنده بدون آرگومان

اگر super() در سازنده زیر کلاس مشخص نشده باشد، و اگر superclass سازنده no-argument را اعلام نکند، کامپایلر یک خطا را گزارش می‌کند. . این به این دلیل است که سازنده کلاس فرعی باید سازنده سوپرکلاس بدون آرگومان را هنگامی که super() وجود ندارد فراخوانی کند.

درک سلسله مراتب کلاس جاوا

من یک کلاس برنامه AccountDemo ایجاد کرده ام که به شما امکان می دهد سلسله مراتب کلاس Account را امتحان کنید. ابتدا نگاهی به کد منبع AccountDemo بیاندازید.

class AccountDemo
{
   public static void main(String[] args)
   {
      SavingsAccount sa = new SavingsAccount(10000);
      System.out.println("account name: " + sa.getName());
      System.out.println("initial amount: " + sa.getAmount());
      sa.deposit(5000);
      System.out.println("new amount after deposit: " + sa.getAmount());

      CheckingAccount ca = new CheckingAccount(20000);
      System.out.println("account name: " + ca.getName());
      System.out.println("initial amount: " + ca.getAmount());
      ca.deposit(6000);
      System.out.println("new amount after deposit: " + ca.getAmount());
      ca.withdraw(3000);
      System.out.println("new amount after withdrawal: " + ca.getAmount());
   }
}

روش main() در فهرست ۵ ابتدا SavingsAccount و سپس CheckingAccount را نشان می‌دهد. با فرض اینکه فایل های منبع Account.java، SavingsAccount.java، CheckingAccount.java و AccountDemo.java در همان دایرکتوری، یکی از دستورات زیر را برای کامپایل همه این فایل های منبع اجرا کنید:

javac AccountDemo.java
javac *.java

برای اجرای برنامه دستور زیر را اجرا کنید:

java AccountDemo

شما باید خروجی زیر را مشاهده کنید:

account name: savings
initial amount: 10000
new amount after deposit: 15000
account name: checking
initial amount: 20000
new amount after deposit: 26000
new amount after withdrawal: 23000

نسخ روش در مقابل بارگذاری روش

یک کلاس فرعی می‌تواند یک متد ارثی را بگذارد (جایگزین) کند تا به جای آن، نسخه متد زیر کلاس فراخوانی شود. یک متد نادیده گرفته باید همان نام، لیست پارامترها و نوع بازگشتی را به عنوان روشی که لغو می شود مشخص کند. برای نشان دادن، من یک روش print() را در کلاس Vehicle که در فهرست ۶ نشان داده شده است، اعلام کرده‌ام.

class Vehicle
{
   private String make;
   private String model;
   private int year;

   Vehicle(String make, String model, int year)
   {
      this.make = make;
      this.model = model;
      this.year = year;
   }

   String getMake()
   {
      return make;
   }

   String getModel()
   {
      return model;
   }

   int getYear()
   {
      return year;
   }

   void print()
   {
      System.out.println("Make: " + make + ", Model: " + model + ", Year: " +
                         year);
   }
}

بعد، print() را در کلاس Truck لغو می کنم.

class Truck extends Vehicle
{
   private double tonnage;

   Truck(String make, String model, int year, double tonnage)
   {
      super(make, model, year);
      this.tonnage = tonnage;
   }

   double getTonnage()
   {
      return tonnage;
   }

   void print()
   {
      super.print();
      System.out.println("Tonnage: " + tonnage);
   }
}

روش print()

کامیون دارای نام، نوع بازگشت و فهرست پارامترهای خودرو print است. روش (). همچنین توجه داشته باشید که روش print() کامیون ابتدا روش print() خودرو را فراخوانی می‌کند. پیشوند super. به نام روش. اغلب ایده خوبی است که ابتدا منطق سوپرکلاس را اجرا کنید و سپس منطق زیر کلاس را اجرا کنید.

فراخوانی متدهای سوپرکلاس از متدهای زیر کلاس

برای فراخوانی یک متد سوپرکلاس از متد زیر کلاس فراگیر، نام روش را با کلمه رزرو شده super و عملگر دسترسی عضو پیشوند قرار دهید. در غیر این صورت، به صورت بازگشتی متد overriding زیر کلاس را فراخوانی می کنید. در برخی موارد، یک زیر کلاس با اعلام فیلدهای همنام، فیلدهای سوپرکلاس غیرخصوصی را پنهان می کند. می‌توانید از super و اپراتور دسترسی اعضا برای دسترسی به قسمت‌های سوپرکلاس غیرخصوصی استفاده کنید.

برای تکمیل این مثال، روش main() کلاس VehicleDemo را استخراج کردم:


Truck truck = new Truck("Ford", "F150", 2008, 0.5);
System.out.println("Make = " + truck.getMake());
System.out.println("Model = " + truck.getModel());
System.out.println("Year = " + truck.getYear());
System.out.println("Tonnage = " + truck.getTonnage());
truck.print();

خط نهایی، truck.print();، روش print() truck را فراخوانی می کند. این روش ابتدا Vehicle‘s print() را فراخوانی می‌کند تا محصول، مدل و سال کامیون را نمایش دهد. سپس تناژ کامیون را تولید می کند. این بخش از خروجی در زیر نشان داده شده است:

Make: Ford, Model: F150, Year: 2008
Tonnage: 0.5

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

گاهی اوقات ممکن است به دلیل امنیتی یا دلایل دیگری نیاز به اعلام روشی داشته باشید که نباید نادیده گرفته شود. برای این منظور می توانید از کلمه کلیدی final استفاده کنید. برای جلوگیری از نادیده گرفتن، به سادگی پیشوند یک هدر متد را با final، مانند final String getMake() قرار دهید. اگر کسی بخواهد این روش را در یک کلاس فرعی نادیده بگیرد، کامپایلر یک خطا گزارش می‌کند.

از @Override برای تشخیص بارگذاری بیش از حد روش استفاده کنید

فرض کنید روش print() را در لیست ۷ با روش زیر جایگزین کرده اید:

void print(String owner)
{
   System.out.print("Owner: " + owner);
   super.print();
}

کلاس Truck تغییر یافته اکنون دارای دو روش print() است: روشی که به صراحت اعلام شده است و روشی که از Vehicle به ارث رسیده است. روش void print (مالک رشته) روش print() Vehicle را لغو نمی کند. در عوض، آن را بیش از حد بارگذاری می کند.

شما می‌توانید با قرار دادن پیشوند هدر متد زیر کلاس با حاشیه‌نویسی @Override، تلاش برای اضافه‌بار به‌جای لغو یک روش را در زمان کامپایل تشخیص دهید:

@Override
void print(String owner)
{
   System.out.print("Owner: " + owner);
   super.print();
}

مشخص کردن @Override به کامپایلر می گوید که متد داده شده روش دیگری را لغو می کند. اگر کسی بخواهد متد را بیش از حد بارگذاری کند، کامپایلر یک خطا را گزارش می‌کند. بدون این حاشیه نویسی، کامپایلر خطایی را گزارش نمی کند زیرا بارگذاری بیش از حد روش قانونی است.

زمان استفاده از @Override

عادت پیشوند کردن متدهای نادیده گرفته شده را با @Override ایجاد کنید. این عادت به شما کمک می کند تا اشتباهات بارگذاری بیش از حد را خیلی زودتر تشخیص دهید.

روش های نادیده گرفته شده و روش های محافظت شده

جاوا کلمه کلیدی محافظت‌شده را برای استفاده در زمینه‌ای فراگیر از روش ارائه می‌کند. همچنین می‌توانید از محافظت‌شده برای فیلدها استفاده کنید. این کلمه کلیدی معمولاً برای شناسایی روش‌هایی که برای نادیده گرفتن طراحی شده‌اند استفاده می‌شود، با توجه به اینکه همه روش‌های قابل دسترس نباید نادیده گرفته شوند.

وقتی یک روش یا فیلد را محافظت شده اعلام می‌کنید، متد یا فیلد برای همه کدهای موجود در هر کلاسی که در همان بسته اعلام شده است قابل دسترسی است. همچنین برای زیر کلاس ها بدون توجه به بسته های آنها قابل دسترسی است. (در مقاله آینده درباره بسته ها بحث خواهم کرد.)

نتیجه گیری

نیمه دوم این مقدمه وراثت کلاس Object و متدهای آن را تور می کند. هر کلاس جاوا از Object ارث می برد، بنابراین آشنایی با این سوپرکلاس برای درک سلسله مراتب کلاس جاوا ضروری است. همچنین به نکته جاوا معرفی ترکیب در مقابل وراثت مراجعه کنید. . Composition یک جایگزین مهم برای وراثت برای ایجاد روابط بین کلاس ها ارائه می دهد. همچنین یکی از بزرگترین چالش ها را با ارث حل می کند.

پرش به: وارثیت در جاوا، بخش ۲: شی و روش‌های آن .