Chương 4: Kế thừa (Inheritance) và đa hình (Polymorphism)

0

4.1. Thừa kế (inheritance)

4.1.1. Lớp kế thừa

Một lớp con (subclass) có thể kế thừa tất cả những vùng dữ liệu và phương thức của một lớp khác –lớp cha (siêu lớp – superclass).

Như vậy việc tạo một lớp mới từ một lớp đã biết sao cho các thành phần (fields và methods) của lớp cũ cũng sẽ thành các thành phần (fields và methods) của lớp mới. Khi đó ta gọi lớp mới là lớp dẫn xuất (derived class) từ lớp cũ (superclass). Có thể lớp cũ cũng là lớp được dẫn xuất từ một lớp nào đấy, nhưng đối với lớp mới vừa tạo thì lớp cũ đó là một lớp siêu lớp trực tiếp (immediate supperclass).

Dùng từ khóa extends để chỉ lớp dẫn xuất.

Ví dụ:

// super class B

class B{

            // …

}

// sub class A

class A extends B

{

//…

}

4.1.2. Khái báo phương thức chồng

Tính kế thừa giúp cho các lớp con nhận được các thuộc tính/phương thức public và protected của lớp cha. Đồng thời cũng có thể thay thế các phương thức của lớp cha bằng cách khai báo chồng. Chẳng hạn phương thức tinhgiaban() áp dụng trong lớp xega sẽ cho kết quả gấp 2.5 lần chi phí sản xuất thay vì gấp 2 chi phí sản xuất giống như trong lớp xemay.

Ví dụ:

public class xega extends xemay

{

public xega()

{ … }

 

public xega(String s_nhasx, String s_model, f_chiphisx, int i_thoigiansx);

{

this.nhasx = s_nhasx;

this.model = s_model;

this.chiphisx = f_chiphisx;

this.thoigiansx = i_thoigiansx;

this.so = 0;

}

public float tinhgiaban()

{

return 2.5 * chiphisx;

}

}

Java cung cấp 3 tiền tố/từ khóa để hỗ trợ tính kế thừa của lớp:

  • public: lớp có thể truy cập từ các gói, chương trình khác.
  • final: Lớp hằng, lớp không thể tạo dẫn xuất (không thể có con), hay đôi khi người ta gọi là lớp “vô sinh”.
  • abstract: Lớp trừu tượng (không có khai báo các thành phần và các phương thức trong lớp trừu tượng). Lớp dẫn xuất sẽ khai báo, cài đặt cụ thể các thuộc tính, phương thức của lớp trừu tượng.

Chú ý: Trong Java không cho phép đa kế thừa, nghĩa là 1 lớp con chỉ kế thừa duy nhất từ 1 lớp cha (khác với C++), ngược lại 1 lớp cha có thể cho phép nhiều lớp con kế thừa.

4.2. Đa hình (polymorphism)

Tính đa hình cho phép cài đặt các lớp dẫn xuất khác nhau từ một lớp nguồn. Một đối tượng có thể có nhiều kiểu khác nhau gọi là tính đa hình.

Ví dụ:

class A_Object

{

//…

void method_1()

{

//…

}

}

class B_Object extends A_Object

{

//…

void method_1()

{

//…

}

}

class C

{

public static void main(String[] args)

{

// To mt mng 2 phn tkiu A

A_Object arr_Object = new A_Object[2];

B_Object var_1 = new B_Object();

// Phn tử đầu tiên ca mng arr_Object[0]tham

// chiếu đến 1 đối tượng kiu B_Object dnxut

// tA_Object

arr_Object[0] = var_1;

A_Object var_2;

for (int i=0; i<2; i++) {

var_2 = arr_Object[i];

var_2.method_1();

}

}

}

Vòng lặp for trong đoạn chương trình trên:

– Với i = 0 thì biến var_2 có kiểu là B_Object, và lệnh var_2.method_1() sẽ gọi thực hiện phương thức method_1 của lớp B_Object.

– Với i = 1 thì biến var_2 có kiểu là A_Object, và lệnh var_2.method_1() sẽ gọi thực hiện phương thức method_1 của lớp A_Object.

Trong ví dụ trên đối tượng var_2 có thể nhận kiểu A_Object hay B_Object. Hay nói các khác, một biến đối tượng kiểu A_Object như var_2 trong ví dụ trên có thể tham chiếu đến bất kỳ đối tượng nào của bất kỳ lớp con nào của lớp A_Object (ví dụ var_2 có thể tham chiếu đến đối tượng var_1, var_1 là đối tượng của lớp B_Object dẫn xuất từ lớp A_Object). Ngược lại một biến của lớp con không thể tham chiếu đến bất kỳ đối tượng nào của lớp cha.

