Thừa kế và đa hình trong Java

1- Giới thiệu

Thừa kế và đa hình (Inheritance & polymorphism) - đây là một khái niệm vô cùng quan trọng trong Java. Mà bạn bắt buộc phải hiểu nó.

2- Class, cấu tử và đối tượng (Class, Constructor, instance)

Bạn cần hiểu một cách rạch ròi về class, cấu tử (constructor) và đối tượng trước khi bắt đầu tìm hiểu quan hệ thừa kế trong java. Chúng ta xem class Person, mô tả một con người với các thông tin tên, năm sinh, nơi sinh.
Person.java
package org.o7planning.tutorial.inheritance.basic;

public class Person {

	// Trường name - Thông tin tên người.
	private String name;
	// Trường bornYear - thông tin năm sinh
	private Integer bornYear;
	// Trường placeOfBirth - thông tin nơi sinh.
	private String placeOfBirth;

	// Constructor có 3 tham số.
	// Mục đích nhằm để gán các giá trị cho các trường của Person.
	// Chỉ định rõ tên, năm sinh, nơi sinh của một người.
	public Person(String name, Integer bornYear, String placeOfBirth) {
		this.name = name;
		this.bornYear = bornYear;
		this.placeOfBirth = placeOfBirth;
	}

	// Constructor có 2 tham số.
	// Mục đích gán các giá trị cho 2 trường tên và năm sinh cho Person.
	// Nơi sinh không được gán.
	public Person(String name, Integer bornYear) {
		this.name = name;
		this.bornYear = bornYear;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Integer getBornYear() {
		return bornYear;
	}

	public void setBornYear(Integer bornYear) {
		this.bornYear = bornYear;
	}

	public String getPlaceOfBirth() {
		return placeOfBirth;
	}

	public void setPlaceOfBirth(String placeOfBirth) {
		this.placeOfBirth = placeOfBirth;
	}
}
PersonDemo.java
package org.o7planning.tutorial.inheritance.basic;

public class PersonDemo {

	public static void main(String[] args) {

		// Đối tượng: Thomas Edison.
		Person edison = new Person("Thomas Edison", 1847);

		System.out.println("Info:");
		System.out.println("Name: " + edison.getName());
		System.out.println("Born Year: " + edison.getBornYear());
		System.out.println("Place Of Birth: " + edison.getPlaceOfBirth() );

		// Đối tượng: Bill Gates
		Person billGates = new Person("Bill Gate", 1955, "Seattle, Washington");

		System.out.println("Info:");
		System.out.println("Name: " + billGates.getName());
		System.out.println("Born Year: " + billGates.getBornYear());
		System.out.println("Place Of Birth: " + billGates.getPlaceOfBirth());

	}

}

Phân biệt Class, cấu tử (constructor) và đối tượng:

Lớp Person mô phỏng một lớp người, nó là một thứ gì đó trừu tượng, nhưng nó có các trường để mang thông tin, trong ví dụ trên là tên, năm sinh, nơi sinh.

Cấu tử (Constructor) - Người ta còn gọi là "Phương thức khởi tạo"
  • Cấu tử luôn có tên giống tên lớp.
  • Một class có một hoặc nhiều cấu tử.
  • Cấu tử có hoặc không có tham số, cấu tử không có tham số còn gọi là cấu tử mặc định.
  • Cấu tử được sử dụng để tạo ra một đối tượng của lớp.
Như vậy lớp Person (Mô tả lớp người) là thứ trừu tượng, nhưng khi chỉ rõ vào bạn hoặc tôi thì đó là 2 đối tượng (instance) thuộc lớp Person. Và Constructor là phương thức đặc biệt để tạo ra đối tượng, Constructor sẽ gán các giá trị vào các trường (field) của class cho đối tượng..

Hãy xem minh họa, khởi tạo đối tượng từ cấu tử như thế nào.

3- Thừa kế trong java

Chúng ta cần một vài class tham gia vào minh họa.
  • Animal: Class mô phỏng một lớp Động vật.
  • Duck: Class mô phỏng lớp vịt, là một class con của Animal.
  • Cat: Class mô phỏng lớp mèo, là một class con của Animal
  • Mouse: Class mô phỏng lớp chuột, là một class con của Animal.
Ở đây chúng ta có class Animal, với một method không có nội dung.
  • public abstract String getAnimalName();
Method này là một method trừu tượng (abstract), tại các class con cần phải khai báo và triển khai nội dung của nó. Method này có ý nghĩa là trả về tên loài động vật.
Class Animal có 1 phương thức trừu tượng nó phải được khai báo là trìu tượng (abstract). Class trừu tượng có các cấu tử (constructor) nhưng bạn không thể khởi tạo đối tượng từ nó.
  • Về bản chất nghĩa là bạn muốn tạo một đối tượng động vật, bạn cần tạo từ một loại đông vật cụ thể, trong trường hợp này bạn phải khởi tạo từ cấu tử (constructor) của Cat, Mouse hoặc Duck.
Animal.java
package org.o7planning.tutorial.inheritance.animal;

// Lớp có ít nhất 1 phương thức trừu tượng 
// bắt buộc phải được khai báo là trừu tượng.
public abstract class Animal {

