Java – Constructor

Constructor trong Java được dùng để tạo instance cho class. Các constructor gần giống với các phương thức nhưng trừ 2 điểm sau: Tên trùng với tên của class và không có kiểu trả về. Đôi khi constructor được coi nhưcác phương thức đặc biệt để khởi tạo object.

Bất cứ khi nào chúng ta sử từ khoá new để tạo một instance của class, constructor sẽ được gọi và object của class sẽ được trả về. Do constructor có thể chỉ trả về object cho class, nên nó hoàn toàn được Java runtime thực hiện nên chúng ta không cần thiết phải thêm kiểu trả về cho nó.

Nếu chúng ta thêm kiểu trả về cho constructor thì nó sẽ trở thành một phương thức của class. Đây là cách Java runtime phân biệt giữa một phương thức bình thường và một constructor. Giả sử chúng ta có đoạn code sau trong employee class

public Employee() {
  System.out.println("Employee Constructor");
}

public Employee Employee() {
  System.out.println("Employee Method");
  return new Employee();
}

Đầu tiên ở đây là một constructor, để ý rằng không có kiểu trả về và không trả về câu lệnh. Thứ hai là một phương thức bình thường, trong đó chúng ta gọi lại constructor đầu tiên để tạo instance của Employee. Nhưng bạn cũng không nên tạo các phương thức mà có tên giống với constructor bởi vì nó có thể gây ra sự nhầm lẫn.

Các kiểu constructor

Có 3 kiểu constructor trong Java

  1. Constructor mặc định
  2. Constructor không có tham số (no-args constructor)
  3. Constructor có tham số

Constructor mặc định

Java không bắt buộc phải luôn cung cấp một implement của constructor trong code của class. Nếu bạn không cung cấp một constructor thì Java sẽ cung cấp một implement constructor mặc định để sử dụng. Hãy cùng xem một chương trình đơn giản sử dụng constructor mặc định do chúng ta không định nghĩa một constructor.

package com.example.constructor;

public class Data {
  public static void main(String[] args) {
    Data d = new Data();
  }
}
  1. Constructor mặc định chỉ có vai trò khởi tạo object và trả về nó trong code đang gọi
  2. Construtor mặc định không có tham số và được trình biên dịch Java cung cấp khi không có constructor nào được định nghĩa
  3. Hầu hết thì chúng ta chỉ cần sử dụng constructor mặc định vì các thuộc tính khác có thể được truy cập thông qua các phương thức getter setter.

Constructor không có tham số (no-args constructor)

Constructor không có tham số được gọi là no-args constructor. Nó giống như override phương thức mặc định và thường được sử dụng để thực hiện một số tác vụ cần khởi tạo trước như là kiểm tra các resource, kết nối mạng, log…

Hãy cùng xem nhanh ví dụ bên dưới đây

package com.example.constructor;
public class Data {
  //no-args constructor
  public Data() {
    System.out.println("No-Args Constructor");
  }
  public static void main(String[] args) {
    Data d = new Data();
  }
}

Bây giờ khi chúng ta gọi new Data() thì constructor (No-Args) của chúng ta sẽ được gọi

Constructor có tham số (Parameterized Constructor)

Các constructor với các tham số thì được gọi là parameterized constructor. Hãy cùng xem ví dụ bên dưới đây

package com.example.constructor;

public class Data {
  private String name;
  public Data(String n) {
    System.out.println("Parameterized Constructor");
    this.name = n;
  }
  public String getName() {
    return name;
  }
  public static void main(String[] args) {
    Data d = new Data("Java");
    System.out.println(d.getName());
  }
}

Kết quả:

Parameterized Constructor
Java

Overloading constructor

Khi chúng ta có nhiều hơn một constructor thì nó là constructor overloading trong Java. Hãy cùng xem ví dụ bên dưới đây

package com.example.constructor;

public class Data {
  private String name;
  private int id;

  //no-args constructor
  public Data() {
    this.name = "Default Name";
  }
  //one parameter constructor
  public Data(String n) {
    this.name = n;
  }
  //two parameter constructor
  public Data(String n, int i) {
    this.name = n;
    this.id = i;
  }
  public String getName() {
    return name;
  }
  public int getId() {
    return id;
  }

  @Override
  public String toString() {
    return "ID="+id+", Name="+name;
  }
  public static void main(String[] args) {
    Data d = new Data();
    System.out.println(d);
		
    d = new Data("Java");
    System.out.println(d);
		
    d = new Data("Pankaj", 25);
    System.out.println(d);	
  }
}

Private constructor

Chúng ta không thể sử dụng abstract, final, static và các từ khoá đồng bộ với constructor, tuy nhiên chúng ta có thể sử dụng các modifier truy cập để kiểm soát việc khởi tạo object class. Sử dụng truy cập public và default tốt nhưng việc sử dụng một private constructor làm gì? Trong trường hợp đó, bất kỳ class nào cũng sẽ không thể tạo instance của class.

Một constructor được tạo private trong trường hợp chúng ta thực hiện singleton design pattern. Do Java tự động cung cấp một constructor mặc định, chúng ta phải tạo một constructor rõ ràng và thiết lập là private. Các client class được cung cấp một phương thức tĩnh hữu ích để lấy instance của class. Một private constructor được khai báo như sau

// private constructor
private Data() {
  //constructor trống để thực hiện singleton pattern
  //có thể có code được sử dụng bên trong phương thức getInstance() của class
}

Constructor chaining

Khi một constructor gọi constructor khác của cùng một class thì được gọi là constructor chaining. Chúng ta phải sử dụng từ khoá this để gọi constructor khác. Đôi khi, nó được sử dụng để thiết lập giá trị mặc định cho các biến của class.

