Constructor và Methods
Sau bài này, bạn sẽ:
- Hiểu và sử dụng constructors (default, parameterized, overloaded) để khởi tạo objects
- Áp dụng constructor chaining với this() để tái sử dụng logic khởi tạo
- Định nghĩa methods với return types, parameters, và thực hiện method overloading
- Hiểu pass by value trong Java cho cả primitive types và reference types
- Sử dụng varargs để tạo methods nhận số lượng tham số không xác định
Bài trước: Class và Object — Đã học cách khai báo class và tạo objects. Bài này sẽ tìm hiểu về constructors để khởi tạo objects và methods để định nghĩa behaviors.
Constructor là gì?
Constructor là một method đặc biệt được gọi tự động khi tạo object bằng từ khóa new. Mục đích chính: khởi tạo giá trị ban đầu cho object.
Đặc điểm của Constructor
- Tên giống tên class (phân biệt hoa thường)
- Không có return type (kể cả
void) - Được gọi tự động khi dùng
new - Có thể overload (nhiều constructors khác tham số)
Constructor giống như quá trình lắp ráp khi mua xe:
- Khi xe rời khỏi nhà máy, nó đã được lắp ráp sẵn (constructor chạy)
- Bạn có thể tùy chọn màu sắc, phụ kiện (tham số constructor)
- Xe luôn ở trạng thái sẵn sàng sử dụng (object đã khởi tạo)
Cú pháp Constructor
class ClassName {
// Constructor
[access_modifier] ClassName(parameters) {
// Khởi tạo fields
}
}
Các loại Constructor
1. Default Constructor (No-arg Constructor)
Constructor không có tham số.
class Student {
String name;
int age;
// Default constructor
public Student() {
name = "Unknown";
age = 0;
System.out.println("Default constructor called");
}
}
// Sử dụng
Student student = new Student(); // Output: Default constructor called
Nếu bạn không viết bất kỳ constructor nào, Java tự động tạo một default constructor rỗng:
public ClassName() {
// empty
}
Nhưng nếu bạn đã viết bất kỳ constructor nào, Java sẽ không tự động tạo default constructor nữa!
2. Parameterized Constructor
Constructor có tham số để khởi tạo giá trị cụ thể.
class Student {
String name;
int age;
String studentId;
// Parameterized constructor
public Student(String name, int age, String studentId) {
this.name = name;
this.age = age;
this.studentId = studentId;
System.out.println("Parameterized constructor called");
}
}
// Sử dụng
Student student = new Student("Nguyễn Văn An", 20, "SV001");
Copy Constructor
Java không có built-in copy constructor như C++, nhưng ta có thể tự tạo:
public class Student {
private String name;
private int age;
// Copy constructor
public Student(Student other) {
this.name = other.name;
this.age = other.age;
}
}
Student original = new Student("Alice", 20);
Student copy = new Student(original); // Copy constructor
this.name = name; - this dùng để phân biệt:
this.name: Field của objectname: Tham số của constructor
Chúng ta sẽ học chi tiết về this ở bài sau.
3. Constructor Overloading
Một class có thể có nhiều constructors với số lượng hoặc kiểu tham số khác nhau.
class BankAccount {
String accountNumber;
String ownerName;
double balance;
// Constructor 1: No-arg
public BankAccount() {
this.accountNumber = "UNKNOWN";
this.ownerName = "UNKNOWN";
this.balance = 0.0;
}
// Constructor 2: 2 parameters
public BankAccount(String accountNumber, String ownerName) {
this.accountNumber = accountNumber;
this.ownerName = ownerName;
this.balance = 0.0; // Default balance
}
// Constructor 3: 3 parameters
public BankAccount(String accountNumber, String ownerName, double initialBalance) {
this.accountNumber = accountNumber;
this.ownerName = ownerName;
this.balance = initialBalance;
}
}
// Sử dụng
BankAccount acc1 = new BankAccount();
BankAccount acc2 = new BankAccount("ACC001", "Nguyễn Văn A");
BankAccount acc3 = new BankAccount("ACC002", "Trần Thị B", 5000.0);
Constructor Chaining với this()
Một constructor có thể gọi constructor khác trong cùng class bằng this().
class Product {
String name;
double price;
int quantity;
// Constructor 1
public Product(String name) {
this(name, 0.0); // Gọi constructor 2
}
// Constructor 2
public Product(String name, double price) {
this(name, price, 0); // Gọi constructor 3
}
// Constructor 3 (main constructor)
public Product(String name, double price, int quantity) {
this.name = name;
this.price = price;
this.quantity = quantity;
}
}
- Đặt logic chính trong constructor đầy đủ nhất
- Các constructor khác gọi đến constructor chính qua
this() this()phải là câu lệnh đầu tiên trong constructor
Ngoài constructors, một pattern phổ biến là dùng static factory methods để tạo objects:
// Thay vì: new Boolean(true)
Boolean b = Boolean.valueOf(true); // Static factory method
// Ưu điểm: Descriptive names, caching, return subtypes
Pattern này sẽ được học chi tiết ở Module Design Patterns.
Thứ tự khởi tạo Object (Initialization Order)
Khi tạo object bằng new, Java thực hiện theo thứ tự cụ thể được định nghĩa trong JLS §12.5.
Thứ tự khởi tạo chính xác khi tạo object:
- Cấp phát bộ nhớ cho object trên heap
- Khởi tạo tất cả instance variables với giá trị mặc định (0, false, null)
- Thực thi constructor theo thứ tự:
- Gọi
super()(constructor của class cha) - ngầm định hoặc tường minh - Khởi tạo instance variables theo thứ tự khai báo
- Thực thi instance initializer blocks
{}theo thứ tự khai báo - Thực thi constructor body
- Gọi
Thứ tự khởi tạo đầy đủ (kể cả static)
Khi class được load lần đầu:
1. Static members (Class initialization - chạy 1 lần duy nhất):
- Static variables (khởi tạo theo thứ tự khai báo)
- Static blocks
static {}(theo thứ tự khai báo)
2. Instance members (Object initialization - mỗi lần new):
- Instance variables (khởi tạo theo thứ tự khai báo)
- Instance initializer blocks
{}(theo thứ tự khai báo) - Constructor body
Ví dụ chi tiết: Thứ tự khởi tạo
class InitializationOrder {
// 1. Static variables - chạy khi class load
private static int staticVar = getStaticValue();
// 2. Static block - chạy sau static variables
static {
System.out.println("3. Static block executed");
staticVar += 10;
}
// 3. Instance variables - chạy khi tạo object
private int instanceVar = getInstanceValue();
// 4. Instance initializer block - chạy sau instance variables
{
System.out.println("6. Instance initializer block executed");
instanceVar += 5;
}
// 5. Constructor - chạy cuối cùng
public InitializationOrder() {
System.out.println("7. Constructor executed");
System.out.println(" staticVar = " + staticVar);
System.out.println(" instanceVar = " + instanceVar);
}
private static int getStaticValue() {
System.out.println("2. Static variable initialized");
return 100;
}
private int getInstanceValue() {
System.out.println("5. Instance variable initialized");
return 50;
}
}
public class InitDemo {
public static void main(String[] args) {
System.out.println("1. Main started");
System.out.println("4. Creating first object...");
InitializationOrder obj1 = new InitializationOrder();
System.out.println("\n8. Creating second object...");
InitializationOrder obj2 = new InitializationOrder();
}
}
Output:
1. Main started
2. Static variable initialized
3. Static block executed
4. Creating first object...
5. Instance variable initialized
6. Instance initializer block executed
7. Constructor executed
staticVar = 110
instanceVar = 55
8. Creating second object...
5. Instance variable initialized
6. Instance initializer block executed
7. Constructor executed
staticVar = 110
instanceVar = 55
Giải thích:
- Static members (2, 3) chỉ chạy 1 lần khi class load
- Instance members (5, 6, 7) chạy mỗi lần tạo object
class Parent {
public Parent() {
System.out.println("Parent constructor");
}
public Parent(String name) {
System.out.println("Parent constructor: " + name);
}
}
class Child extends Parent {
public Child() {
// super(); // ✅ Ngầm định - Java tự động thêm nếu không có this() hay super()
System.out.println("Child constructor");
}
public Child(String name) {
super(name); // ✅ OK - gọi Parent(String)
System.out.println("Child constructor: " + name);
}
public Child(String name, int age) {
this(name); // ✅ OK - gọi Child(String)
// super(name); // ❌ COMPILE ERROR - không thể có cả this() và super()
System.out.println("Age: " + age);
}
// ❌ COMPILE ERROR examples:
/*
public Child(int x) {
System.out.println("Before");
this(); // ❌ this() phải là câu lệnh ĐẦU TIÊN
}
public Child(double x) {
super();
this(); // ❌ Không thể có cả super() và this()
}
public Child(boolean flag) {
// this(new Child()); // ❌ Constructor không thể gọi chính nó (recursive)
}
*/
}
Quy tắc OCP quan trọng:
this()hoặcsuper()PHẢI là câu lệnh đầu tiên trong constructor- Không thể có cả
this()vàsuper()trong cùng constructor - Nếu không gọi
this()haysuper(), Java tự động thêmsuper()(no-arg) - Constructor không thể gọi chính nó (trực tiếp hay gián tiếp)
Methods (Phương thức)
Method là một khối code thực hiện một nhiệm vụ cụ thể. Methods định nghĩa behavior (hành vi) của object.
Cú pháp Method
[access_modifier] returnType methodName(parameter_list) {
// method body
return value; // nếu returnType không phải void
}
Các thành phần:
- Access modifier:
public,private,protected, hoặc default - Return type: Kiểu dữ liệu trả về (hoặc
voidnếu không trả về gì) - Method name: Tên method (camelCase convention)
- Parameter list: Danh sách tham số (có thể rỗng)
- Method body: Code thực thi
Ví dụ cơ bản
class Calculator {
// Method không trả về (void)
public void printWelcome() {
System.out.println("Welcome to Calculator!");
}
// Method trả về int
public int add(int a, int b) {
return a + b;
}
// Method trả về double
public double divide(double a, double b) {
if (b != 0) {
return a / b;
} else {
System.out.println("Cannot divide by zero!");
return 0.0;
}
}
// Method trả về boolean
public boolean isEven(int number) {
return number % 2 == 0;
}
}
// Sử dụng
Calculator calc = new Calculator();
calc.printWelcome(); // Welcome to Calculator!
int sum = calc.add(5, 3); // sum = 8
double result = calc.divide(10, 2); // result = 5.0
boolean check = calc.isEven(7); // check = false
Method Overloading
Method Overloading là kỹ thuật có nhiều methods cùng tên nhưng khác tham số (số lượng hoặc kiểu).
Method overloading là một dạng của polymorphism (đa hình) được quyết định lúc compile time.
Quy tắc Overloading
Methods được coi là overloaded nếu:
- Cùng tên
- Khác method signature:
- Số lượng tham số khác nhau, HOẶC
- Kiểu dữ liệu tham số khác nhau, HOẶC
- Thứ tự kiểu tham số khác nhau
Return type KHÔNG phải là tiêu chí phân biệt overloading!
// ❌ Compile error - chỉ khác return type
int calculate(int a) { return a * 2; }
double calculate(int a) { return a * 2.0; }
Ví dụ Method Overloading
class MathUtils {
// Overloading theo số lượng tham số
public int multiply(int a, int b) {
return a * b;
}
public int multiply(int a, int b, int c) {
return a * b * c;
}
// Overloading theo kiểu tham số
public double multiply(double a, double b) {
return a * b;
}
// Overloading theo thứ tự tham số
public void display(String name, int age) {
System.out.println("Name: " + name + ", Age: " + age);
}
public void display(int age, String name) {
System.out.println("Age: " + age + ", Name: " + name);
}
}
// Sử dụng
MathUtils math = new MathUtils();
System.out.println(math.multiply(2, 3)); // 6
System.out.println(math.multiply(2, 3, 4)); // 24
System.out.println(math.multiply(2.5, 3.5)); // 8.75
math.display("An", 20); // Name: An, Age: 20
math.display(20, "An"); // Age: 20, Name: An
Ví dụ thực tế: Area Calculator
class AreaCalculator {
// Diện tích hình vuông
public double calculateArea(double side) {
return side * side;
}
// Diện tích hình chữ nhật
public double calculateArea(double width, double height) {
return width * height;
}
// Diện tích hình tròn
public double calculateArea(double radius, boolean isCircle) {
if (isCircle) {
return Math.PI * radius * radius;
}
return 0;
}
// Diện tích tam giác
public double calculateTriangleArea(double base, double height) {
return 0.5 * base * height;
}
}
// Sử dụng
AreaCalculator areaCalc = new AreaCalculator();
System.out.println("Square: " + areaCalc.calculateArea(5)); // 25.0
System.out.println("Rectangle: " + areaCalc.calculateArea(4, 6)); // 24.0
System.out.println("Circle: " + areaCalc.calculateArea(3, true)); // 28.27...
System.out.println("Triangle: " + areaCalc.calculateTriangleArea(4, 5)); // 10.0
Pass by Value trong Java
Java luôn luôn là pass by value (truyền theo giá trị), nhưng hành vi khác nhau giữa primitive types và reference types.
Pass by Value với Primitive Types
Giá trị của biến được copy sang tham số. Thay đổi tham số không ảnh hưởng biến gốc.
class PassByValueDemo {
public void modifyPrimitive(int num) {
num = 100; // Chỉ thay đổi bản copy
System.out.println("Inside method: " + num); // 100
}
public static void main(String[] args) {
PassByValueDemo demo = new PassByValueDemo();
int number = 10;
demo.modifyPrimitive(number);
System.out.println("Outside method: " + number); // 10 (không đổi)
}
}
Pass by Value với Reference Types
Reference (địa chỉ) được copy sang tham số. Cả hai references trỏ đến cùng một object, nên thay đổi object sẽ ảnh hưởng.
class Student {
String name;
int age;
}
class ReferenceDemo {
public void modifyStudent(Student s) {
s.age = 25; // Thay đổi object mà s trỏ đến
System.out.println("Inside method - age: " + s.age); // 25
}
public void reassignStudent(Student s) {
s = new Student(); // Gán reference mới (chỉ ảnh hưởng bản copy)
s.name = "New Student";
s.age = 30;
}
public static void main(String[] args) {
ReferenceDemo demo = new ReferenceDemo();
Student student = new Student();
student.name = "An";
student.age = 20;
demo.modifyStudent(student);
System.out.println("Outside method - age: " + student.age); // 25 (đã đổi!)
demo.reassignStudent(student);
System.out.println("Outside method - name: " + student.name); // "An" (không đổi)
}
}
Output:
Inside method - age: 25
Outside method - age: 25
Outside method - name: An
- Primitive: Copy giá trị → thay đổi không ảnh hưởng ngoài
- Reference: Copy địa chỉ → thay đổi object ảnh hưởng ngoài
- Reassign reference: Không ảnh hưởng bên ngoài (chỉ đổi bản copy)
Varargs (Variable Arguments)
Varargs cho phép truyền số lượng tham số không xác định vào method.
Cú pháp Varargs
returnType methodName(dataType... variableName) {
// variableName được xử lý như một array
}
Ví dụ Varargs
class VarargsDemo {
// Method nhận số lượng int không xác định
public int sum(int... numbers) {
int total = 0;
for (int num : numbers) {
total += num;
}
return total;
}
// Varargs kết hợp với tham số thông thường
public void displayInfo(String title, String... names) {
System.out.println(title + ":");
for (String name : names) {
System.out.println("- " + name);
}
}
public static void main(String[] args) {
VarargsDemo demo = new VarargsDemo();
System.out.println(demo.sum(1, 2, 3)); // 6
System.out.println(demo.sum(1, 2, 3, 4, 5)); // 15
System.out.println(demo.sum()); // 0 (empty)
demo.displayInfo("Students", "An", "Bình", "Cường");
// Output:
// Students:
// - An
// - Bình
// - Cường
}
}
Quy tắc Varargs
- Chỉ có một varargs trong method signature
- Varargs phải là tham số cuối cùng
// ✅ Đúng
void method(int a, String... names) { }
// ❌ Sai - varargs không phải cuối cùng
void method(String... names, int a) { }
// ❌ Sai - nhiều hơn 1 varargs
void method(int... nums, String... names) { }
Covariant Return Types (Java 5+)
Từ Java 5, method override có thể trả về subtype của return type gốc.
class Animal {
public Animal getAnimal() {
return new Animal();
}
}
class Dog extends Animal {
@Override
public Dog getAnimal() { // ✅ Covariant return - Dog is subtype of Animal
return new Dog();
}
}
// Ví dụ thực tế: clone()
class Product implements Cloneable {
private String name;
public Product(String name) {
this.name = name;
}
@Override
public Product clone() { // Covariant return - was Object in Cloneable
try {
return (Product) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
Lợi ích: Không cần type casting khi gọi method.
Dog dog = new Dog();
Dog returnedDog = dog.getAnimal(); // Không cần cast (Dog)
Method Signature vs Method Descriptor
Khái niệm OCP quan trọng: Phân biệt signature và descriptor.
Method Signature (Chữ ký phương thức)
Method signature là sự kết hợp của:
- Method name
- Parameter list (số lượng, kiểu, thứ tự)
Method signature KHÔNG bao gồm:
- Return type
- Access modifier
- Tên tham số (chỉ có kiểu)
- Exceptions
Ví dụ Method Signature
// Signature: calculate(int, int)
public int calculate(int a, int b) { return a + b; }
// Signature khác: calculate(double, double)
public double calculate(double a, double b) { return a + b; }
// ❌ Signature giống calculate(int, int) - compile error!
public double calculate(int x, int y) { return x * y; }
Method Descriptor (Chi tiết kỹ thuật)
Method descriptor bao gồm:
- Method name
- Parameter types
- Return type (khác với signature!)
Dùng trong:
- JVM bytecode
- Reflection API
- Method references
class Example {
public int add(int a, int b) { return a + b; }
// Signature: add(int, int)
// Descriptor: (II)I // (int, int) returns int
}
- Signature: Dùng để phân biệt overloading (compile-time)
- Descriptor: Dùng trong JVM bytecode (runtime)
OCP exam chủ yếu hỏi về signature (overloading rules).
class OverloadingTraps {
// ✅ OK - khác signature (int vs Integer)
public void process(int x) {
System.out.println("int: " + x);
}
public void process(Integer x) {
System.out.println("Integer: " + x);
}
// ❌ COMPILE ERROR - chỉ khác return type
/*
public int getValue() { return 1; }
public double getValue() { return 1.0; } // Error!
*/
// ✅ OK - khác parameter order
public void display(String s, int i) {}
public void display(int i, String s) {}
// ❌ COMPILE ERROR - varargs ambiguity
/*
public void print(int... nums) {}
public void print(int[] nums) {} // Error - varargs is array!
*/
public static void main(String[] args) {
OverloadingTraps obj = new OverloadingTraps();
obj.process(10); // Calls process(int)
obj.process(Integer.valueOf(10)); // Calls process(Integer)
// Autoboxing vs method selection
Integer x = 10;
obj.process(x); // Calls process(Integer) - exact match
}
}
Ví dụ tổng hợp: Employee Management
class Employee {
private String employeeId;
private String name;
private String department;
private double salary;
private int yearsOfExperience;
// Constructor 1: Full parameters
public Employee(String employeeId, String name, String department,
double salary, int yearsOfExperience) {
this.employeeId = employeeId;
this.name = name;
this.department = department;
this.salary = salary;
this.yearsOfExperience = yearsOfExperience;
}
// Constructor 2: Without experience (default 0)
public Employee(String employeeId, String name, String department, double salary) {
this(employeeId, name, department, salary, 0);
}
// Constructor 3: Entry level (default department and salary)
public Employee(String employeeId, String name) {
this(employeeId, name, "General", 1000.0, 0);
}
// Method: Display employee info
public void displayInfo() {
System.out.println("=== Employee Information ===");
System.out.println("ID: " + employeeId);
System.out.println("Name: " + name);
System.out.println("Department: " + department);
System.out.println("Salary: $" + salary);
System.out.println("Experience: " + yearsOfExperience + " years");
}
// Method overloading: Increase salary
public void increaseSalary(double amount) {
salary += amount;
System.out.println("Salary increased by $" + amount);
}
public void increaseSalary(double percentage, boolean isPercentage) {
if (isPercentage) {
double increase = salary * (percentage / 100);
salary += increase;
System.out.println("Salary increased by " + percentage + "%");
}
}
// Method: Calculate bonus
public double calculateBonus() {
if (yearsOfExperience < 2) {
return salary * 0.05; // 5%
} else if (yearsOfExperience < 5) {
return salary * 0.10; // 10%
} else {
return salary * 0.15; // 15%
}
}
// Method: Promote employee
public void promote(String newDepartment, double salaryIncrease) {
this.department = newDepartment;
this.salary += salaryIncrease;
System.out.println(name + " promoted to " + newDepartment);
System.out.println("New salary: $" + salary);
}
}
public class EmployeeManagement {
public static void main(String[] args) {
// Sử dụng các constructors khác nhau
Employee emp1 = new Employee("E001", "Nguyễn Văn An", "IT", 2000, 3);
Employee emp2 = new Employee("E002", "Trần Thị Bình", "HR", 1800);
Employee emp3 = new Employee("E003", "Lê Văn Cường");
// Test methods
emp1.displayInfo();
System.out.println("Bonus: $" + emp1.calculateBonus());
emp1.increaseSalary(200);
emp1.increaseSalary(10, true); // 10% increase
System.out.println("\n" + "=".repeat(40) + "\n");
emp2.displayInfo();
emp2.promote("Management", 500);
System.out.println("\n" + "=".repeat(40) + "\n");
emp3.displayInfo();
}
}
Bài tập thực hành
Bài 1: Rectangle Class
Tạo class Rectangle với:
- Fields:
width,height - Constructors:
Rectangle(): width = height = 1Rectangle(double side): Hình vuôngRectangle(double width, double height)
- Methods:
double getArea()double getPerimeter()void scale(double factor): Nhân width/height với factorboolean isSquare()
Bài 2: Method Overloading - StringUtils
Tạo class StringUtils với các methods overloaded format():
format(String text): Capitalize first letterformat(String text, boolean uppercase): Uppercase nếu true, lowercase nếu falseformat(String... words): Nối các words bằng dấu cáchformat(String text, int maxLength): Cắt text nếu dài hơn maxLength
Bài 3: Pass by Value Test
Viết code minh họa:
- Method thay đổi primitive không ảnh hưởng ngoài
- Method thay đổi object ảnh hưởng ngoài
- Method reassign reference không ảnh hưởng ngoài
Bài 4: Calculator với Varargs
Tạo class Calculator với:
int sum(int... numbers): Tổngdouble average(double... numbers): Trung bìnhint max(int... numbers): Số lớn nhấtint min(int... numbers): Số nhỏ nhất
Tổng kết
Key Takeaways
- Constructor: Method đặc biệt khởi tạo object, cùng tên class, không có return type
- Constructor types:
- Default (no-arg): Không tham số
- Parameterized: Có tham số
- Overloaded: Nhiều constructors khác tham số
- Constructor chaining: Dùng
this()để gọi constructor khác - Method: Định nghĩa behavior, có return type, có thể có parameters
- Method Overloading: Cùng tên, khác signature (số lượng/kiểu tham số)
- Pass by Value:
- Primitive: Copy giá trị
- Reference: Copy địa chỉ (thay đổi object ảnh hưởng ngoài)
- Varargs: Cho phép số lượng tham số không xác định (
type... name) - Method Signature: Tên method + parameter list (không bao gồm return type)
Bài tiếp theo
Trong bài tiếp theo, chúng ta sẽ tìm hiểu về Encapsulation và Access Modifiers - cách bảo vệ dữ liệu và kiểm soát truy cập trong OOP.
Bài 4: Encapsulation và Access Modifiers →
Đọc thêm
"A method should do one thing, do it well, and do it only." — Robert C. Martin, Clean Code