4.3. Phương thức từu tượng và lớp trừu tượng

Lớp trừu tượng (abtract class) là lớp không có khai báo các thuộc tính thành phần và các phương thức. Các lớp dẫn xuất của nó sẽ khai báo thuộc tính, cài đặt cụ thể các phương thức của lớp trừu tượng.

Phương thức trừu tượng (abstract method) là phương thức không có nội dung, chỉ được khai báo trong lớp trừu tượng.

Ví dụ:

abstract class A

{

abstract void method_1(); // phương thức trừu tượng

}

public class B extends A

{

public void method_1()

{

// cài đặt chi tiết cho phương thc method_1

// trong lp con B.

//…

}

}

public class C extends A

{

public void method_1()

{

// cài đặt chi tiết cho phương thc method_1

// trong lp con C.

//…

}

}

Lưu ý: Các phương thức được khai báo dùng các tiền tố private static thì không được khai báo là trừu tượng abstract. Tiền tố private thì không thể truy xuất từ các lớp dẫn xuất, còn tiền tố static thì chỉ dùng riêng cho lớp khai báo mà thôi.

4.4. Giao diện (interface)

4.3.1.Khái niệm interface

Như chúng ta đã biết một lớp trong java chỉ có một siêu lớp trực tiếp hay một cha duy nhất (đơn thừa kế). Để tránh đi tính phức tạp của đa thừa kế (multi-inheritance) trong lập trình hướng đối tượng, Java thay thế bằng giao tiếp (interface). Một lớp có thể có nhiều giao tiếp (interface) với các lớp khác để thừa hưởng thêm vùng dữ liệu và phương thức của các giao tiếp này.

 4.3.2.Khai báo interface

Interface được khai báo như một lớp. Nhưng các thuộc tính của interface là các hằng (khai báo dùng từ khóa final) và các phương thức của giao tiếp là trừu tượng (mặc dù không có từ khóa abstract).

Trong các lớp có cài đặt các interface ta phải tiến hành cài đặt cụ thể các phương thức này.

Ví dụ:

public interface sanpham

{

static final String nhasx = “Honda VN”;

static final String dienthoai = “08-8123456”;

public int gia(String s_model);

}

// khai báo 1 lp có cài đặt interface

public class xemay implements sanpham

{

// cài đặt li phương thc ca giao din trong lp

public int gia(String s_model)

{

if (s_model.equals(“2005”))

return (2000);

else

return (1500);

}

public String chobietnhasx()

{

return (nhasx);

}

}

Có một vấn đề khác với lớp là một giao diện (interface) không chỉ có một giao diện cha trực tiếp mà có thể dẫn xuất cùng lúc nhiều giao diện khác (hay có nhiều giao diện cha). Khi đó nó sẽ kế thừa tất cả các giá trị hằng và các phương thức của các giao diện cha. Các giao diện cha được liệt kê thành chuỗi và cách nhau bởi dấu phẩy “,”. Khai báo như sau:

public interface InterfaceName extends interface1, interface2, interface3

{

//…

}

4.5. Lớp nội

Lớp nội là lớp được khai báo bên trong 1 lớp khác. Lớp nội thể hiện tính đóng gói cao và có thể truy xuất trực tiếp biến của lớp cha.

Ví dụ:

public class A

{

//…

int <field_1>

static class B

{

//…

int <field_2>

public B(int par_1)

{

field_2 = par_1 + field_1;

}

}

}

Trong ví dụ trên thì chương trình dịch sẽ tạo ra hai lớp với hai files khác nhau: A.class và B.class

4.6. Lớp vô sinh

Lớp không thể có lớp dẫn xuất từ nó (không có lớp con) gọi là lớp “vô sinh”, hay nói cách khác không thể kế thừa được từ một lớp “vô sinh”. Lớp “vô sinh” dùng để hạn chế, ngăn ngừa các lớp khác dẫn xuất từ nó.

Để khai báo một lớp là lớp “vô sinh”, chúng ta dùng từ khóa final class. Tất cả các phương thức của lớp vô sinh đều vô sinh, nhưng các thuộc tính của lớp vô sinh thì có thể không vô sinh.

Ví dụ:

public final class A

{

public final int x;

private int y;

public final void method_1()

{

//…

}

public final void method_2()

{

//…

}

}

 

4.7. Ví dụ minh họa

Ví dụ: Minh họa tính đa hình (polymorphism) trong phân cấp kế thừa thông qua việc mô tả và xử lý một số thao tác cơ bản trên các đối tượng hình học.

