Tính đa hình (Polymorphism) trong Java – GP Coder (Lập trình Java)

Tính đa hình ( polymorphism ) là một trong bốn đặc thù cơ bản của lập trình hướng đối tượng người tiêu dùng trong Java .

Tính đa hình là khả năng một đối tượng có thể thực hiện một tác vụ theo nhiều cách khác nhau.

Đối với tính chất này, nó được thể hiện rõ nhất qua việc gọi phương thức của đối tượng. Các phương thức hoàn toàn có thể giống nhau, nhưng việc xử lý luồng có thể khác nhau. Nói cách khác: Tính đa hình cung cấp khả năng cho phép người lập trình gọi trước một phương thức của đối tượng, tuy chưa xác định đối tượng có phương thức muốn gọi hay không. Đến khi thực hiện (run-time), chương trình mới xác định được đối tượng và gọi phương thức tương ứng của đối tượng đó. Kết nối trễ giúp chương trình được uyển chuyển hơn, chỉ yêu cầu đối tượng cung cấp đúng phương thức cần thiết là đủ.

Trong Java, chúng ta sử dụng nạp chồng phương thức (method overloading) và ghi đè phương thức (method overriding) để có tính đa hình.

  • Nạp chồng (Overloading): Đây là khả năng cho phép một lớp có nhiều thuộc tính, phương thức cùng tên nhưng với các tham số khác nhau về loại cũng như về số lượng. Khi được gọi, dựa vào tham số truyền vào, phương thức tương ứng sẽ được thực hiện.
  • Ghi đè (Overriding): là hai phương thức cùng tên, cùng tham số, cùng kiểu trả về nhưng thằng con viết lại và dùng theo cách của nó, và xuất hiện ở lớp cha và tiếp tục xuất hiện ở lớp con. Khi dùng override, lúc thực thi, nếu lớp Con không có phương thức riêng, phương thức của lớp Cha sẽ được gọi, ngược lại nếu có, phương thức của lớp Con được gọi.

Đa hình lúc runtime trong java

Đa hình lúc runtime là quá trình gọi phương thức đã được ghi đè trong thời gian thực thi chương trình. Trong quá trình này, một phương thức được ghi đè được gọi thông qua biến tham chiếu của một lớp cha.

Trước khi tìm hiểu và khám phá về đa hình tại runtime, tất cả chúng ta cùng tìm hiểu và khám phá về Upcasting .

Upcasting là gì ?

Khi biến tham chiếu của lớp cha tham chiếu tới đối tượng người dùng của lớp con, thì đó là Upcasting. Ví dụ :


class A {
} 

class B extends A {
} 

A a = new B(); // upcasting 

Các bạn xem thêm ở bài viết Cơ chế Upcasting và Downcasting trong java để hiểu rõ hơn .

Ví dụ về đa hình tại runtime trong Java

Ví dụ 1 : tất cả chúng ta tạo hai lớp Bike và Splendar. Lớp Splendar thừa kế lớp Bike và ghi đè phương pháp run ( ) của nó. Chúng ta gọi phương pháp run bởi biến tham chiếu của lớp cha. Khi nó tham chiếu tới đối tượng người dùng của lớp con và phương pháp lớp con ghi đè phương pháp của lớp cha, phương pháp lớp con được triệu hồi tại runtime .Khi việc gọi phương pháp được quyết định hành động bởi JVM chứ không phải Compiler, do đó đó là đa hình tại runtime .


public class Bike {
	public void run() {
		System.out.println("running");
	}
}

public class Splender extends Bike {
	public void run() {
		System.out.println("running safely with 60km");
	}

	public static void main(String args[]) {
		Bike b = new Splender(); // upcasting 
		b.run();
	}
}

Kết quả :

running safely with 60km

Ví dụ 2 : Giả sử Bank là một lớp cung ứng phương pháp để lấy lãi suất vay. Nhưng lãi suất vay lại khác nhau giữa từng ngân hàng nhà nước. Ví dụ, những ngân hàng nhà nước VCB, AGR và CTG hoàn toàn có thể phân phối những lãi suất vay lần lượt là 8 %, 7 % và 9 %. ( Ví dụ này cũng có trong chương ghi đè phương pháp nhưng không có Upcasting ) .

class Bank {
	int getRateOfInterest() {
		return 0;
	}
}

class VCB extends Bank {
	int getRateOfInterest() {
		return 8;
	}
}

class AGR extends Bank {
	int getRateOfInterest() {
		return 7;
	}
}

class CTG extends Bank {
	int getRateOfInterest() {
		return 9;
	}
}