	// Tên, ví dụ Mèo Tom, Chuột Jerry.
	private String name;

	// Constructor mặc định.
	public Animal() {
		// Gán giá trị mặc định cho 'name'.
		this.name = this.getAnimalName();
	}

	public Animal(String name) {
		this.name = name;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	// Đây là một phương thức trừu tượng.
	// Trả về tên của loài động vật này.
	// Nội dung cụ thể của phương thức này
	// sẽ được thực hiện (implemented) tại các lớp con.
	public abstract String getAnimalName();

}

Tiếp theo xem class Cat, thừa kế từ Animal.

Cat cũng có các cấu tử của nó, và cũng có các trường của nó. Trong dòng đầu tiên của cấu tử bao giờ cũng phải gọi super(..) nghĩa là gọi lên cấu tử cha, để khởi tạo giá trị cho các trường của class cha.

Nếu bạn không gọi, mặc định Java hiểu là đã gọi super(), nghĩa là gọi cấu tử mặc định của class cha.
Cat.java
package org.o7planning.tutorial.inheritance.animal;

public class Cat extends Animal {

	private int age;
	private int height;

	public Cat(int age, int height) {
		// Gọi tới Constructor mặc định của lớp cha (Animal).
		// Nhằm mục đích gán tạo giá trị cho các trường (field) của lớp cha.
		super();
		// Sau đó mới gán giá trị cho các trường (field) của nó.
		this.age = age;
		this.height = height;
	}

	public Cat(String name, int age, int height) {
		// Gọi tới Constructor của lớp cha (Animal)
		// Nhằm mục đích gán tạo giá trị cho các trường (field) của lớp cha.
		super(name);
		// Sau đó mới gán giá trị cho các trường (field) của nó.
		this.age = age;
		this.height = height;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public int getHeight() {
		return height;
	}

	public void setHeight(int height) {
		this.height = height;
	}

	// Thực hiện (implement) phương thức trừu tượng được khai báo tại lớp cha.
	@Override
	public String getAnimalName() {
		return "Cat";
	}

}

Khi bạn khởi tạo một đối tượng Cat điều gì xẩy ra?

Xem class Mouse, thừa kế từ Animal.

Mouse.java
package org.o7planning.tutorial.inheritance.animal;

public class Mouse extends Animal {

	private int weight;

	// Constructor mặc định.
	public Mouse() {
		// Gọi tới cấu tử Mouse(int)
		// Call to constructor Mouse(int)
		this(100);
	}

	// Constructor có 1 tham số
	public Mouse(int weight) {
		// Nếu bạn không gọi bất kỳ một super(..) nào.
		// Java sẽ gọi một super() mặc định.
		// super();
		this.weight = weight;
	}

	// Constructor có 2 tham số.
	public Mouse(String name, int weight) {
		super(name);
		this.weight = weight;
	}

	public int getWeight() {
		return weight;
	}

	public void setWeight(int weight) {
		this.weight = weight;
	}

	@Override
	public String getAnimalName() {
		return "Mouse";
	}
}
InstanceofDemo.java
package org.o7planning.tutorial.inheritance.demo;

import org.o7planning.tutorial.inheritance.animal.Animal;
import org.o7planning.tutorial.inheritance.animal.Cat;
import org.o7planning.tutorial.inheritance.animal.Mouse;

public class InstanceofDemo {