Chú ý rằng việc gọi constructor khác nên là câu lệnh đầu tiên trong khối code. Cũng thế, không nên có đoạn gọi đệ quy vì sẽ gây ra lặp vô hạn. Nào hãy cùng xem ví dụ bên dưới đây

package com.example.constructor;

public class Employee {
  private int id;
  private String name;

  public Employee() {
    this("John Doe", 999);
    System.out.println("Default Employee Created");
  }
	
  public Employee(int i) {
    this("John Doe", i);
    System.out.println("Employee Created with Default Name");
  }
  public Employee(String s, int i) {
    this.id = i;
    this.name = s;
    System.out.println("Employee Created");
  }
  public static void main(String[] args) {
    Employee emp = new Employee();
    System.out.println(emp);
    Employee emp1 = new Employee(10);
    System.out.println(emp1);
    Employee emp2 = new Employee("Pankaj", 20);
    System.out.println(emp2);
  }

  @Override
  public String toString() {
    return "ID = "+id+", Name = "+name;
  }
  
  public int getId() {
    return id;
  }
  
  public void setId(int id) {
    this.id = id;
  }

  public String getName() {
    return name;
  }

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

Chúng ta đã ghi đè phương thức toString() để in ra một vài thông tin hữu ích về object Employee.

Kết quả

Employee Created
Default Employee Created
ID = 999, Name = John Doe
Employee Created
Employee Created with Default Name
ID = 10, Name = John Doe
Employee Created
ID = 20, Name = Pankaj

Chú ý rằng một constructor đang được gọi từ constructor khác, đó là quá trình xâu chuỗi các constructor được gọi

Super constructor

Đôi khi một class được kế thừa từ một super class, trong trường hợp đó nếu chúng ta phải gọi constructor của super class, thì chúng ta có thể sử dụng từ khoá super

Chú rằng việc gọi super constructor nên là câu lệnh đầu tiên constructor của class con. Ngoài ra khi khởi tạo constructor class con, Java đầu tiên sẽ khởi tạo super class và sau đó là class con. Nếu nếu super class không được gọi chính xác thì constructor mặc định hoặc constructor không có tham số sẽ được gọi bởi Java runtime. Để hiểu rõ hơn hãy cùng xem ví dụ sau đây

package com.example.constructor;

public class Person {
  private int age;

  public Person() {
    System.out.println("Person Created");
  }

  public Person(int i) {
    this.age = i;
    System.out.println("Person Created with Age = " + i);
  }
}
package com.example.constructor;

public class Student extends Person {
  private String name;

  public Student() {
    System.out.println("Student Created");
  }

  public Student(int i, String n) {
    super(i); // super class constructor called
    this.name = n;
    System.out.println("Student Created with name = " + n);
  }
}

Bây giờ khi chúng ta tạo một Student object như sau

Student st = new Student();

Kết quả

Person Created
Student Created

Do cuộc gọi đến constructor không có tham số của Student class, vì không có cuộc gọi đến super trong câu lệnh đầu tiên, constructor mặc định hoặc không có tham số được gọi. Do đó kết quả sẽ như thế nào khi chúng ta sử dụng constructor có tham số của class Student như sau Student st = new Student(34, "Pankaj");

Kết quả

Person Created with Age = 34
Student Created with name = Pankaj

Chúng ta nhận được kết quả rõ ràng bởi vì chúng ta gọi super class constructor, nên Java ko cần thêm bất kỳ công việc nào.

Sao chép constructor

Sao chép constructor là lấy object của cùng class như tham số và tạo sao chép của nó. Đôi khi chúng ta phải sao chép object khác để thực hiện một vài xử lý. Chúng ta có thể thực hiện như sau

  1. Thực hiện clone
  2. Cung cấp phương thức hữu ích để deep copy object
  3. Có một constructor copy

Hãy cùng xem ví dụ sau

package com.example.constructor;

import java.util.ArrayList;
import java.util.List;

public class Fruits {

  private List<String> fruitsList;

  public List<String> getFruitsList() {
    return fruitsList;
  }

  public void setFruitsList(List<String> fruitsList) {
    this.fruitsList = fruitsList;
  }

  public Fruits(List<String> fl) {
    this.fruitsList = fl;
  }
	
  public Fruits(Fruits fr) {
    List<String> fl = new ArrayList<>();
    for (String f : fr.getFruitsList()) {
      fl.add(f);
    }
    this.fruitsList = fl;
  }
}

Chú ý rằng Fruits(Fruits fr) đang thực hiện một deep copy để trả về bản sao của object. Hãy cùng xem ví dụ sau để hiểu rằng tại sao nên dùng một constructor copy để sao chép object

package com.example.constructor;

import java.util.ArrayList;
import java.util.List;

public class ConstructorTest {
  public static void main(String[] args) {
    List<String> fl = new ArrayList<>();
    fl.add("Mango");
    fl.add("Orange");

    Fruits fr = new Fruits(fl);

    System.out.println(fr.getFruitsList());

    Fruits fr = fr;
    fr.getFruitsList().add("Apple");

    System.out.println(fr.getFruitsList());

    fr = new Fruits(fr);
    fr.getFruitsList().add("Banana");
    System.out.println(fr.getFruitsList());
    System.out.println(fr.getFruitsList());
  }
}

Kết quả

[Mango, Orange]
[Mango, Orange, Apple]
[Mango, Orange, Apple]
[Mango, Orange, Apple, Banana]

Chú ý rằng khi constructor sao chép được sử dụng, cả object gốc và object được sao chép đều không liên hệ tới nhau và bất kỳ thay đổi nào của một trong hai object này đều không ảnh hưởng lẫn nhau.

You May Also Like

About the Author: Nguyen Dinh Thuc

Leave a Reply

Your email address will not be published.