Rate this post

Trong lập trình Java, một class là một khái niệm cốt lõi đóng vai trò như một khuôn mẫu để tạo ra các đối tượng, mang đến khả năng định nghĩa cấu trúc và hành vi cho dữ liệu. Class bao gồm trường dữ liệu (fields) để lưu trữ trạng thái của đối tượng và phương thức (methods) để thể hiện hành vi hoặc cách thức thao tác với dữ liệu đó. Trong lập trình hướng đối tượng (OOP), class không chỉ làm nền tảng để định nghĩa và tạo ra các thực thể riêng biệt (các đối tượng) mà còn giúp tổ chức và quản lý mã nguồn một cách hệ thống và mô-đun.

Class trong OOP giúp thực hiện ba nguyên lý cơ bản: đóng gói (Encapsulation), kế thừa (Inheritance), và đa hình (Polymorphism). Đóng gói cho phép ẩn đi các chi tiết triển khai bên trong class và chỉ cung cấp một giao diện công khai để tương tác với đối tượng, giúp giảm thiểu phức tạp và tăng cường tính bảo mật. Kế thừa cho phép một class mới nhận và mở rộng các thuộc tính và phương thức từ một class đã có, đảm bảo tính tái sử dụng mã nguồn. Đa hình cho phép một interface duy nhất để tham chiếu đến các thực thể của nhiều class khác nhau, giúp mã nguồn linh hoạt hơn và dễ bảo trì.

Ví dụ, trong một ứng dụng quản lý nhân viên, class Employee có thể chứa các trường dữ liệu như name, id, và position cùng với các phương thức như calculateSalary()displayDetails(). Class này tạo ra một khuôn mẫu để mô tả và quản lý thông tin liên quan đến nhân viên, trong khi các đối tượng cụ thể của Employee đại diện cho từng nhân viên cụ thể trong tổ chức.

Như vậy, class trong Java là một khái niệm trung tâm, giúp triển khai OOP một cách hiệu quả, qua đó mang lại khả năng mô-đun hóa, tái sử dụng mã nguồn và dễ dàng quản lý sự phức tạp trong phát triển phần mềm.

Ví dụ về một class trong Java có thể như sau:

public class Dog {
  // thuộc tính (biến)
  private String name;
  private int age;

  // Phương thức (hàm)
  public void setName(String name) {
    this.name = name;
  }
  
  public void setAge(int age) {
    this.age = age;
  }

  public String getName() {
    return this.name;
  }

  public int getAge() {
    return this.age;
  }

  public void bark() {
    System.out.println("Woof! Woof!");
  }
}

Trong ví dụ trên, tôi đã tạo một class có tên “Dog” với hai thuộc tính là “name” và “age”, và 4 phương thức là setName(),setAge(), getName(), getAge() và bark().

Ví dụ về cách sử dụng class trên:

Dog dog1 = new Dog();
dog1.setName("Scooby");
dog1.setAge(7);
System.out.println(dog1.getName());
System.out.println(dog1.getAge());
dog1.bark();

Trong ví dụ trên, Tôi đã tạo một đối tượng của class Dog, gán giá trị cho thuộc tính, và gọi các phương thức của đối tượng Dog đó.

Lưu ý: mọi class trong java mặc định extend từ lớp Object của java, nên các class có các hàm mặc định như equals(), toString(), hashCode()…

Tại sao sử dụng class trong java

Sử dụng class trong Java là một trong những tính năng quan trọng của ngôn ngữ lập trình, vì nó cho phép bạn xây dựng các đối tượng có các thuộc tính và phương thức riêng biệt.

  1. Tái sử dụng mã: Sử dụng class cho phép bạn tái sử dụng mã trong các đối tượng khác nhau. Ví dụ, nếu bạn có một class có tên “Dog” với các phương thức bark() và run(), bạn có thể sử dụng các phương thức đó trong nhiều đối tượng khác nhau mà không cần viết lại mã.
  2. Tổ chức mã: Sử dụng class cho phép bạn tổ chức mã của bạn theo các tầng. Ví dụ, bạn có thể tạo một class có tên “Animal” chứa các thuộc tính và phương thức chung cho tất cả các loài vật, và sau đó tạo các class con như “Dog”, “Cat”, “Elephant” mà kế thừa từ class “Animals” và thêm các thuộc tính và phương thức riêng cho mỗi loài.
  3. Tăng sự tách biệt và rõ ràng: Sử dụng class cho phép bạn tách biệt và rõ ràng giữa các đối tượng và chức năng của chúng. Ví dụ, bạn có thể tạo một class “Car” và một class “Bicycle”, với mỗi class chứa các thuộc tính và phương thức tương ứng với loại xe đó.
  4. Tăng tính bảo mật: Sử dụng class cho phép bạn bảo vệ thuộc tính và phương thức của đối tượng khỏi sự truy cập không được cho phép