// Định nghĩa lp tru tượng cơ stên Shape trong

// tp tin Shape.java

public abstract class Shape extends Object

{

// trả về diện tích của một đối tượng hình học shape public double area()

{

return 0.0;

}

// trả về thể tích của một đối tượng hình học shape

public double volume()

{

return 0.0;

}

/* Phương thức trừu tượng cần phải được hiện thực trong những lớp con để trả về tên đối tượng hình học shape thích hợp*/

public abstract String getName();

}

// end class Shape

 

// Định nghĩa lớp Point trong tập tin Point.java

public class Point extends Shape

{

protected int x, y; // Tọa độ x, y của 1 điểm

// constructor không tham số.

public Point()

{

setPoint( 0, 0 );

}

// constructor có tham số.

public Point(int xCoordinate, int yCoordinate)

{

setPoint( xCoordinate, yCoordinate );

}

// gán tọa độ x, y cho 1 điểm

public void setPoint( int xCoordinate, int yCoordinate )

{

x = xCoordinate;

y = yCoordinate;

}

// lấy tọa độ x của 1 điểm

public int getX()

{

return x;

}

 

/* Phương thức trừu tượng cần phải được hiện thực trong những lớp con để trả về tên đối tượng hình học shape thích hợp*/

public abstract String getName();

} // end class Shape

 

// Định nghĩa lớp Point trong tập tin Point.java

public class Point extends Shape

{

protected int x, y; // Tọa độ x, y của 1 điểm

// constructor không tham số.

public Point()

{

setPoint( 0, 0 );

}

// constructor có tham số.

public Point(int xCoordinate, int yCoordinate)

{

setPoint( xCoordinate, yCoordinate );

}

// gán tọa độ x, y cho 1 điểm

public void setPoint( int xCoordinate, int yCoordinate )

{

x = xCoordinate;

y = yCoordinate;

}

// lấy tọa độ x của 1 điểm

public int getX()

{

return x;

}

 

// lấy tọa độ y của 1 điểm

public int getY()

{

return y;

}         

// Thể hiện tọa độ của 1 điểm dưới dạng chuỗi

public String toString()

{

return “[” + x + “, ” + y + “]”;

}

// trả về tên của đối tượng shape

public String getName()

{

return “Point”;

}

} // end class Point

Định nghĩa một lớp cha Shape là một lớp trừu tượng dẫn xuất từ Object và có 3 phương thức khai báo dùng tiền tố public. Phương thức getName() khai báo trừu tượng vì vậy nó phải được hiện thực trong các lớp con. Phương thức area() (tính diện tích) và phương thức volume() (tính thể tích) được định nghĩa và trả về 0.0. Những phương thức này sẽ được khai báo chồng trong các lớp con để thực hiện chức năng tính diện tích cũng như thể tích phù hợp với những đối tượng hình học tương ứng (đường tròn, hình trụ, …)

Lớp Point: dẫn xuất từ lớp Shape. Một điểm thì có diện tích và thể tích là 0.0, vì vậy những phương thức area() và volume() của lớp cha không cần khai báo chồng trong lớp Point, chúng được thừa kế như đã định nghĩa trong lớp trừu tượng Shape. Những phương thức khác như setPoint(…) để gán tọa độ x, y cho một điểm, còn phương thức getX(), getY() trả về tọa độ x, y của một điểm. Phương thức getName() là hiện thực cho phương thức trừu tượng trong lớp cha, nếu như phương thức getName() mà không được định nghĩa thì lớp Point là một lớp trừu tượng.

// Định nghĩa lớp Circle trong tập tin Circle.java

public class Circle extends Point

{

// Dẫn xuất từ lớpPoint

protected double radius;

// constructor không tham số

public Circle()

{

// ngầm gọi đến constructor của lớp cha

setRadius( 0 );

}

// constructor có tham số

public Circle( double circleRadius, int xCoordinate,int yCoordinate )

{

// gọi constructorcủa lớp cha

super( xCoordinate, yCoordinate );

setRadius( circleRadius );

}

// Gán bán kính của đường tròn

public void setRadius( double circleRadius )

{

radius = ( circleRadius >= 0 ? circleRadius:0 );

}

// Lấy bán kính của đường tròn

public double getRadius()

{

return radius;

}

// Tính diện tích đường tròn Circle

public double area()

{

return Math.PI * radius * radius;

}

// Biểu diễn đường tròn bằng một chuỗi

public String toString()

{

return “Center = ” + super.toString() +”; Radius = ” + radius;

}

// trả về tên của shape

public String getName()

{

return “Circle”;

}

} // end class Circle

Lớp Circle dẫn xuất từ lớp Point, một đường tròn có thể tích là 0.0, vì vậy phương thức volume() của lớp cha không khai báo chồng, nó sẽ thừa kế từ lớp Point, mà lớp Point thì thừa kế từ lớp Shape. Diện tích đường tròn khác với một điểm, vì vậy phương thức tính diện tích area() được khai báo chồng. Phương thức getName() hiện thực phương thức trừu tượng đã khai báo trong lớp cha, nếu phương thức getName() không khai báo trong lớp Circle thì nó sẽ kế thừa từ lớp Point. Phương thức setRadius dùng để gán một bán kính (radius) mới cho một đối tượng đường tròn, còn phương thức getRadius trả về bán kính của một đối tượng đường tròn.

// Định nghĩa lớp hình trụ Cylinder

// trong tập tin Cylinder.java.

public class Cylinder extends Circle

{

// chiều cao của Cylinder

protected double height;

// constructor không có tham số

public Cylinder()

{

// ngầm gọi đến constructor của lớp cha

setHeight( 0 );

}

// constructor có tham số

public Cylinder(double cylinderHeight, double cylinderRadius, int xCoordinate,int yCoordinate)

{

// Gọi constructor của lớp cha

super( cylinderRadius, xCoordinate, yCoordinate );

setHeight( cylinderHeight );

}

// Gán chiều cao cho Cylinder

public void setHeight( double cylinderHeight )

{

height = ( cylinderHeight >= 0) ? cylinderHeight : 0 ;

// Lấy chiều cao của Cylinder

public double getHeight()

{

return height;

}

// Tính diện tích xung quanh của Cylinder

public double area()

{

return 2 * super.area() + 2 * Math.PI * radius * height;

}

// Tính thể tích của Cylinder

public double volume()

{

return super.area() * height;

}

// Biểu diễn Cylinder bằng một chuỗi

public String toString()

{

return super.toString() + “; Height = ” + height;

}

// trả về tên của shape

public String getName()

{

return “Cylinder”;

}

} // end class Cylinder

Lớp Cylinder dẫn xuất từ lớp Circle. Một Cylinder (hình trụ) có diện tích và thể tích khác với một Circle (hình tròn), vì vậy cả hai phương thức area() volume() cần phải khai báo chồng. Phương thức getName() là hiện thực phương thức trừu tượng trong lớp cha, nếu phương thức getName() không khai báo trong lớp Cylinder thì nó sẽ kế thừa từ lớp Circle. Phương thức setHeight dùng để gán chiều cao mới cho một đối tượng hình trụ, còn phương thức getHeight trả về chiều cao của một đối tượng hình trụ.

// Test.java

// Kiểm tra tính kế thừa của Point, Circle, Cylinder với lớp trừu tượng Shape.

// Khai báo thư viện

import java.text.DecimalFormat;

public class Test

{         

// Kiểm tra tính kế thừa của các đối tượng hình học

public static void main( String args[] )

{

// Tạo ra các đối tượng hìnhhọc

Point point = new Point( 7, 11 );

Circle circle = new Circle( 3.5, 22, 8 );

Cylinder cylinder = new Cylinder( 10, 3.3, 10, 10 );

// Tạo một mảng các đối tượng hình học

Shape arrayOfShapes[] = new Shape[ 3 ];

// arrayOfShapes[ 0 ] là một đối tượng Point

arrayOfShapes[ 0 ] = point;

// arrayOfShapes[ 1 ] là một đối tượng Circle

arrayOfShapes[ 1 ] = circle;

// arrayOfShapes[ 2 ] là một đối tượng cylinder

arrayOfShapes[ 2 ] = cylinder;

// Lấy tên và biểu diễn của mỗi đối tượng hình học

String output = point.getName() + “: ” + point.toString() + “\n” +

circle.getName() + “: ” + circle.toString() + “\n” +

cylinder.getName() + “: ” + cylinder.toString();

DecimalFormat precision2 = new DecimalFormat( “0.00” );

// duyệt mảng arrayOfShapes lấy tên, diện tích, thể tích

// của mỗi đối tượng hình học trong mảng.

for ( int i = 0; i < arrayOfShapes.length; i++ )

{

output += “\n\n” + arrayOfShapes[ i ].getName() + “: ” +

arrayOfShapes[ i].toString() + “\n Area = ” +

precision2.format( arrayOfShapes[ i ].area() ) + “\nVolume = ” + precision2.format( arrayOfShapes[ i ].volume() );

}

System.out.println(output);

System.exit( 0 );

}

} // end class Test

Biên soạn: txthanh – apps1pro.com

Share.

About Author

Leave A Reply