وراثت (inheritance) در جاوا

وراثت (inheritance) در جاوا

تعریف وراثت

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

گاهی اوقات نیاز داریم متد ها و فیلد های یک کلاس رو داخل کلاس دیگه پیاده‌سازی کنیم به‌جای اینکه متد و فیلد ها رو دوباره بنویسیم از کلاسی که مایحتاجمونو داره ارث بری میکنیم.

میتونیم یک کلاس از روی کلاس دیگه تعریف کنیم، در این صورت کلاس جدید علاوه بر دیتافیلد و متد های کلاس قبلی، دیتافیلد و متد های اختصاصی خودش هم میتونه داشته باشه؛ به این کار وراثت یا ارث بری (inheritance) در جاوا میگیم.

به کلاسی که دیتافیلد و متد های کلاس دیگه رو ارث می بره ساب کلاس (subclass) و به کلاسی که ازش میخوایم ارث بری کنیم، سوپرکلاس (superclass) میگیم.

به کمک وراثت کلاس های دیگه میتونن دیتافیلد و متدهای سوپرکلاس رو به ارث ببرن.

به عنوان مثال میدونیم Rectangle و Circle هردو یک شکل هندسی هستند و شکل های هندسی یک سری ويژگی های مشترک بینشون وجود داره (مثل مساحت و محیط) بنابر این میتونیم یک کلاس عمومی به اسم GeometricShape براشون تعریف کنیم.

ارث بری دو کلاس Circle و Rectangle از GeometricShape در جاوا
ارث بری دو کلاس Circle و Rectangle از GeometricShape

در تصویر بالا GeometricShape سوپرکلاس (superclass) است، Rectangle و Circle ساب کلاس (subclass) های GeometricShape هستند.

نحوه ی ارث بری از یک کلاس

با کلید واژه ی extends در جاوا میتونیم از یک کلاس ارث بری کنیم.

اگه بخوایم دیتافیلد و متد های یک کلاس رو به ارث ببریم باید از کلیدواژه ی extends در جاوا استفاده کنیم.

فرم کلی:

public class Parent { ... } public class Child extends Parent{ ... }

توجه

هر کلاسی بیشتر از یک کلاس نمیتونه به طور مستقیم ارث بری کنه.

یک کلاس میتونه به صورت غیر مستقیم از کلاس های دیگه ارث بری داشته باشه در زیر کلاس Child به طور غیر مستقیم از GrandParent ارث بری کرده.

public class GrandParent{ ... } class Parent extends GrandParent{ ... } class Child extends Parent{ ... }

هنگام ایجاد آبجکت از ساب کلاس ابتدا فیلد های سوپر کلاس مقداردهی میشن و سپس فیلد های ساب کلاس؛ به این موضوع با جزئیات بیشتر در ادامه و در بخش ویژگی های کانستراکتور خواهیم پرداخت.

class Parent { Parent(){ System.out.println("Initializing Parent"); } } class Child extends Parent{ Child(){ System.out.println("Initializing Child"); } }
public static void main(String[] args){ Child child = new Child(); }

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

class Parent { public void printParentMessage(){ System.out.println("A message from parent"); } } class Child extends Parent{ public void printChildMessage(){ System.out.println("A message from child"); } }
public static void main(String[] args){ Child child = new Child(); child.printParentMessage(); child.printChildMessage(); }

توجه:

با اینکه تمام دیتافیلد و متدهای سوپرکلاس در ساب کلاس به ارث میرسن، ولی اگه اعضای سوپر کلاس محدودیت دسترسی داشته باشند در ساب کلاس قابل صدا زدن نیستند؛ مثلا اگه متد یا فیلدی در سوپرکلاس private باشه دیگه در ساب کلاس نمیتونیم صداش بزنیم هرچند به ساب کلاس منتقل شده است.