Cấu trúc cơ bản của một Class

Một class trong Java được xây dựng dựa trên ba cấu trúc cơ bản: trường dữ liệu (fields), phương thức (methods), và constructor, mỗi phần đều đóng vai trò quan trọng trong việc định nghĩa và quản lý hành vi và trạng thái của đối tượng.

Trường Dữ Liệu (Fields):
Trường dữ liệu là các biến được định nghĩa bên trong class và được sử dụng để lưu trữ thông tin hoặc trạng thái của đối tượng. Các trường dữ liệu thường được đặt với các modifier như private, public, hoặc protected để kiểm soát việc truy cập từ bên ngoài class, thực hiện nguyên tắc đóng gói trong OOP.

public class Car {
    private String brand;  // Trường dữ liệu lưu trữ thương hiệu của xe
    private int year;      // Trường dữ liệu lưu trữ năm sản xuất
}

Phương Thức (Methods):
Phương thức định nghĩa các hành động hoặc hành vi mà đối tượng của class có thể thực hiện. Mỗi phương thức có thể truy cập và thao tác với các trường dữ liệu của class, cũng như thực thi các tác vụ cụ thể. Phương thức cung cấp cơ chế để tương tác với đối tượng và thực hiện các chức năng liên quan đến đối tượng đó.

public void displayInfo() {
    System.out.println("Brand: " + brand + ", Year: " + year);
}

Constructor:
Constructor là một loại phương thức đặc biệt được gọi tự động khi một đối tượng mới của class được tạo ra. Mục đích của constructor là khởi tạo đối tượng, thiết lập trạng thái ban đầu bằng cách gán giá trị cho các trường dữ liệu. Một class có thể có nhiều constructor với các tham số khác nhau, cho phép tạo ra đối tượng trong các trạng thái khác nhau.

public Car(String brand, int year) {
    this.brand = brand;
    this.year = year;
}

Như vậy, cấu trúc cơ bản của một class trong Java bao gồm trường dữ liệu để định nghĩa trạng thái, phương thức để định nghĩa hành vi, và constructor để khởi tạo đối tượng, tất cả đều hợp tác để tạo nên một đối tượng hoàn chỉnh và có chức năng.

Khai báo và Định nghĩa Class

Trong Java, việc khai báo một class bắt đầu bằng từ khóa class theo sau là tên của class. Tên class thường được viết theo quy tắc CamelCase, bắt đầu bằng một chữ cái viết hoa. Cơ thể của class được bao quanh bởi cặp dấu ngoặc nhọn {}, bên trong đó chứa định nghĩa về trường dữ liệu (fields), phương thức (methods), và constructor (nếu có). Trường dữ liệu định nghĩa các thuộc tính hoặc trạng thái của đối tượng, trong khi phương thức định nghĩa các hành động hoặc hành vi mà đối tượng có thể thực hiện.

Ví dụ:
Giả sử bạn muốn định nghĩa một class đơn giản Book để quản lý thông tin về sách, bao gồm tên sách và tác giả. Dưới đây là cách bạn có thể khai báo và định nghĩa class Book:

public class Book {
    // Trường dữ liệu
    private String title;  // Tên sách
    private String author; // Tác giả

    // Constructor để khởi tạo đối tượng Book với tên sách và tác giả
    public Book(String title, String author) {
        this.title = title;
        this.author = author;
    }

    // Phương thức để hiển thị thông tin sách
    public void displayInfo() {
        System.out.println("Book: " + title + " by " + author);
    }
}

Trong ví dụ trên, class Book được khai báo với hai trường dữ liệu titleauthor, mỗi trường được khai báo là private để đảm bảo tính đóng gói và ngăn chặn truy cập trực tiếp từ bên ngoài class. Constructor Book(String title, String author) được sử dụng để khởi tạo đối tượng Book với thông tin cụ thể. Phương thức displayInfo() là một phương thức công khai (public) được định nghĩa để hiển thị thông tin của sách khi được gọi.