	public static void main(String[] args) {

		// Khởi tạo một đối tượng động vật.
		// Animal là một lớp trừu tượng,
		// bạn không thể tạo ra một đối tượng từ Constructor của Animal.
		Animal tom = new Cat("Tom", 3, 20);

		System.out.println("name: " + tom.getName());
		System.out.println("animalName: " + tom.getAnimalName());

		// Sử dụng toán tử 'instanceof' để kiểm tra xem
		// một đối tượng có phải là một kiểu nào đó hay không.
		boolean isMouse = tom instanceof Mouse;
		System.out.println("Tom is mouse? " + isMouse);

		boolean isCat = tom instanceof Cat;
		System.out.println("Tom is cat? " + isCat);

		boolean isAnimal = tom instanceof Animal;
		System.out.println("Tom is animal? " + isAnimal);

	}

}
Kết quả chạy ví dụ:
InheritMethodDemo.java
package org.o7planning.tutorial.inheritance.demo;

import org.o7planning.tutorial.inheritance.animal.Cat;

public class InheritMethodDemo {

	public static void main(String[] args) {

		// Tạo một đối tượng Cat.
		Cat tom = new Cat("Tom", 3, 20);

		// Gọi các phương thức thừa kế được từ lớp cha (Animal).
		System.out.println("name: " + tom.getName());
		System.out.println("animalName: " + tom.getAnimalName());

		System.out.println("-----------------");

		// Gọi các phương thức được khai báo trên lớp Cat.
		System.out.println("Age: " + tom.getAge());
		System.out.println("Height: " + tom.getHeight());
	}

}
Kết quả chạy ví dụ:

Ép kiểu (Cast) trong Java

CastDemo.java
package org.o7planning.tutorial.inheritance.demo;

import java.util.Random;

import org.o7planning.tutorial.inheritance.animal.Animal;
import org.o7planning.tutorial.inheritance.animal.Cat;
import org.o7planning.tutorial.inheritance.animal.Mouse;

public class CastDemo {

	// Phương thức này trả về ngẫu nhiên một con vật.
	public static Animal getRandomAnimal() {
		// Trả về giá trị ngẫu nhiên 0 hoặc 1.
		int random = new Random().nextInt(2);

		Animal animal = null;
		if (random == 0) {
			animal = new Cat("Tom", 3, 20);
		} else {
			animal = new Mouse("Jerry", 5);
		}
		return animal;
	}

	public static void main(String[] args) {

		Animal animal = getRandomAnimal();

		if (animal instanceof Cat) {
			// Ép kiểu (cast) thành kiểu Cat.
			Cat cat = (Cat) animal;

			// Và gọi một phương thức của lớp Cat.
			System.out.println("Cat height: " + cat.getHeight());
		} else if (animal instanceof Mouse) {
			// Ép kiểu (cast) thành kiểu Mouse.
			Mouse mouse = (Mouse) animal;

			// Và gọi một phương thức của lớp Mouse.
			System.out.println("Mouse weight: " + mouse.getWeight());
		}

	}

}

4- Tính đa hình trong Java

Bạn có một con mèo nguồn gốc châu Á (AsianCat), bạn có thể nói nó là một con mèo (Cat) hoặc nói nó là một con vật (Animal) đó là một khía cạnh của từ đa hình.

Hoặc một ví dụ khác: Trên lý lịch của bạn ghi rằng bạn là một người châu Á, trong khi đó bạn thực tế là một người Việt Nam.

Ví dụ dưới đây cho bạn thấy cách hành sử giữa khai báo và thực tế
Class AsianCat là một class thừa kế từ Cat.
AsianCat.java
package org.o7planning.tutorial.inheritance.animal;

public class AsianCat extends Cat {

	public AsianCat(String name, int age, int height) {
		super(name, age, height);
	}

	// Ghi đè (override) phương thức của lớp cha (Cat)
	@Override
	public String getAnimalName() {
		return "Asian Cat";
	}
}
Tính đa hình của Java được giải thích trong hình minh họa dưới đây:
PolymorphismDemo.java
package org.o7planning.tutorial.polymorphism;

import org.o7planning.tutorial.inheritance.animal.AsianCat;
import org.o7planning.tutorial.inheritance.animal.Cat;

public class PolymorphismDemo {

  public static void main(String[] args) {

      Cat cat1 = new Cat("Tom", 3, 20);

      Cat cat2 = new AsianCat("ATom", 2, 19);

      System.out.println("Animal Name of cat1: " + cat1.getAnimalName());

      System.out.println("Animal Name of cat2: " + cat2.getAnimalName());

  }

}
Kết quả chạy ví dụ:

Xem thêm các chuyên mục: