package (Bài trước)
(Bài tiếp) Ép kiểu

Lập trình hướng đối tượng (OOP) nói chung và lập trình hướng đối tượng java (oop trong java) có bốn vấn đề chính: tính đống gói, kế thừa, đa hình và trừu tượng hóa. Ở đây thảo luận về 4 vấn đề đó với Java

Tính đóng gói OOP trong Java

Ý tưởng của tính đóng gói (Encapsulation) là đảm bảo việc triển khai chi tiết dữ liệu của đối tượng không cần biết bởi thành phần sử dụng. Các thuộc tính dữ liệu của một lớp sẽ là ẩn với các lớp khác, truy cập dữ liệu của một lớp thực hiện thông qua các phương thức / hoặc thuộc tính mà lớp đó cho phép. Thường truy cập các dữ liệu (đọc hoặc thiết lập) Java cung cấp các hàm public là hàm seter và getter để thống nhất cho tính chất đóng gói.

Xem lớp ví dụ sau:

class BankAccount {

      // số tiền dư trong tài khoản
      private double balance=0;

      // gửi tiền vào tài khoản
      public void deposit(double x) {
        if(x > 0) {
          this.balance += x;
        }
  }
}

Khi sử dụng

var yourAcc = new BankAccount();
yourAcc.balance += 500; // lỗi - không truy cập được
yourAcc.deposit(500);

Lớp trên có biến balance là ẩn với bên ngoài, không thể truy cập để đọc hay thiết lập giá trị cho nó một cách trực tiếp. Muốn thay đổi giá trị của nó (truy cập) thì chỉ có cách thông qua deposite, phương thực này có thể cộng giá trị vào biến, nhưng trước khi thực hiện nó sẽ kiểm tra tính hợp lệ của dữ liệu trước

Một số lợi ích sử dụng tính đóng gói như trên

  • Điều khiển cách lấy và thiết lập dữ liệu của lớp
  • Mềm dẻo hơn và dễ dang thay đổi code
  • Có khả năng thay đổi một phần code mà không ảnh hưởng đến phần khác

Tính kế thừa OOP Java

Tính kế thừa (Inheritance) có nghĩa là một lớp nhận được - có được các thuộc tính, phương thức từ một lớp khác. Lớp nhận được các thuộc tính gọi là lớp con và lớp kia gọi là lớp cha, lớp cơ sở ...

Trong Java sử dụng từ khóa extends khi khai báo lớp, để cho biết lớp đó kế thừa lớp cha nào (Java đơn kế thừa, lớp con chỉ cho một lớp cha)

Hãy xem ví dụ sau, xây dựng lớp Animal làm lớp cơ sở (lớp cha)

class Animal {
  protected int legs;
  public void eat() {
    System.out.println("Animal eats");
  }
}

Giờ khai báo một lớp có tên Dog kế thừa lớp Animal (thuộc tính và phương thức của Animal sẽ có trong Dog)

class Dog extends Animal {
  Dog() {
    this.legs = 4;
  }
}

Như vậy Dog là lớp con, còn Animal là lớp cha. Thuộc tính legs, phương thức eat tự động kế thừa bởi Dog

Sử dụng

Dog d = new Dog();
d.eat();            //eat kế thừa từ Animal
d.showlegs();       //showlegs kế thừa từ Animal

// Out:
// Animal eats
// Legs: 4

Đọc lại ý nghĩa từ khóa protected tại mục: Modifier - phạm vi truy cập

Vấn đề kế thừa phương thức khởi tạo của lớp cha

Nên nhớ khởi phương thức khởi tạo của lớp cha không được lớp con kế thừa. Tuy vậy mỗi khi khởi phương thức tạo của lớp con chạy thì phương thức tạo của lớp cha sẽ chạy trước. Gọi phương thức khởi tạo của lớp cha thì dùng cú pháp supper(tham số nếu có);, trong phương thức tạo lớp con không gọi phương thức tạo lớp cha một cách tường minh ở đầu dòng code thì phương thức tạo mặc định của lớp cha tự động chạy (chính là supper())