Khai báo và định nghĩa class như trên cho phép bạn tạo các đối tượng Book với các thuộc tính riêng biệt và sử dụng phương thức displayInfo() để hiển thị thông tin của sách, minh họa cách các trường dữ liệu và phương thức làm việc cùng nhau trong một class để đại diện và quản lý thông tin một cách có cấu trúc.

Tạo Đối Tượng từ Class

Trong Java, quá trình tạo (instantiate) đối tượng từ một class là bước quan trọng để sử dụng các định nghĩa và hành vi đã được xác định trong class đó. Để tạo một đối tượng mới, bạn sử dụng từ khóa new theo sau là gọi constructor của class. Constructor là một phương thức đặc biệt trong class được thiết kế để khởi tạo đối tượng, thiết lập trạng thái ban đầu của nó bằng cách gán giá trị cho các trường dữ liệu.

Khi bạn gọi constructor bằng cách sử dụng new, Java sẽ cấp phát bộ nhớ cho đối tượng mới và khởi tạo các trường dữ liệu của nó theo những gì được xác định trong constructor. Sau khi đối tượng được tạo, bạn có thể sử dụng nó để gọi phương thức và truy cập các trường dữ liệu (tuân theo quyền truy cập được xác định).

Ví dụ:
Giả sử bạn có một class Car được định nghĩa như sau:

public class Car {
    private String brand;
    private int year;

    public Car(String brand, int year) {
        this.brand = brand;
        this.year = year;
    }

    public void displayInfo() {
        System.out.println("Car: " + brand + " - " + year);
    }
}

Để tạo một đối tượng Car mới từ class này, bạn sử dụng từ khóa new kết hợp với constructor của Car:

Car myCar = new Car("Toyota", 2020);

Trong ví dụ trên, new Car("Toyota", 2020) sẽ tạo một đối tượng Car mới với brand là “Toyota” và year là 2020. Sau khi đối tượng được tạo, bạn có thể sử dụng nó để gọi phương thức displayInfo():

myCar.displayInfo();  // In ra: Car: Toyota - 2020

Quá trình tạo đối tượng từ class và sử dụng đối tượng đó thông qua các phương thức và trường dữ liệu là nền tảng của lập trình hướng đối tượng, giúp quản lý và sử dụng dữ liệu một cách có cấu trúc và hiệu quả.

Truy cập Thành viên của Class

Trong lập trình hướng đối tượng bằng Java, việc truy cập thành viên của class, bao gồm trường dữ liệu và phương thức, là một phần quan trọng của việc tương tác với đối tượng. Thành viên của class có thể được truy cập trực tiếp hoặc gián tiếp tùy thuộc vào phạm vi truy cập (access modifier) được chỉ định cho chúng.

Truy Cập Trực Tiếp:
Truy cập trực tiếp đến trường dữ liệu hoặc phương thức của một đối tượng được thực hiện bằng cách sử dụng toán tử dấu chấm (.), sau đó là tên của trường dữ liệu hoặc phương thức. Cách tiếp cận này chỉ khả thi khi trường dữ liệu hoặc phương thức được đánh dấu là public hoặc khi truy cập từ bên trong cùng một class.

public class Car {
    public String brand;  // Trường dữ liệu public

    public void displayInfo() {  // Phương thức public
        System.out.println("Brand: " + brand);
    }
}

Car myCar = new Car();
myCar.brand = "Toyota";  // Truy cập trực tiếp vào trường dữ liệu
myCar.displayInfo();  // Gọi phương thức trực tiếp

Truy Cập Gián Tiếp Qua Getter và Setter:
Truy cập gián tiếp thông qua các phương thức getter và setter là một kỹ thuật đóng gói quan trọng, giúp bảo vệ dữ liệu bên trong đối tượng. Getter và setter là các phương thức public cho phép lấy (get) và thiết lập (set) giá trị của trường dữ liệu private mà không cần phải truy cập trực tiếp vào chúng, cung cấp một cách kiểm soát và xác thực dữ liệu trước khi thực hiện bất kỳ thay đổi nào.

public class Car {
    private String brand;  // Trường dữ liệu private

    public String getBrand() {  // Getter
        return brand;
    }

    public void setBrand(String b) {  // Setter
        brand = b;
    }
}

Car myCar = new Car();
myCar.setBrand("Toyota");  // Thiết lập giá trị thông qua setter
System.out.println(myCar.getBrand());  // Truy cập giá trị thông qua getter

Trong ví dụ này, trường dữ liệu brand được đặt là private, do đó không thể truy cập trực tiếp từ bên ngoài class Car. Thay vào đó, các phương thức getBrand()setBrand(String b) (getter và setter) được sử dụng để tương tác với trường dữ liệu này một cách an toàn.

Sử dụng getter và setter không chỉ giúp đảm bảo tính bảo mật và đóng gói trong lập trình hướng đối tượng mà còn cho phép thêm logic xác thực hoặc tính toán phức tạp khi truy cập hoặc cập nhật dữ liệu, làm cho mã nguồn linh hoạt và dễ bảo trì hơn.

Phạm vi Truy cập (Access Modifiers)

Trong Java, phạm vi truy cập (access modifiers) đóng vai trò quan trọng trong việc kiểm soát việc truy cập vào class, trường dữ liệu, phương thức và constructor. Các phạm vi truy cập bao gồm: public, private, protected, và mức độ truy cập mặc định (không sử dụng modifier). Sự lựa chọn phạm vi truy cập ảnh hưởng đến việc sử dụng và khả năng tương tác giữa các đối tượng và class trong một ứng dụng.

Public:
Modifier public làm cho thành viên của class có thể truy cập được từ bất kỳ đâu trong ứng dụng. Khi một class, trường dữ liệu, phương thức hoặc constructor được khai báo là public, chúng có thể được truy cập bởi bất kỳ class nào khác trong cùng package hoặc các package khác.

Private:
Modifier private hạn chế việc truy cập chỉ trong phạm vi của chính class đó. Trường dữ liệu hoặc phương thức được khai báo là private không thể được truy cập trực tiếp từ bên ngoài class, giúp ẩn đi chi tiết triển khai và bảo vệ trạng thái nội tại của đối tượng.

Protected:
Modifier protected cho phép truy cập từ class khai báo nó, các class con (kể cả nếu chúng ở trong package khác), và các class khác trong cùng package. Protected thường được sử dụng trong kế thừa để cung cấp quyền truy cập cho các lớp con.

Mức Độ Truy Cập Mặc Định:
Khi không sử dụng bất kỳ access modifier nào, mức độ truy cập mặc định được áp dụng. Trong trường hợp này, thành viên class có thể được truy cập từ bất kỳ class nào khác trong cùng một package, nhưng không từ các package khác.

Sự lựa chọn phạm vi truy cập phản ánh nguyên tắc đóng gói trong lập trình hướng đối tượng, giúp quản lý tính bảo mật và tính toàn vẹn của dữ liệu. Sử dụng private cho trường dữ liệu để tránh truy cập trực tiếp từ bên ngoài class, kết hợp với việc cung cấp các phương thức public (getter và setter) để kiểm soát việc truy cập và cập nhật giá trị, là một thực tiễn tốt. Modifier public thích hợp cho các phương thức mà bạn muốn cho phép truy cập từ bên ngoài class, trong khi protected hữu ích khi muốn giới hạn truy cập nhưng vẫn cho phép lớp con mở rộng tính năng. Sự hiểu biết về các phạm vi truy cập là nền tảng để xây dựng ứng dụng có cấu trúc tốt và bảo mật.

Kế thừa trong class

Kế thừa trong lập trình hướng đối tượng, đặc biệt trong Java, là một cơ chế cho phép một class mới (subclass hoặc derived class) kế thừa các thuộc tính và phương thức từ một class đã có (superclass hoặc base class). Khái niệm này giúp tạo ra một hệ thống phân cấp class, nơi các subclass có thể tận dụng và mở rộng các tính năng của superclass mà không cần viết lại mã.

Trong Java, kế thừa được thực hiện bằng cách sử dụng từ khóa extends. Khi một class B kế thừa từ class A, class B sẽ tự động kế thừa tất cả các thuộc tính và phương thức không phải là private từ class A. Class B cũng có thể định nghĩa thêm các thuộc tính và phương thức của riêng mình hoặc ghi đè (override) các phương thức kế thừa từ class A.

Ví dụ:

public class Vehicle {  // Superclass
    protected String brand = "Ford";  // Thuộc tính Vehicle

    public void honk() {  // Phương thức Vehicle
        System.out.println("Tuut, tuut!");
    }
}