class Parent { //بخاطر سطح دسترسی که بهش دادیم، در ساب کلاس نمیتونیم صداش بزنیم private void printParentMessage(){ System.out.println("A message from parent"); } } class Child extends Parent{ public void printChildMessage(){ System.out.println("A message from child"); } }

بررسی کانستراکتور ها در وراثت

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

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

۱- صدا زدن مستقیم کانستراکتور سوپر کلاس:

با استفاده از کلیدواژه ی super میتونیم یکی از کانستراکتور های سوپر کلاس رو داخل کانستراکتور ساب کلاس صدا بزنیم.

//اگه کانستراکتور در سوپر کلاس پارامتر داشت، پارامتر ها رو داخل پرانتز کلیدواژه پاس میدیم. super(p0, p1, ..., pn); //اگه کانستراکتور در سوپرکلاس بدون پارامتر بود. super();

2- صدا زدن غیرمستقیم کانستراکتور های سوپر کلاس در ساب کلاس:

با کلیدواژه ی this میتونیم کانستراکتوری که در ساب کلاس از super استفاده میکنه رو صدا بزنیم.

class Child extends Parent{ Child(...){ super(...); } Child(...){ this(...); } }

توجه:

تنها در حالتی که سوپرکلاس کانستراکتور بدون پارامتر وجود داشته باشه میتونیم برای کانستراکتور های ساب کلاس از super استفاده نکنیم، در این وضعیت کامپایلر به طور خودکار ‎super() رو قبل از هر کدی در کانستراکتور هایی که از super استفاده نکردیم قرار میده.

توجه:

کلیدواژه های super یا this باید داخل کانستراکتوری که داریم تعریف میکنیم قبل از هر کدی قرار بگیرند.

مثال

class GeometricShape{ private String color; private boolean filled; GeometricShape(String color, boolean filled){ this.color = color; this.filled = filled; } } class Circle extends GeometricShape{ private double radius; Circle(double radius, String color, filled){ super(color , filled); this.radius = radius; } Circle(){ this(1.0, "White" , false); } }

زنجیره ی کانستراکتور ها

میدونیم که با صدا زدن کانستراکتور یک نمونه از کلاس تولید میکنیم؛ هنگام صدا زدن یکی از کانستراکتور های ساب کلاس برای تولید یک نمونه از ساب کلاس، ابتدا کانستراکتور سوپر کلاس صدا زده میشه و اگه خود سوپرکلاس، ساب کلاس یک سوپر کلاس دیگه باشه قبل از اجرای کد های کانستراکتور سوپرکلاس، کانستراکتور قبلی صدا زده میشه و به همین ترتیب تا به آخرین سوپرکلاس برسیم؛ سپس کد های داخل کانستراکتور اخرین سوپر کلاس اجرا میشن و به همین ترتیب تا برسه به کلاسی که ساب کلاس ماست. به این موضوع زنجیره ی کانستراکتور ها میگیم.

مثال:

public class Faculty extends Employee { public static void main(String[] args) { new Faculty(); } public Faculty() { System.out.println("(4) Performs Faculty's tasks"); } } class Employee extends Person { public Employee() { this("(2) Invokes Employee's overloaded constructor"); System.out.println("(3) Performs Employee's tasks "); } public Employee(String s) { System.out.println(s); } } class Person { public Person() { System.out.println("(1) Performs Person's tasks"); } }

در زیر Person سوپرکلاس همه ی کلاس هاست ابتدا کانستراکتور Person اجرا میشه و چون در Employee با this کانستراکتور پارامتر دارشو داخل کانستراکتور بدون پارامترش صدا زدیم کانستراکتور پارامتر دار اجرا میشه و سپس بقیه کد های کنستراکتور بدون پارامترش و در نهایت کد های داخل کانستراکتور Faculty اجرا میشن.

مفهوم باز نویسی (Overriding)

بازنویسی متد به شما این امکان را می دهد تا کدهای داخل متد در سوپر کلاس رو تغییر بدید.

میتونیم متد های سوپر کلاس رو داخل ساب کلاس بازنویسی کنیم. برای این کار کافیه تا متد با اسم و پارامتر هایی که در سوپرکلاس تعریف شده رو در ساب کلاس دوباره بنویسیم. به این کار باز نویسی (Override کردن) متد میگن.

public class Main{ public static void main(String[] args){ Child child = new Child(); child.printMessage("First"); } } class Parent{ public void printMessage(String s){ System.out.println(s + " message from parent"); } } class Child extends Parent{ //متد باز نویسی شده public void printMessage(String s){ System.out.println(s + " message from child"); } }

شاید در برنامه خیلی مشخص نباشه که متد بازنویسی شده برای همین به لحاظ خوانایی کد میتونیم قبل از بازنویسی متد از برچسب ‎@Override استفاده کنیم.

public class Main{ public static void main(String[] args){ Child child = new Child(); child.printMessage("First"); } } class Parent{ public void printMessage(String s){ System.out.println(s + " message from parent"); } } class Child extends Parent{ @Override public void printMessage(String s){ System.out.println(s + " message from child"); } }

استفاده از کلیدواژه ی super داخل متد های بازنویسی شده

هنگام بازنویسی متد، میتونیم با کلیدواژه ی super کدهای متد در سوپرکلاس رو صدا بزنیم.

public class Main{ public static void main(String[] args){ Child child = new Child(); child.printMessage("First"); } } class Parent{ public void printMessage(String s){ System.out.println(s + " message from parent"); } } class Child extends Parent{ @Override public void printMessage(String s){ super.printMessage("First"); System.out.println(s + " message from child"); } }

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

public class Main{ public static void main(String[] args){ Child child = new Child(); child.printMessage("First"); } } class Parent{ public void printMessage(String s){ System.out.println(s + " message from parent"); } } class Child extends Parent{ @Override public void printMessage(String s){ super.printMessage("First"); System.out.println(s + " message from child"); super.printMessage("Second"); } }

کلاس Object

کلاس Object سوپرکلاس مستقیم یا غیر مستقیم تمام کلاس های جاواست.

هنگامی که یک کلاس تعریف میکنیم کلاسمون به طور پیشفرض از Object ارث بری میکنه.

داخل کلاس Object متد های hashCode، toString و equals تعریف شده که این متد ها در کلاس های مختلف بنابر نیاز توسعه دهنده بازنویسی میشن.

مثال

public class Point2D { private double x, y; public Point2D(double x, double y){ this.x = x; this.y = y; } public double getX() { return x; } public void setX(double x) { this.x = x; } public double getY() { return y; } public void setY(double y) { this.y = y; } @Override public String toString() { return "[x= " + x + ", y= " + y + "]"; } @Override public boolean equals(Object obj) { if (obj instanceof Point2D) return ((Point2D) obj).x == this.x && ((Point2D) obj).y == this.y; return false; } }
public static void main(String[] args){ Point2D p0 = new Point2D(3, 5); Point2D p1 = new Point2D(6, 7); Point2D p2 = new Point2D(3,5); System.out.println("p0: " + p0.toString()); System.out.println("p1:" + p1.toString()); System.out.println("p2: " + p2.toString()); System.out.println("Are p0 and p1 equal? " + p0.equals(p1)); System.out.println("Are p0 and p2 equal? " + p0.equals(p2)); }

مورد مطالعه (مثال ها)

در زیر کلاس های Circle و Rectangle از کلاس GeometricShape ارث برده اند.

public class GeometricShape { private final Date dateCreated = new Date(); private String color; private boolean filled; public GeometricShape(String color, boolean filled){ this.color = color; this.filled = filled; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public Date getDateCreated() { return dateCreated; } public boolean isFilled() { return filled; } public void setFilled(boolean filled) { this.filled = filled; } } class Circle extends GeometricShape{ private double radius; public Circle(double radius, String color, boolean filled){ super(color, filled); this.radius = radius; } public Circle(double radius){ this(radius, "white", false); } public double getRadius() { return radius; } public void setRadius(double radius) { this.radius = radius; } public double getArea(){ return Math.PI * radius * radius; } public double getPerimeter(){ return 2 * Math.PI * radius; } @Override public String toString() { return "Circle: created on " + getDateCreated().toString() + "\n" + "radius: " + getRadius() + " area: " + getArea() + " perimeter " + getPerimeter(); } } class Rectangle extends GeometricShape{ private double width; private double height; public Rectangle(double width , double height, String color, boolean filled){ super(color, filled); this.width = width; this.height = height; } public Rectangle(){ this(1.0, 1.0, "White", false); } public double getWidth() { return width; } public void setWidth(double width) { this.width = width; } public double getHeight() { return height; } public void setHeight(double height) { this.height = height; } public double getArea(){ return width * height; } public double getPerimeter(){ return (width + height) * 2; } @Override public String toString() { return "Rectangle: created on " + getDateCreated().toString() + "\n" + "width: " + getWidth() + " height: " + getHeight() + " area: " + getArea() + " perimeter: " + getPerimeter(); } }

استفاده از کلاس های Rectangle و Circle.

public static void main(String[] args){ Rectangle rectangle = new Rectangle(2,4, "Red", true); System.out.println(rectangle.toString()); System.out.println(); Circle circle = new Circle(4, "Blue", true); System.out.println(circle.toString()); }

خلاصه

- از ارث بری برای انتقال فیلد ها و متد ها از یک کلاس به کلاس دیگه بدون دوباره نویسی اونا استفاده میکنیم.

- به کلاسی که ازش ارث بری میکنیم سوپرکلاس و به کلاسی که ارث میبره ساب کلاس میگیم.

- در جاوا فقط از یک کلاس میتونیم ارث بری کنیم.

- با کلیدواژه ی extends از یک کلاس ارث بری میکنیم.

- در ارث بری کانستراکتور های سوپرکلاس به ساب کلاس منتقل نمیشن.

- هنگام ارث بری هر کانستراکتور در ساب کلاس باید حداقل یکی از کانستراکتور های سوپرکلاس رو داخل خودش صدا بزنه.

- برای صدا زدن کانستراکتور های سوپرکلاس در ساب کلاس دو راه وجود داره: ۱- مستقیم با استفاده از کلیدواژه ی super؛ ۲- صدا زدن غیر مستقیم یکی از کانستراکتور های داخل کلاس که یکی از کانستراکتور های سوپر کلاس رو صدا زده باشه.

- اگه یکی از کانستراکتور های سوپرکلاس بدون پارامتر باشه نیاز به صدا زدنش نیست و کامپایلر به طور خودکار کانستراکتور بدون پارامتر رو برای کانستراکتور هایی که در ساب کلاس از super استفاده نکردیم در نظر میگیره.

- کلیدواژه ی super در کانستراکتور ها باید قبل از هر کد دیگه ای در کانستراکتور تعریف بشه.

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

- میتونیم یک متد از سوپرکلاس رو در ساب کلاس بازنویسی کنیم. و همینطور با کلیدواژه ی super کد های داخل متد در سوپرکلاس رو صدا بزنیم.

- کلاس Object سوپرکلاس مستقیم یا غیر مستقیم تمام کلاس هاست.

- در کلاس آبجکت سه متد hashCode، toString و equals تعریف شده که میتونیم به طور اختصاصی در هر کلاسی که تعریف میکنیم بازنویسیشون کنیم.

arrow_drop_up
کپی شد!