۲۹ شهریور ۱۴۰۳

Techboy

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

زمان استفاده از کلاس های انتزاعی در مقابل رابط ها در جاوا

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

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

کلاس‌ها و رابط‌های انتزاعی در کد جاوا و حتی در خود کیت توسعه جاوا (JDK) فراوان هستند. هر عنصر کد یک هدف اساسی دارد:

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

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

ماهیت یک رابط در جاوا

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

زمان استفاده از رابط ها در جاوا

اینترفیس ها برای جداسازی کد و پیاده سازی چندشکلی بسیار مفید هستند. می‌توانیم نمونه‌ای را در JDK با رابط List ببینیم:


public interface List<E> extends Collection<E> {

    int size();
    boolean isEmpty();
    boolean add(E e);
    E remove(int index);
    void clear();
}

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

واسط List حاوی قراردادی است که می‌تواند توسط ArrayList، Vector، LinkedList و کلاس های دیگر.

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


List list = new ArrayList();
System.out.println(list.getClass());

 List list = new LinkedList();
 System.out.println(list.getClass());

خروجی این کد در اینجا آمده است:


class java.util.ArrayList
class java.util.LinkedList

در این مورد، روش‌های پیاده‌سازی برای ArrayList، LinkedList و Vector همگی متفاوت هستند، که یک سناریوی عالی برای استفاده از رابط. اگر متوجه شدید که بسیاری از کلاس‌ها متعلق به یک کلاس والد با اقدامات روش مشابه اما رفتار متفاوت هستند، بهتر است از یک رابط استفاده کنید.

بعد، اجازه دهید به چند مورد از کارهایی که می‌توانیم با رابط‌ها انجام دهیم نگاه کنیم.

نسخ یک روش رابط در جاوا

به خاطر داشته باشید که رابط نوعی قرارداد است که باید توسط یک کلاس بتن اجرا شود. متدهای واسط به طور ضمنی انتزاعی هستند و همچنین نیاز به اجرای کلاس مشخص دارند.

یک مثال در اینجا آمده است:


public class OverridingDemo {
  public static void main(String[] args) {
    Challenger challenger = new JavaChallenger();
    challenger.doChallenge();
  }
}

interface Challenger {
  void doChallenge();
}

class JavaChallenger implements Challenger {
  @Override
  public void doChallenge() {
    System.out.println("Challenge done!");
  }
}

خروجی این کد در اینجا آمده است:


Challenge done!

به جزئیات توجه کنید که روش های رابط به طور ضمنی انتزاعی هستند. این بدان معنی است که ما نیازی به صراحتاً آنها را به عنوان انتزاعی نداریم.

متغیرهای ثابت در جاوا

قانون دیگری که باید به خاطر بسپارید این است که یک رابط فقط می تواند دارای متغیرهای ثابت باشد. بنابراین، کد زیر خوب است:


public interface Challenger {
  
  int number = 7;
  String name = "Java Challenger";

}

توجه داشته باشید که هر دو متغیر به طور ضمنی نهایی و ایستا هستند. این بدان معنی است که آنها ثابت هستند، به یک نمونه وابسته نیستند و نمی توان آنها را تغییر داد.

API چیست؟ رابط های برنامه نویسی کاربردی توضیح داده شده است

اگر می‌خواهیم متغیرها را در واسط Challenger تغییر دهیم، به این شکل بگو:


Challenger.number = 8;
Challenger.name = "Another Challenger";

ما یک خطای کامپایل را راه اندازی خواهیم کرد، مانند این:


Cannot assign a value to final variable 'number'
Cannot assign a value to final variable 'name'

روش های پیش فرض در جاوا

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

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

یک روش در JDK که از یک روش پیش‌فرض استفاده می‌کند، forEach() است که بخشی از رابط Iterable است. به‌جای کپی کردن کد در هر پیاده‌سازی Iterable، می‌توانیم به سادگی از روش forEach دوباره استفاده کنیم:


default void forEach(Consumer<? super T> action) { 
  // Code implementation here…

هر پیاده‌سازی تکرارپذیر می‌تواند از روش forEach() بدون نیاز به اجرای روش جدید استفاده کند. سپس، می‌توانیم کد را با یک روش پیش‌فرض دوباره استفاده کنیم.

بیایید روش پیش فرض خود را ایجاد کنیم:


public class DefaultMethodExample {

  public static void main(String[] args) {
    Challenger challenger = new JavaChallenger();
    challenger.doChallenge();
  }

}

class JavaChallenger implements Challenger { }

interface Challenger {

  default void doChallenge() {
    System.out.println("Challenger doing a challenge!");
  }
}

خروجی این است:


Challenger doing a challenge!

نکته مهمی که در مورد روش های پیش فرض باید به آن توجه کرد این است که هر روش پیش فرض نیاز به پیاده سازی دارد. یک روش پیش‌فرض نمی‌تواند ثابت باشد.

اکنون، اجازه دهید به کلاس‌های انتزاعی برویم.

ماهیت یک کلاس انتزاعی در جاوا

کلاس های انتزاعی می توانند حالت با متغیرهای نمونه داشته باشند. این به این معنی است که یک متغیر نمونه را می توان استفاده کرد و جهش داد. این یک مثال است:


public abstract class AbstractClassMutation {

  private String name = "challenger";

  public static void main(String[] args) {
    AbstractClassMutation abstractClassMutation = new AbstractClassImpl();
    abstractClassMutation.name = "mutated challenger";
    System.out.println(abstractClassMutation.name);
  }

}

class AbstractClassImpl extends AbstractClassMutation { }

خروجی اینجاست:


mutated challenger

روش های انتزاعی در کلاس های انتزاعی

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


public abstract class AbstractMethods {

  abstract void doSomething();

}

تلاش برای اعلام یک روش بدون پیاده سازی، و بدون کلمه کلیدی abstract، مانند این:


public abstract class AbstractMethods {
   void doSomethingElse();
}

به یک خطای کامپایل منجر می‌شود، مانند این:


Missing method body, or declare abstract

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

این ایده خوبی است که از یک کلاس انتزاعی زمانی که نیاز به پیاده سازی حالت تغییرپذیر دارید استفاده کنید. به عنوان مثال، چارچوب مجموعه‌های جاوا شامل AbstractList، که از حالت متغیرها استفاده می کند.

در مواردی که نیازی به حفظ وضعیت کلاس ندارید، معمولاً بهتر است از یک رابط استفاده کنید.

کلاس های انتزاعی در عمل

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

تفاوت‌های بین کلاس‌های انتزاعی و رابط‌ها در جاوا

از دیدگاه برنامه نویسی شی گرا، تفاوت اصلی بین یک رابط و یک کلاس انتزاعی این است که یک رابط نمی تواند حالت داشته باشد، در حالی که کلاس انتزاعی می تواند حالت داشته باشد. با متغیرهای نمونه.

NET 8 Preview 2 عملکرد Blazor را افزایش می دهد

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

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

همچنین مهم است که توجه داشته باشید که عبارات لامبدا را فقط می‌توان با یک رابط عملکردی (به معنی واسط تنها با یک روش) استفاده کرد، در حالی که کلاس‌های انتزاعی با تنها یک روش انتزاعی نمی‌توانند از لامبدا استفاده کنند. p>

جدول ۱ تفاوت‌های بین کلاس‌های انتزاعی و رابط‌ها را خلاصه می‌کند.

رابط ها

کلاس های انتزاعی

فقط می تواند متغیرهای استاتیک نهایی را داشته باشد. یک رابط هرگز نمی تواند وضعیت خود را تغییر دهد.

می تواند هر نوع نمونه یا متغیر ایستا، قابل تغییر یا تغییرناپذیر داشته باشد.

یک کلاس می تواند چندین رابط را پیاده سازی کند.

یک کلاس می تواند تنها یک کلاس انتزاعی را گسترش دهد.

می توان با کلمه کلیدی Implements پیاده سازی کرد. یک رابط همچنین می‌تواند رابط‌های توسعه را گسترش دهد.

فقط قابل تمدید است.

فقط می‌توان از فیلدهای نهایی، پارامترها یا متغیرهای محلی برای روش‌ها استفاده کرد.

می تواند فیلدها، پارامترها یا متغیرهای محلی قابل تغییر نمونه داشته باشد.

فقط رابط‌های کاربردی می‌توانند از ویژگی لامبدا در جاوا استفاده کنند.

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

نمی توان سازنده داشت.

می تواند سازنده داشته باشد.

می تواند روش های انتزاعی داشته باشد.

می‌تواند روش‌های پیش‌فرض و استاتیک (در جاوا ۸ معرفی شده) داشته باشد.

می‌تواند روش‌های خصوصی با پیاده‌سازی داشته باشد (معرفی شده در جاوا ۹).

می تواند هر نوع روشی داشته باشد.

رابط ها

کلاس های انتزاعی

فقط می تواند متغیرهای استاتیک نهایی را داشته باشد. یک رابط هرگز نمی تواند وضعیت خود را تغییر دهد.

می تواند هر نوع نمونه یا متغیر ایستا، قابل تغییر یا تغییرناپذیر داشته باشد.

یک کلاس می تواند چندین رابط را پیاده سازی کند.

یک کلاس می تواند تنها یک کلاس انتزاعی را گسترش دهد.

می توان با کلمه کلیدی Implements پیاده سازی کرد. یک رابط همچنین می‌تواند رابط‌های توسعه را گسترش دهد.

فقط قابل تمدید است.

فقط می‌توان از فیلدهای نهایی، پارامترها یا متغیرهای محلی برای روش‌ها استفاده کرد.

می تواند فیلدها، پارامترها یا متغیرهای محلی قابل تغییر نمونه داشته باشد.

فقط رابط‌های کاربردی می‌توانند از ویژگی لامبدا در جاوا استفاده کنند.

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

نمی توان سازنده داشت.

می تواند سازنده داشته باشد.

می تواند روش های انتزاعی داشته باشد.

می‌تواند روش‌های پیش‌فرض و استاتیک (در جاوا ۸ معرفی شده) داشته باشد.

می‌تواند روش‌های خصوصی با پیاده‌سازی داشته باشد (معرفی شده در جاوا ۹).

می تواند هر نوع روشی داشته باشد.

در چالش کد جاوا شرکت کنید!

بیایید تفاوت‌های اصلی بین رابط‌ها و کلاس‌های انتزاعی را با چالش کد جاوا بررسی کنیم. چالش کد زیر را داریم، یا می‌توانید کلاس‌های انتزاعی در مقابل چالش رابط‌ها را در قالب ویدیویی مشاهده کنید. p>

در کد زیر، هم یک کلاس واسط و هم یک کلاس انتزاعی اعلان شده است و کد نیز از لامبدا استفاده می کند.


public class AbstractResidentEvilInterfaceChallenge {
  static int nemesisRaids = 0;
  public static void main(String[] args) {
    Zombie zombie = () -> System.out.println("Graw!!! " + nemesisRaids++);
    System.out.println("Nemesis raids: " + nemesisRaids);
    Nemesis nemesis = new Nemesis() { public void shoot() { shoots = 23; }};

    Zombie.zombie.shoot();
    zombie.shoot();
    nemesis.shoot();
    System.out.println("Nemesis shoots: " + nemesis.shoots +
        " and raids: " + nemesisRaids);
  }
}
interface Zombie {
  Zombie zombie = () -> System.out.println("Stars!!!");
  void shoot();
}
abstract class Nemesis implements Zombie {
   public int shoots = 5;
}

به نظر شما وقتی این کد را اجرا کنیم چه اتفاقی می افتد؟ یکی از موارد زیر را انتخاب کنید:


     Compilation error at line 4
     
     Graw!!! 0
     Nemesis raids: 23
     Stars!!!
     Nemesis shoots: 23 and raids:1
     
     Nemesis raids: 0
     Stars!!!
     Graw!!! 0
     Nemesis shoots: 23 and raids: 1
     
     Nemesis raids: 0
     Stars!!!
     Graw!!! 1
     Nemesis shoots: 23 and raids:1
     
	Compilation error at line 6

ویدیوی چالش کد جاوا

آیا خروجی درستی را برای این چالش انتخاب کرده اید؟ ویدیو را تماشا کنید یا به خواندن ادامه دهید تا متوجه شوید.

GitHub Copilot Chat به GitHub می آید

درک رابط‌ها و کلاس‌ها و متدهای انتزاعی در جاوا

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

خط اول چالش کد شامل یک عبارت لامبدا برای رابط Zombie است. توجه کنید که در این لامبدا یک میدان ساکن را افزایش می دهیم. یک فیلد نمونه نیز در اینجا کار می کند، اما یک متغیر محلی که خارج از لامبدا اعلام شده است، کار نمی کند. بنابراین، تا کنون، کد به خوبی کامپایل خواهد شد. همچنین توجه کنید که عبارت لامبدا هنوز اجرا نشده است، بنابراین فیلد nemesisRaids هنوز افزایش نخواهد یافت.

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


Nemesis raids: 0

یک مفهوم جالب دیگر در این چالش کد جاوا این است که ما از کلاس داخلی ناشناس استفاده می کنیم. این اساساً به معنای هر کلاسی است که متدهای کلاس انتزاعی Nemesis را پیاده سازی کند. ما واقعاً کلاس انتزاعی Nemesis را نمونه سازی نمی کنیم زیرا در واقع یک کلاس ناشناس است. همچنین توجه داشته باشید که اولین کلاس انضمامی همیشه موظف به پیاده سازی متدهای انتزاعی در هنگام گسترش آنها خواهد بود.

در داخل رابط Zombie، رابط zombie static Zombie را داریم که با عبارت لامبدا اعلام شده است. بنابراین، وقتی روش تصویربرداری زامبی را فرا می‌خوانیم، موارد زیر را چاپ می‌کنیم:


Stars!!!

خط بعدی کد عبارت لامبدا را که در ابتدا ایجاد کردیم فراخوانی می کند. بنابراین، متغیر nemesisRaids افزایش خواهد یافت. با این حال، از آنجایی که ما از عملگر post-increment استفاده می کنیم، تنها پس از این دستور کد افزایش می یابد. خروجی بعدی این خواهد بود:


Graw!!! 0 

اکنون روش shoot را از nemesis فراخوانی می کنیم، که متغیر نمونه shoots آن را به ۲۳ تغییر می دهد. . توجه داشته باشید که این قسمت از کد بزرگترین تفاوت بین یک رابط و یک کلاس انتزاعی را نشان می دهد.

در نهایت، مقدار nemesis.shoots و nemesisRaids را چاپ می کنیم. بنابراین، خروجی این خواهد بود:

Nemesis shoots: 23 and raids: 1

در نتیجه، خروجی صحیح گزینه C است:


     Nemesis raids: 0
     Stars!!!
     Graw!!! 0
     Nemesis shoots: 23 and raids: 1

درباره جاوا بیشتر بیاموزید