public class Car extends Vehicle {  // Subclass
    private String modelName = "Mustang";  // Thuộc tính Car

    public static void main(String[] args) {
        Car myCar = new Car();

        myCar.honk();  // Gọi phương thức từ superclass

        System.out.println(myCar.brand + " " + myCar.modelName);  // Truy cập thuộc tính từ superclass và subclass
    }
}

Trong ví dụ trên, class Car kế thừa từ class Vehicle. Điều này cho phép Car sử dụng thuộc tính brand và phương thức honk() từ Vehicle, cùng với việc thêm thuộc tính modelName riêng của mình.

Lợi ích của Kế thừa:

  1. Tái sử dụng mã: Kế thừa cho phép lập trình viên tái sử dụng các phần mã đã được kiểm chứng từ các class hiện có, giảm thiểu sự trùng lặp và tăng tính bảo mật mã nguồn.
  2. Mở rộng chức năng: Các subclass có thể mở rộng hoặc tinh chỉnh chức năng của superclass, cho phép tạo ra các đối tượng với các hành vi đặc biệt mà không ảnh hưởng đến lớp cha.
  3. Tính đa hình: Kế thừa hỗ trợ tính đa hình, cho phép một biến của lớp cha tham chiếu đến đối tượng của lớp con, giúp việc triển khai các hành vi đa dạng dựa trên đối tượng cụ thể được tham chiếu.

Kế thừa là một trong những nguyên lý cốt lõi của lập trình hướng đối tượng, mang lại sự linh hoạt và mạnh mẽ cho việc phát triển phần mềm, giúp xây dựng các hệ thống phần mềm có cấu trúc, dễ mở rộng và bảo trì.

Đa Hình và Đóng gói

Trong lập trình hướng đối tượng Java, đa hình (Polymorphism) và đóng gói (Encapsulation) là hai nguyên lý cốt lõi giúp tạo nên sự linh hoạt và bảo mật cho mã nguồn.

Đa Hình:

Đa hình trong Java được thể hiện qua khả năng một đối tượng thuộc về nhiều dạng khác nhau. Đa hình được thực hiện chủ yếu thông qua hai cách: ghi đè phương thức (method overriding) và nạp chồng phương thức (method overloading).

  • Ghi Đè Phương Thức (Method Overriding): Là kỹ thuật cho phép một lớp con cung cấp một phiên bản cụ thể của phương thức đã được định nghĩa trong lớp cha của nó. Điều này cho phép các đối tượng của lớp con sử dụng phiên bản phương thức của chính nó thay vì phiên bản từ lớp cha.
class Animal {
    public void sound() {
        System.out.println("Animal makes a sound");
    }
}

class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("Dog barks");
    }
}
  • Nạp Chồng Phương Thức (Method Overloading): Là kỹ thuật cho phép định nghĩa nhiều phương thức cùng tên nhưng khác nhau về số lượng hoặc kiểu dữ liệu của tham số. Nạp chồng phương thức giúp tăng cường tính linh hoạt của mã nguồn.
class Adder {
    static int add(int a, int b) {
        return a + b;
    }
    static double add(double a, double b) {
        return a + b;
    }
}

Đóng Gói:
Đóng gói là kỹ thuật giấu đi chi tiết triển khai bên trong của đối tượng và chỉ cung cấp một giao diện công khai để tương tác với đối tượng đó. Đóng gói được thực hiện thông qua việc sử dụng các modifier truy cập như private, public, và protected.

  • Sử dụng private cho các trường dữ liệu giúp giấu đi trạng thái nội tại của đối tượng và ngăn chặn truy cập trực tiếp từ bên ngoài class.
  • Cung cấp các phương thức công khai (public) như getter và setter để truy cập và cập nhật giá trị của các trường dữ liệu private một cách an toàn.
public class Person {
    private String name;  // Trường dữ liệu private

    public String getName() {  // Getter
        return name;
    }

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

Đóng gói không chỉ tăng cường tính bảo mật cho dữ liệu mà còn giúp giảm thiểu sự phụ thuộc lẫn nhau giữa các phần của mã nguồn, qua đó tăng tính mô-đun và dễ bảo trì cho ứng dụng. Đa hình và đóng gói cùng nhau tạo nên nền móng vững chắc cho việc thiết kế phần mềm linh hoạt, mở rộng và bảo mật.

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *

Contact Me on Zalo
Call now