وراثت و ترکیب، دو روش اساسی برای ارتباط کلاسهای جاوا را مقایسه کنید، سپس اشکالزدایی ClassCastExceptions را در وراثت جاوا تمرین کنید.
- زمان استفاده از وراثت در جاوا
- زمان استفاده از ترکیب در جاوا
- وراثت در مقابل ترکیب: دو مثال
- رویکردن روش با وراثت جاوا
- استفاده از سازنده ها با وراثت
- ارسال محتوا و ClassCastException را تایپ کنید
- در چالش وراثت جاوا شرکت کنید!
- چه اتفاقی افتاد؟
- درباره جاوا بیشتر بیاموزید
وراثت و ترکیب دو تکنیک برنامه نویسی هستند که توسعه دهندگان برای ایجاد روابط بین کلاس ها و اشیاء استفاده می کنند. در حالی که وراثت یک کلاس را از کلاس دیگر مشتق می کند، ترکیب یک کلاس را به عنوان مجموع اجزای آن تعریف می کند.
کلاسها و اشیاء ایجاد شده از طریق وراثت بهشدت مرتبط هستند زیرا تغییر والد یا سوپرکلاس در یک رابطه ارثی خطر شکستن کد شما را به همراه دارد. کلاسها و اشیاء ایجاد شده از طریق ترکیب بهطور آزاد با هم مرتبط هستند، به این معنی که میتوانید به راحتی اجزای سازنده را بدون شکستن کد خود تغییر دهید.
از آنجایی که کدهای جفت آزاد انعطاف پذیری بیشتری ارائه می دهند، بسیاری از توسعه دهندگان یاد گرفته اند که ترکیب تکنیکی بهتر از وراثت است، اما حقیقت پیچیده تر است. انتخاب یک ابزار برنامه نویسی مشابه انتخاب ابزار آشپزخانه مناسب است: شما از چاقوی کره برای برش سبزیجات استفاده نمی کنید و به همین ترتیب نباید ترکیب را برای هر سناریو برنامه نویسی انتخاب کنید.
در این چالش جاوا، تفاوت بین وراثت و ترکیب و چگونه تصمیم بگیرید که کدام یک برای برنامه شما مناسب است. در مرحله بعد، من شما را با چندین جنبه مهم اما چالش برانگیز ارث بری جاوا آشنا می کنم: overriding روش، کلمه کلیدی super
، و نوع ریخته گری. در نهایت، آنچه را که آموختهاید، با کار کردن از طریق یک مثال وراثت خط به خط آزمایش میکنید تا تعیین کنید که خروجی باید چقدر باشد.
زمان استفاده از وراثت در جاوا
در برنامه نویسی شی گرا، زمانی می توانیم از وراثت استفاده کنیم که بدانیم بین یک فرزند و کلاس والد آن رابطه “is a” وجود دارد. برخی از نمونه ها عبارتند از:
- یک فرد یک انسان است.
- گربه یک حیوان است.
- یک ماشین یک وسیله نقلیه است.
در هر مورد، فرزند یا زیر کلاس یک نسخه تخصصی از والد یا سوپرکلاس است. ارث بردن از سوپرکلاس نمونه ای از استفاده مجدد از کد است. برای درک بهتر این رابطه، کمی وقت بگذارید و کلاس Car
را مطالعه کنید، که از Vehicle
به ارث می رسد:
class Vehicle {
String brand;
String color;
double weight;
double speed;
void move() {
System.out.println("The vehicle is moving");
}
}
public class Car extends Vehicle {
String licensePlateNumber;
String owner;
String bodyStyle;
public static void main(String... inheritanceExample) {
System.out.println(new Vehicle().brand);
System.out.println(new Car().brand);
new Car().move();
}
}
هنگامی که قصد استفاده از وراثت را دارید، از خود بپرسید که آیا کلاس فرعی واقعاً نسخه تخصصی تری از سوپرکلاس است. در این مورد، خودرو یک نوع وسیله نقلیه است، بنابراین رابطه ارث معنا پیدا می کند.
زمان استفاده از ترکیب در جاوا
در برنامه نویسی شی گرا، می توانیم از ترکیب در مواردی استفاده کنیم که یک شی “دارای” (یا بخشی از) شی دیگری است. برخی از نمونه ها عبارتند از:
- یک ماشین دارای باتری است (باتری بخشی از خودرو است).
- یک فرد یک قلب دارد (قلب بخشی از شخص است).
- یک خانه یک اتاق نشیمن دارد (اتاق نشیمن بخشی از خانه است).
برای درک بهتر این نوع رابطه، ترکیب یک خانه
را در نظر بگیرید:
public class CompositionExample {
public static void main(String... houseComposition) {
new House(new Bedroom(), new LivingRoom());
// The house now is composed with a Bedroom and a LivingRoom
}
static class House {
Bedroom bedroom;
LivingRoom livingRoom;
House(Bedroom bedroom, LivingRoom livingRoom) {
this.bedroom = bedroom;
this.livingRoom = livingRoom;
}
}
static class Bedroom { }
static class LivingRoom { }
}
در این مورد، ما می دانیم که یک خانه دارای یک اتاق نشیمن و یک اتاق خواب است، بنابراین می توانیم از اشیاء Bedroom
و LivingRoom
در ترکیب یک استفاده کنیم. >خانه
.
کد را دریافت کنید
کد منبع را برای نمونههایی در این جاوا چلنجر دریافت کنید. میتوانید همزمان با دنبال کردن مثالها، آزمایشهای خود را اجرا کنید.
ارث در مقابل ترکیب: دو مثال
کد زیر را در نظر بگیرید. آیا این نمونه خوبی از وراثت است؟
import java.util.HashSet;
public class CharacterBadExampleInheritance extends HashSet<Object> {
public static void main(String... badExampleOfInheritance) {
BadExampleInheritance badExampleInheritance = new BadExampleInheritance();
badExampleInheritance.add("Homer");
badExampleInheritance.forEach(System.out::println);
}
در این مورد، پاسخ منفی است. کلاس فرزند متدهای زیادی را به ارث می برد که هرگز از آنها استفاده نخواهد کرد، و در نتیجه کدهایی با هم جفت شده است که هم گیج کننده و هم نگهداری آن دشوار است. اگر به دقت نگاه کنید، همچنین مشخص می شود که این کد تست “is a” را قبول نمی کند.
اکنون بیایید همان مثال را با استفاده از ترکیب امتحان کنیم:
import java.util.HashSet;
import java.util.Set;
public class CharacterCompositionExample {
static Set<String> set = new HashSet<>();
public static void main(String... goodExampleOfComposition) {
set.add("Homer");
set.forEach(System.out::println);
}
استفاده از ترکیب برای این سناریو به کلاس CharacterCompositionExample
اجازه میدهد فقط از دو روش از HashSet
استفاده کند، بدون اینکه همه آنها را به ارث ببرد. این منجر به کد سادهتر و کمتر همراه میشود که درک و نگهداری آن آسانتر خواهد بود.
نمونه های ارث بری در JDK
کیت توسعه جاوا پر از نمونه های خوب وراثت است:
class IndexOutOfBoundsException extends RuntimeException {...}
class ArrayIndexOutOfBoundsException extends IndexOutOfBoundsException {...}
class FileWriter extends OutputStreamWriter {...}
class OutputStreamWriter extends Writer {...}
interface Stream<T> extends BaseStream<T, Stream<T>> {...}
توجه کنید که در هر یک از این مثالها، کلاس فرزند یک نسخه تخصصی از والد آن است. برای مثال، IndexOutOfBoundsException
نوعی RuntimeException
است.
رویکردن روش با وراثت جاوا
وراثت به ما امکان می دهد تا از متدها و سایر ویژگی های یک کلاس در یک کلاس جدید استفاده مجدد کنیم که بسیار راحت است. اما برای اینکه وراثت واقعا کار کند، ما همچنین باید بتوانیم برخی از رفتارهای ارثی را در زیر کلاس جدید خود تغییر دهیم. برای مثال، ممکن است بخواهیم صدایی را که Cat
تولید می کند، تخصصی کنیم:
class Animal {
void emitSound() {
System.out.println("The animal emitted a sound");
}
}
class Cat extends Animal {
@Override
void emitSound() {
System.out.println("Meow");
}
}
class Dog extends Animal {
}
public class Main {
public static void main(String... doYourBest) {
Animal cat = new Cat(); // Meow
Animal dog = new Dog(); // The animal emitted a sound
Animal animal = new Animal(); // The animal emitted a sound
cat.emitSound();
dog.emitSound();
animal.emitSound();
}
}
این نمونه ای از وراثت جاوا با نادیده گرفتن متد است. ابتدا، کلاس Animal
را توسعه می کنیم تا یک کلاس Cat
جدید ایجاد کنیم. در مرحله بعد، روش emitSound()
کلاس Animal
را لغو میکنیم تا صدای خاصی را که Cat
میسازد به دست آوریم. حتی اگر نوع کلاس را به عنوان Animal
اعلام کردهایم، وقتی آن را به عنوان Cat
نمونهسازی میکنیم، میو گربه را دریافت میکنیم.
برخوردار از روش چندشکلی است
شاید از آخرین مقاله من به یاد داشته باشید که غیر از روش نمونه ای از چندشکلی یا فراخوانی روش مجازی است.
آیا جاوا چندین وراثت دارد؟
برخلاف برخی از زبانها، مانند C++، جاوا اجازه وراثت چندگانه با کلاسها را نمیدهد. با این حال، می توانید از ارث بری چندگانه با رابط ها استفاده کنید. تفاوت بین یک کلاس و یک رابط، در این مورد، این است که رابط ها حالت را حفظ نمی کنند.
اگر سعی کنید چندین وراثت را مانند من در زیر انجام دهید، کد کامپایل نمی شود:
class Animal {}
class Mammal {}
class Dog extends Animal, Mammal {}
یک راه حل با استفاده از کلاس ها، ارث بردن یک به یک است:
class Animal {}
class Mammal extends Animal {}
class Dog extends Mammal {}
یک راه حل دیگر جایگزینی کلاس ها با رابط ها است:
interface Animal {}
interface Mammal {}
class Dog implements Animal, Mammal {}
استفاده از “super” برای دسترسی به روشهای کلاسهای والد
وقتی دو کلاس از طریق وراثت به هم مرتبط هستند، کلاس فرزند باید بتواند به هر فیلد، متد یا سازنده کلاس والد قابل دسترسی دسترسی داشته باشد. در جاوا، ما از کلمه رزرو شده super
استفاده میکنیم تا اطمینان حاصل کنیم که کلاس فرزند همچنان میتواند به روش لغو شده والد خود دسترسی داشته باشد:
public class SuperWordExample {
class Character {
Character() {
System.out.println("A Character has been created");
}
void move() {
System.out.println("Character walking...");
}
}
class Moe extends Character {
Moe() {
super();
}
void giveBeer() {
super.move();
System.out.println("Give beer");
}
}
}
در این مثال، Character
کلاس والد Moe است. با استفاده از super
، میتوانیم به روش move()
Character
دسترسی پیدا کنیم تا به Moe آبجو بدهیم.
استفاده از سازنده با ارث بری
وقتی یک کلاس از کلاس دیگر ارث می برد، سازنده superclass همیشه قبل از بارگیری زیر کلاس آن، ابتدا بارگذاری می شود. در بیشتر موارد، کلمه رزرو شده super
به طور خودکار به سازنده اضافه می شود. با این حال، اگر سوپرکلاس یک پارامتر در سازنده خود داشته باشد، باید به طور عمدی سازنده super
را فراخوانی کنیم، همانطور که در زیر نشان داده شده است:
public class ConstructorSuper {
class Character {
Character() {
System.out.println("The super constructor was invoked");
}
}
class Barney extends Character {
// No need to declare the constructor or to invoke the super constructor
// The JVM will to that
}
}
اگر کلاس والد سازنده ای با حداقل یک پارامتر داشته باشد، باید سازنده را در زیر کلاس اعلام کنیم و از super
برای فراخوانی صریح سازنده والد استفاده کنیم. کلمه رزرو شده super
به طور خودکار اضافه نمی شود و کد بدون آن کامپایل نمی شود. به عنوان مثال:
public class CustomizedConstructorSuper {
class Character {
Character(String name) {
System.out.println(name + "was invoked");
}
}
class Barney extends Character {
// We will have compilation error if we don't invoke the constructor explicitly
// We need to add it
Barney() {
super("Barney Gumble");
}
}
}
casting و ClassCastException را تایپ کنید
Casting راهی برای برقراری ارتباط صریح با کامپایلر است که شما واقعاً قصد دارید یک نوع مشخص را تبدیل کنید. مثل این است که بگویید: “هی، جی وی ام، من می دانم دارم چه کار می کنم، پس لطفاً این کلاس را با این تیپ انتخاب کنید.” اگر کلاسی که شما ارسال کرده اید با نوع کلاسی که اعلام کرده اید سازگار نباشد، یک ClassCastException
دریافت خواهید کرد.
در وراثت، میتوانیم کلاس فرزند را بدون ارسال محتوا به کلاس والد اختصاص دهیم، اما نمیتوانیم بدون استفاده از ارسال محتوا، کلاس والد را به کلاس فرزند اختصاص دهیم.
مثال زیر را در نظر بگیرید:
public class CastingExample {
public static void main(String... castingExample) {
Animal animal = new Animal();
Dog dogAnimal = (Dog) animal; // We will get ClassCastException
Dog dog = new Dog();
Animal dogWithAnimalType = new Dog();
Dog specificDog = (Dog) dogWithAnimalType;
specificDog.bark();
Animal anotherDog = dog; // It's fine here, no need for casting
System.out.println(((Dog)anotherDog)); // This is another way to cast the object
}
}
class Animal { }
class Dog extends Animal { void bark() { System.out.println("Au au"); } }
وقتی میخواهیم یک نمونه حیوان
را به سگ
ارسال کنیم، یک استثنا دریافت میکنیم. این به این دلیل است که حیوان
چیزی در مورد فرزند خود نمی داند. این می تواند یک گربه، یک پرنده، یک مارمولک و غیره باشد. هیچ اطلاعاتی در مورد حیوان خاص وجود ندارد.
مشکل در این مورد این است که Animal
را به این صورت نمونهسازی کردهایم:
Animal animal = new Animal();
سپس سعی کرد آن را به این شکل پخش کند:
Dog dogAnimal = (Dog) animal;
از آنجایی که ما نمونه سگ
نداریم، نمیتوان حیوان
را به سگ
اختصاص داد. اگر تلاش کنیم، یک ClassCastException
دریافت خواهیم کرد.
برای جلوگیری از استثناء، باید سگ
را به این صورت نمونه سازی کنیم:
Dog dog = new Dog();
سپس آن را به حیوان
اختصاص دهید:
Animal anotherDog = dog;
در این مورد، به دلیل اینکه کلاس Animal
را گسترش دادهایم، نمونه Dog
حتی نیازی به پخش کردن ندارد. نوع کلاس والد Animal
به سادگی تخصیص را می پذیرد.
ریختهگری با سوپرتایپها
میتوان سگ
را با سوپرتایپ حیوان
اعلام کرد، اما اگر بخواهیم روش خاصی را از Dog
فراخوانی کنیم، به آن نیاز داریم. برای ریختن آن به عنوان مثال، اگر بخواهیم متد bark()
را فراخوانی کنیم، چه؟ سوپرتایپ Animal
هیچ راهی برای دانستن اینکه دقیقاً چه نمونه حیوانی را فرا میخوانیم ندارد، بنابراین قبل از اینکه بتوانیم bark()
bark را فراخوانی کنیم، باید
Dog
را به صورت دستی ارسال کنیم. روش /code>:
Animal dogWithAnimalType = new Dog();
Dog specificDog = (Dog) dogWithAnimalType;
specificDog.bark();
همچنین میتوانید از Casting بدون اختصاص دادن شی به یک نوع کلاس استفاده کنید. این روش زمانی مفید است که نمی خواهید متغیر دیگری را اعلام کنید:
System.out.println(((Dog)anotherDog)); // This is another way to cast the object
چالش وراثت جاوا را قبول کنید!
شما مفاهیم مهم وراثت را یاد گرفته اید، بنابراین اکنون وقت آن است که یک چالش وراثت را امتحان کنید. برای شروع، کد زیر را مطالعه کنید:
پست های مرتبط
وراثت جاوا در مقابل ترکیب: نحوه انتخاب
وراثت جاوا در مقابل ترکیب: نحوه انتخاب
وراثت جاوا در مقابل ترکیب: نحوه انتخاب