Ví dụ:

class A {
    // khởi tạo mặc định của A
    public A() {
      System.out.println("New A");
    }
    public A(String msg) {
        System.out.println("New A  : " + msg);
      }
  }
  class B extends A {
    // Phương thức khởi tạo lớp con không gọi phương thức khởi tạo lớp cha tường minh
    // thì phương thức khởi tạo mặc định của lớp cha tự chạy
    public B() {
        System.out.println("New B");
    }
}

Khi tạo đối tượng lớp B

var b = new B();

// Xuất ra
// New A
// New B

Trường hợp phương thức khởi tạo lớp con gọi phương thức khởi tạo lớp cha tường minh

class B extends A {
    public B() {
        // có gọi phương thức khởi tạo của lớp cha, do vậy phương thức khởi tạo mặc định
        // lớp cha sẽ không chạy
        super("TEST CONSTRUCTOR");
        System.out.println("New B");
    }
}
var b = new B();
// Xuất ra
// New A  : TEST CONSTRUCTOR
// New B

Ở lớp con, bạn có thể truy cập trực tiếp đến lớp cha mằng từ khóa super, ví dụ super.var truy cập biến var của lớp cha. Một lớp chỉ có thể kế thừa một lớp cha

Tính đa hình trong lập trình hướng đối tượng Java

Tính đa hình (Polymorphism), ám chỉ tới ý tưởng có nhiều hình dạng. Việc gọi một phương thức sẽ có thể có sự khác nhau khi thi hành, nó phụ thuộc vào kiểu đối tượng ở thời điểm thực thi.

Hãy xem tính đa hình ở ví dụ sau:

class Animal {
  public void makeSound() {
    System.out.println("Grr...");
  }
}
class Cat extends Animal {
  public void makeSound() {
    System.out.println("Meow");
  }
}
class Dog extends Animal {
  public void makeSound() {
    System.out.println("Woof");
  }
}

Ở ví dụ trên, do Cat và Dog đều kế thừa từ Animal, nến các đối tượng tạo ra bởi Cat, Dog đều là đối tượng Animal. Khi sử dụng có thể code như sau:

public static void main(String[ ] args) {
  Animal a = new Dog();
  Animal b = new Cat();

  a.makeSound();
  //Outputs "Woof"

  b.makeSound();
  //Outputs "Meow"
}

Bạn thấy a, b đều là Animal nhưng khi gọi makeSound() ứng xử là khác nhau, đó là tính đa hình.

Nạp chồng phương thức trong java - overriding

Ở ví dụ trên ta thấy một lớp con kế thừa các phương thức của lớp cha, nhưng nó vẫn có thể định nghĩa lại cách hoạt động của phương thức nào đó trong lớp cha, đó chính là nạp chồng phương thức. Nạp chồng còn gọi với thuật ngữ là Đa hình khi chạy.

Ví dụ

class Animal {
    public void makeSound() {
        System.out.println("Grr...");
    }
}
class Cat extends Animal {
    // Lớp con định nghĩa lại một phương thức đã có trong lớp cha
    // đã nạp chồng makeSound
    public void makeSound() {
        System.out.println("Meow");
    }
}

Ta thấy lớp Cat đã định nghĩa lại makeSound() của lớp cha Animal

Các nguyên tắc khi nạp chồng

  • Kiểu trả về và đối số phải giống nhau
  • Phạm vi truy cập (modifier) không được mở rộng hơn lớp cha. Ví dụ lớp cha khai báo là public thì lớp con có thể quá tải là public, private hoặc protected
  • Phương thức final, static không thể nạp chồng
  • Phương thức không thể kế thừa thì không thể nạp chồng (private)
  • Khởi tạo là không thể nạp chồng

Quá tải phương thức trong java - overloading