class Test3 {
	public static void main(String args[]) {
		Bank b1 = new VCB(); // upcasting 
		Bank b2 = new AGR(); // upcasting 
		Bank b3 = new CTG(); // upcasting 
		System.out.println("VCB lai suat la: " + b1.getRateOfInterest());
		System.out.println("AGR lai suat la: " + b2.getRateOfInterest());
		System.out.println("CTG lai suat la: " + b3.getRateOfInterest());
	}
}

Kết quả :

VCB lai suat la: 8
VCB lai suat la: 7
VCB lai suat la: 9

Ví dụ 3 : Shape

class Shape {
	void draw() {
		System.out.println("drawing...");
	}
}

class Rectangle extends Shape {
	void draw() {
		System.out.println("drawing rectangle...");
	}
}

class Circle extends Shape {
	void draw() {
		System.out.println("drawing circle...");
	}
}

class Triangle extends Shape {
	void draw() {
		System.out.println("drawing triangle...");
	}
}

class TestPolymorphism2 {
	public static void main(String args[]) {
		Shape s;
		s = new Rectangle();
		s.draw();
		s = new Circle();
		s.draw();
		s = new Triangle();
		s.draw();
	}
}

Kết quả :

drawing rectangle...
drawing circle...
drawing triangle...

Đa hình tại runtime trong Java với thành viên tài liệu

Phương thức bị ghi đè không là thành viên tài liệu, vì vậy đa hình tại runtime không hề có được bởi thành viên tài liệu. Trong ví dụ sau đây, cả hai lớp có một thành viên tài liệu là speedlimit, tất cả chúng ta truy vấn thành viên tài liệu bởi biến tham chiếu của lớp cha mà tham chiếu tới đối tượng người tiêu dùng lớp con. Khi tất cả chúng ta truy vấn thành viên tài liệu mà không bị ghi đè, thì nó sẽ luôn luôn truy vấn thành viên tài liệu của lớp cha .

Qui tắc: Đa hình tại runtime không thể có được bởi thành viên dữ liệu.

Ví dụ :

class Bike {
	int speedlimit = 90;
}

class Honda3 extends Bike {
	int speedlimit = 150;

	public static void main(String args[]){  
	  Bike obj=new Honda3();  
	  System.out.println(obj.speedlimit); // 90  
	}
}

Kết quả :

90

Đa hình lúc runtime trong Java với thừa kế nhiều tầng

Ví dụ 1 :

class Animal {
	void eat() {
		System.out.println("eating");
	}
}

class Dog extends Animal {
	void eat() {
		System.out.println("eating fruits");
	}
}

class BabyDog extends Dog {
	void eat() {
		System.out.println("drinking milk");
	}

	public static void main(String args[]) {
		Animal a1, a2, a3;
		a1 = new Animal();
		a2 = new Dog();
		a3 = new BabyDog();
		a1.eat();
		a2.eat();
		a3.eat();
	}
}

Kết quả :

eating
eating fruits
drinking Milk

Ví dụ 2 :

class Animal {
	void eat() {
		System.out.println("animal is eating...");
	}
}

class Dog extends Animal {
	void eat() {
		System.out.println("dog is eating...");
	}
}

class BabyDog1 extends Dog {
	public static void main(String args[]) {
		Animal a = new BabyDog1();
		a.eat();
	}
}

Kết quả :

Dog is eating

Vì BabyDog1 không ghi đè phương pháp eat ( ), nên phương pháp eat ( ) của lớp Dog được gọi .

Nạp chồng phương pháp ( method overloading )

Nếu một lớp có nhiều phương pháp cùng tên nhưng khác nhau về kiểu tài liệu hoặc số lượng những tham số, thì đó là nạp chồng phương pháp ( Method Overloading ) .Sử dụng nạp chồng phương pháp giúp tăng năng lực đọc hiểu chương trình .Nạp chồng phương pháp được sử dụng để thu được tính đa hình lúc biên dịch ( compile ) .Có 2 cách nạp chồng phương pháp trong java

  • Thay đổi số lượng các tham số
  • Thay đổi kiểu dữ liệu của các tham số

Nạp chồng phương pháp : biến hóa số lượng những tham số

Ví dụ : tạo 2 phương pháp có cùng kiểu tài liệu : phương pháp add ( ) tiên phong triển khai việc tính tổng của 2 số, phương pháp thứ hai thực thi việc tính tổng của 3 số .


class Adder {
	static int add(int a, int b) {
		return a + b;
	}

	static int add(int a, int b, int c) {
		return a + b + c;
	}
}

class TestOverloading1 {
	public static void main(String[] args) {
		System.out.println(Adder.add(5, 5));
		System.out.println(Adder.add(5, 5, 5));
	}
}

Kết quả chạy chương trình trên :

10
15

Nạp chồng phương pháp : biến hóa kiểu tài liệu của những tham số

Ví dụ : tạo 2 phương pháp có kiểu tài liệu khác nhau : phương pháp add ( ) tiên phong nhận 2 đối số có kiểu giá trị là integer, phương pháp thứ hai nhận 2 đối số có kiểu giá trị là double .

class Adder {
static int add(int a, int b) {
return a + b;
}

static double add(double a, double b) {
return a + b;
}
}

class TestOverloading2 {
public static void main(String[] args) {
System.out.println(Adder.add(5, 5));
System.out.println(Adder.add(4.3, 5.6));
}
}

Kết quả chạy chương trình trên :

10
9.9

Một số câu hỏi về nạp chồng phương pháp trong java

Tại sao không thể nạp chồng phương thức bằng cách chỉ thay đổi kiểu trả về của phương thức?

Trong java, không hề nạp chồng phương pháp bằng cách chỉ đổi khác kiểu trả về của phương pháp chính do không biết phương pháp nào sẽ được gọi .Ví dụ :

class Adder {
	static int add(int a, int b) {
		return a + b;
	}

	static double add(int a, int b) {
		return a + b;
	}
}

class TestOverloading3 {
	public static void main(String[] args) {
		System.out.println(Adder.add(11, 11)); // Không biết gọi phương thức nào
	}
}

Có thể nạp chồng phương pháp main ( ) không ?Có, bạn hoàn toàn có thể nạp chồng n phương pháp main. Nhưng JVM chỉ gọi phương pháp main ( ) có tham số truyền vào là một mảng String .

Ví dụ:

public class TestOverloading4 {
	public static void main(String[] args) {
		System.out.println("main with String[]");
	}

	public static void main(String args) {
		System.out.println("main with String");
	}

	public static void main() {
		System.out.println("main without args");
	}
}

Kết quả khi chạy chương trình trên :

main with String[]

Nạp chồng phương pháp và tự động hóa ép kiểu

Kiểu tài liệu của đối số truyền vào được đổi khác sang kiểu tài liệu khác ( tự động hóa ép kiểu ) nếu giá trị của đối số đó không tương thích với kiểu tài liệu của tham số đã được đinh nghĩa .Để hiểu khái niệm này hãy xem ảnh sau :

Kiểu byte hoàn toàn có thể được ép sang những kiểu short, int, long, float hoặc double. Kiểu dữ liệu short hoàn toàn có thể được ép sang những kiểu int, long, float hoặc double. Kiểu dữ liệu char hoàn toàn có thể được ép sang những kiểu int, long, float or double …Ví dụ :


class OverloadingCalculation1 {
	void sum(int a, long b) {
		System.out.println(a + b);
	}

	void sum(int a, int b, int c) {
		System.out.println(a + b + c);
	}

	public static void main(String args[]) {
		OverloadingCalculation1 obj = new OverloadingCalculation1();
		obj.sum(20, 20);// now second int literal will be promoted to long
		obj.sum(20, 20, 20);

	}
}

Kết quả khi chạy chương trình trên :

40
60

Ví dụ : nếu không có kiểu đối số nào tương thích, quy đổi kiểu sẽ không được thực thi .

class OverloadingCalculation2 {
	void sum(int a, int b) {
		System.out.println("int arg method invoked");
	}

	void sum(long a, long b) {
		System.out.println("long arg method invoked");
	}

	public static void main(String args[]) {
		OverloadingCalculation2 obj = new OverloadingCalculation2();
		obj.sum(20, 20);// now int arg sum() method gets invoked
	}
}

Kết quả khi chạy chương trình trên :

int arg method invoked

Ví dụ : không có kiểu đối số nào phụ hợp trong phương pháp và mỗi phương pháp đổi khác số đối số tựa như nhau. Trường hợp này sẽ không xác lập được phương pháp nào được gọi .

public class OverloadingCalculation3 {
	void sum(int a, long b) {
		System.out.println("a method invoked");
	}

	void sum(long a, int b) {
		System.out.println("b method invoked");
	}

	public static void main(String args[]) {
		OverloadingCalculation3 obj = new OverloadingCalculation3();
		obj.sum(20, 20); // không xác định được phương thức nào được gọi
	}
}