Khi các phương thức có cùng tên nhưng tham số khác nhau thì gọi là sự quá tải phương thức. Khi gọi phương thức, tuy vào tham số và trình biên dịch quyết định gọi phương thức phù hợp.

Ví dụ: hai hàm max() định nghĩa sau là quá tải. Khi gọi với tham số kiểu int thì tự gọi hàm thứ nhất, và tham số double thì tự gọi hàm thứ 2

int max(int a, int b) {
  if(a > b) {
    return a;
  }
  else {
    return b;
  }
}
double max(double a, double b) {
  if(a > b) {
    return a;
  }
  else {
    return b;
  }
}

Quá tải còn gọi là Đa hình lúc biên dịch

Tính trừu tượng OOP Java

Dữ liệu trừu tượng hóa là cách bao quát thông tin thế giới xung quanh, nó mô ta một cách tổng quát hóa mà không chi tiết. Khái niệm trừu tượng dùng để mô tả cả một tập hợp, loài, dạng ... hơn là một trường hợp cụ thể.

Java trừu tượng hóa thông qua các lớp trừu tượng (abstract) và các giao diện (interface).

Lớp trừu tượng, phương thức trừu tượng trong java

Một lớp trừu tượng thì khi định nghĩa lớp có thêm từ khóa abstract ở đầu. Một phương thức là phương thức trừu tượng thì khi khai báo không có phần thân {}. Khi là trừu tượng thì lớp, phương thực có tính chất sau:

  • Nếu lớp định nghĩa là trừu tượng abstract thì không thể dùng trực tiếp nó để tạo đối tượng (không dùng được toán tử new tạo đối tượng), nó chỉ được dùng làm lớp cha cho các lớp khác kế thừa.
  • Để sử dụng lớp trừu tượng abstract, bạn phải viết một lớp kế thừa nó
  • Một lớp có chứa hàm abstract thì coi như là lớp abstract, lớp kế thừa phải overriding - nạp chồng phương thức abstract.

Ví dụ

abstract class Animal {
  int legs = 0;
  abstract void makeSound();
}

Phương thức makeSound(); trên cũng là abstract, lớp kế thừa bắt buộc phải định nghĩa

class Cat extends Animal {
  public void makeSound() {
    System.out.println("Meow");
  }
}

Ta thấy sự trừu tượng hóa của lớp Animal, mọi Animal đều có tiếng kêu, mỗi lớp kế thừa nó bắt buộc phải định nghĩa ra một kiểu kêu cụ thể.

Giao diện - interface trong java

Giao diện được tạo ra như lớp thay vì dùng từ khóa class thì nó dùng từ khóa interface, một giao diện thì có tính chất giống lớp abstract và điều đặc biệt là tất cả các phương thức đều phải là abstract (chỉ có tên phương thức).

Một số tính chất của interface

  • Định nghĩa bằng từ khóa interface
  • Chỉ có thể chứa các biến static, final (hằng)
  • Không có khởi tạo, vì giao diện không sử dụng trực tiếp tạo ra đối tượng
  • Một interface có thể kế thừa interface khác
  • Một lớp có thể kế thừa một hoặc nhiều interface

Ví dụ đây là một interface

interface Animal {
  public void eat();
  public void makeSound();
}

Giao diện interface trong Java được dùng để triển khai lớp cụ thể, một lớp khai báo là triển khai từ giao diện nào thì khi định nghĩa có sử dụng từ khóa implements theo sau là các giao diện cần triển khai (cách nhau bởi dấu ,). Lớp triển khai phải định nghĩa các phương thức mô tả trong giao diện.

Ví dụ

// implement trong java là triển khai giao diện
class Cat implements Animal {
  public void makeSound() {
    System.out.println("Meow");
  }
  public void eat() {
    System.out.println("omnomnom");
  }
}

Đăng ký nhận bài viết mới
package (Bài trước)
(Bài tiếp) Ép kiểu