Ghi đè phương pháp ( method overriding )

Ghi đè phương thức trong java xảy ra nếu lớp con có phương thức giống lớp cha.

Nói cách khác, nếu lớp con cung ứng sự setup đơn cử cho phương pháp đã được cung ứng bởi một lớp cha của nó được gọi là ghi đè phương pháp ( method overriding ) trong java .Ghi đè phương pháp được sử dụng để thu được tính đa hình tại runtime .Nguyên tắc ghi đè phương pháp :

  • Phương thức phải có tên giống với lớp cha.
  • Phương thức phải có tham số giống với lớp cha.
  • Lớp con và lớp cha có mối quan hệ kế thừa.

Ví dụ về ghi đè phương pháp ( method overriding )

Ví dụ 1 : tất cả chúng ta định nghĩa phương pháp run ( ) trong lớp con giống như đã được định nghĩa trong lớp cha, nhưng được thiết lập rõ ràng trong lớp con. Tên và tham số của phương pháp là giống nhau, 2 lớp cha và con có quan hệ thừa kế .


class Vehicle {
	void run() {
		System.out.println("Vehicle is running");
	}
}

class Bike extends Vehicle {
	void run() {
		System.out.println("Bike is running safely");
	}

	public static void main(String args[]) {
		Bike obj = new Bike();
		obj.run();
	}

}

Kết quả khi chạy chương trình trên :

Bike is running safely

Ví dụ 2 : Giả sử Bank là một đối tượng người tiêu dùng cung ứng lãi suất vay. Nhưng lãi suất vay lại khác nhau giữa từng ngân hàng nhà nước. Ví dụ, những ngân hàng nhà nước VCB, AGR và CTG hoàn toàn có thể phân phối những lãi suất vay lần lượt là 8 %, 7 % và 9 % .

class Bank {
	int getRateOfInterest() {
		return 0;
	}
}

class VCB extends Bank {
	int getRateOfInterest() {
		return 8;
	}
}

class AGR extends Bank {
	int getRateOfInterest() {
		return 7;
	}
}

class CTG extends Bank {
	int getRateOfInterest() {
		return 9;
	}
}

class BankApp {
	public static void main(String args[]) {
		VCB s = new VCB();
		AGR i = new AGR();
		CTG a = new CTG();
		System.out.println("VCB Rate of Interest: " + s.getRateOfInterest());
		System.out.println("AGR Rate of Interest: " + i.getRateOfInterest());
		System.out.println("CTG Rate of Interest: " + a.getRateOfInterest());
	}
}

Kết quả khi chạy chương trình trên :

VCB Rate of Interest: 8
AGR Rate of Interest: 7
CTG Rate of Interest: 9

Một số câu hỏi về ghi đè phương pháp ( method overriding ) trong java

Có ghi đè được phương thức static không?

Không, phương pháp static không hề ghi đè được, chính do ghi đè phương pháp được thực thi lúc runtime ( tính đa hình ) .

Tại sao không ghi đè được phương thức static?

Vì phương pháp static được ràng buộc với class, còn phương pháp instance được ràng buộc với đối tượng người tiêu dùng. Static thuộc về vùng nhớ class còn instance thuộc về vùng nhớ heap .

Có ghi đè phương thức main được không?

Không, vì main là phương pháp static .

Sự khác nhau giữa overloading và overriding trong java

Nạp chồng phương thức Ghi đè phương thức
Nạp chồng phương thức được sử dụng để tăng tính có thể đọc của chương trình Ghi đè phương thức được sử dụng để cung cấp trình triển khai cụ thể của phương thức mà đã được cung cấp bởi lớp cha của nó
Nạp chồng phương thức được thực hiện bên trong lớp (class) Ghi đè phương thức xuất hiện trong hai lớp mà có mối quan hệ IS-A (kế thừa)
Trong Nạp chồng phương thức, tham số phải khác nhau Trong Ghi đè phương thức, tham số phải là giống nhau
Nạp chồng phương thức là ví dụ của đa hình tại biên dịch (compile) Ghi đè phương thức là ví dụ của đa hình tại thực thi (runtime)
Trong Java, Nạp chồng phương thức không thể được thực hiện bởi thay đổi kiểu trả về của phương thức. Kiểu trả về có thể là giống hoặc khác trong Nạp chồng phương thức. Nhưng bạn phải thay đổi tham số Kiểu trả về phải là giống.

Tài liệu tham khảo:

4.9

Nếu bạn thấy hay thì hãy chia sẻ bài viết cho mọi người nhé!

Shares

Bình luận

phản hồi