Chuyển tới nội dung chính

this và static Keywords

Mục tiêu bài học

Sau bài này, bạn sẽ:

  • Sử dụng this keyword để phân biệt instance variables và parameters, constructor chaining, và method chaining
  • Hiểu sự khác biệt giữa static members (class-level) và instance members (object-level)
  • Khai báo và sử dụng static fields, static methods, và static blocks
  • Biết khi nào nên dùng static vs instance dựa trên yêu cầu của bài toán
  • Áp dụng static import để truy cập static members mà không cần class name

Bài trước: Encapsulation và Access Modifiers — Đã học về 4 access modifiers và getters/setters. Bài này sẽ tìm hiểu về this và static keywords - hai khái niệm quan trọng trong Java OOP.

this Keyword

this là một reference (tham chiếu) đến object hiện tại (current object) đang gọi method hoặc constructor.

Ẩn dụ thực tế

Hãy nghĩ về đại từ nhân xưng "tôi/mình":

  • Khi bạn nói "Tôi đang học Java", "tôi" chỉ chính bạn
  • Trong class, this chỉ object đang thực thi code

Công dụng của this

  1. Phân biệt instance variable và parameter
  2. Gọi constructor khác (constructor chaining)
  3. Truyền current object làm tham số
  4. Trả về current object từ method

1. this để phân biệt Instance Variable vs Parameter

Khi tên parameter trùng với tên field, dùng this để phân biệt.

Ví dụ cơ bản

class Student {
private String name; // instance variable
private int age;

// Constructor
public Student(String name, int age) {
this.name = name; // this.name = instance variable
// name = parameter
this.age = age;
}

// Setter
public void setName(String name) {
this.name = name; // Gán parameter vào instance variable
}
}

Giải thích:

  • this.name: Instance variable của object
  • name: Parameter của constructor/method

Không dùng this sẽ như thế nào?

class Student {
private String name;

public void setName(String name) {
name = name; // ❌ Gán parameter vào chính nó - vô nghĩa!
// this.name vẫn là null!
}
}
Lưu ý

Nếu tên parameter khác tên field, không bắt buộc dùng this:

public void setName(String n) {
name = n; // ✅ OK - không trung tên
}

Nhưng dùng this làm code rõ ràng hơn!

Ví dụ: Person Class

class Person {
private String firstName;
private String lastName;
private int age;

public Person(String firstName, String lastName, int age) {
this.firstName = firstName; // this phân biệt field vs parameter
this.lastName = lastName;
this.age = age;
}

public void updateInfo(String firstName, String lastName, int age) {
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
System.out.println("Info updated for " + this.firstName);
}

public void displayInfo() {
// Có thể bỏ this khi không trung tên, nhưng dùng để rõ ràng
System.out.println("Name: " + this.firstName + " " + this.lastName);
System.out.println("Age: " + this.age);
}
}

2. this() - Constructor Chaining

this() gọi constructor khác trong cùng class.

Quy tắc

  1. this() phải là câu lệnh đầu tiên trong constructor
  2. Tránh circular calls (A gọi B, B gọi A)

Ví dụ cơ bản

class Rectangle {
private double width;
private double height;

// Constructor 1: Chỉ nhận width (hình vuông)
public Rectangle(double side) {
this(side, side); // Gọi constructor 2
}

// Constructor 2: Nhận cả width và height
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}

// Constructor 3: Default (1x1)
public Rectangle() {
this(1.0, 1.0); // Gọi constructor 2
}
}

// Sử dụng
Rectangle rect1 = new Rectangle(); // Constructor 3 → 2
Rectangle rect2 = new Rectangle(5); // Constructor 1 → 2
Rectangle rect3 = new Rectangle(4, 6); // Constructor 2 trực tiếp

Ví dụ: Product với nhiều Constructors

class Product {
private String productId;
private String name;
private double price;
private int quantity;
private String category;

// Constructor đầy đủ nhất (main constructor)
public Product(String productId, String name, double price, int quantity, String category) {
this.productId = productId;
this.name = name;
this.price = price;
this.quantity = quantity;
this.category = category;
}

// Constructor không có category (default: "General")
public Product(String productId, String name, double price, int quantity) {
this(productId, name, price, quantity, "General");
}

// Constructor không có quantity (default: 0)
public Product(String productId, String name, double price) {
this(productId, name, price, 0, "General");
}

// Constructor chỉ có id và name
public Product(String productId, String name) {
this(productId, name, 0.0, 0, "General");
}

public void displayInfo() {
System.out.println("Product: " + name + " (" + productId + ")");
System.out.println("Price: $" + price + ", Quantity: " + quantity);
System.out.println("Category: " + category);
}
}
Best Practice
  • Đặt logic chính trong constructor có nhiều tham số nhất
  • Các constructor khác gọi đến constructor chính qua this()
  • Giảm code duplication

3. this làm tham số

Truyền current object vào method khác.

class Printer {
public void print(Document doc) {
System.out.println("Printing: " + doc.getTitle());
}
}

class Document {
private String title;

public Document(String title) {
this.title = title;
}

public String getTitle() {
return title;
}

public void printDocument(Printer printer) {
printer.print(this); // Truyền current object (Document)
}
}

// Sử dụng
Document doc = new Document("Report.pdf");
Printer printer = new Printer();
doc.printDocument(printer); // Output: Printing: Report.pdf

4. this để return current object (Method Chaining)

Method chaining (fluent API) cho phép gọi nhiều methods liên tiếp.

class StringBuilder {
private String value = "";

public StringBuilder append(String str) {
value += str;
return this; // Trả về current object
}

public StringBuilder reverse() {
value = new StringBuilder(value).reverse().toString();
return this; // Trả về current object
}

public String build() {
return value;
}
}

// Sử dụng - method chaining
String result = new StringBuilder()
.append("Hello")
.append(" ")
.append("World")
.reverse()
.build();

System.out.println(result); // dlroW olleH

Ví dụ: Query Builder Pattern

class QueryBuilder {
private String table;
private String columns = "*";
private String whereClause = "";
private String orderBy = "";

public QueryBuilder from(String table) {
this.table = table;
return this;
}

public QueryBuilder select(String columns) {
this.columns = columns;
return this;
}

public QueryBuilder where(String condition) {
this.whereClause = " WHERE " + condition;
return this;
}

public QueryBuilder orderBy(String column) {
this.orderBy = " ORDER BY " + column;
return this;
}

public String build() {
return "SELECT " + columns + " FROM " + table + whereClause + orderBy;
}
}

// Sử dụng
String sql = new QueryBuilder()
.select("name, age")
.from("students")
.where("age > 18")
.orderBy("name")
.build();

System.out.println(sql);
// Output: SELECT name, age FROM students WHERE age > 18 ORDER BY name

static Keyword

static nghĩa là "thuộc về class", không thuộc về bất kỳ object nào.

Ẩn dụ thực tế

Hãy nghĩ về trường học:

  • Instance variable: Tên, tuổi của mỗi học sinh (khác nhau)
  • Static variable: Tên trường, số lượng học sinh chung cho tất cả (giống nhau)

Static vs Instance

Instance (non-static)Static
Thuộc vềObject cụ thểClass
Truy cập quaobjectName.memberClassName.member
Bộ nhớMỗi object có bản copy riêngChỉ 1 copy duy nhất cho tất cả objects
Khởi tạoKhi tạo object (new)Khi class được load lần đầu
Dùng this✅ Có❌ Không

Static Fields (Class Variables)

Static field được chia sẻ bởi tất cả objects của class.

Ví dụ: Đếm số lượng objects

class Student {
private String name;
private static int studentCount = 0; // Static field - chung cho tất cả

public Student(String name) {
this.name = name;
studentCount++; // Tăng mỗi khi tạo object mới
}

public static int getStudentCount() {
return studentCount;
}
}

// Sử dụng
Student s1 = new Student("An");
Student s2 = new Student("Bình");
Student s3 = new Student("Cường");

System.out.println("Total students: " + Student.getStudentCount()); // 3

Giải thích:

  • studentCountstatic → chỉ có 1 biến duy nhất, không phải mỗi object có 1 copy
  • Mỗi lần tạo object mới, studentCount tăng lên
  • Truy cập qua ClassName.staticField (không cần object)

Ví dụ: Configuration Constants

class DatabaseConfig {
// Static constants (final)
public static final String DB_URL = "jdbc:mysql://localhost:3306/mydb";
public static final String DB_USER = "admin";
public static final int MAX_CONNECTIONS = 100;
public static final double TIMEOUT_SECONDS = 30.0;

// Static variable
private static int currentConnections = 0;

public static void incrementConnections() {
if (currentConnections < MAX_CONNECTIONS) {
currentConnections++;
}
}

public static int getCurrentConnections() {
return currentConnections;
}
}

// Sử dụng
System.out.println(DatabaseConfig.DB_URL);
System.out.println(DatabaseConfig.MAX_CONNECTIONS);
DatabaseConfig.incrementConnections();
static final = Constants

static final thường dùng cho constants (hằng số):

  • static: Chung cho tất cả
  • final: Không thể thay đổi
  • Convention: Tên viết HOA, ngăn cách bằng underscore (MAX_VALUE)

Static Methods

Static method thuộc về class, không thuộc object.

Quy tắc Static Methods

  1. Không thể dùng this (không có object hiện tại)
  2. Chỉ truy cập được static members (fields và methods)
  3. Không thể override (sẽ học ở module kế thừa)

Ví dụ cơ bản

class MathUtils {
// Static method - không cần object
public static int add(int a, int b) {
return a + b;
}

public static int max(int a, int b) {
return (a > b) ? a : b;
}

public static double circleArea(double radius) {
return Math.PI * radius * radius;
}
}

// Sử dụng - không cần new object
int sum = MathUtils.add(5, 3); // 8
int maximum = MathUtils.max(10, 20); // 20
double area = MathUtils.circleArea(5); // 78.54...

Static method KHÔNG thể truy cập instance members

class Example {
private int instanceVar = 10;
private static int staticVar = 20;

public static void staticMethod() {
System.out.println(staticVar); // ✅ OK - static access static
// System.out.println(instanceVar); // ❌ Error - static không access instance
// System.out.println(this.instanceVar); // ❌ Error - không có 'this' trong static
}

public void instanceMethod() {
System.out.println(instanceVar); // ✅ OK - instance access instance
System.out.println(staticVar); // ✅ OK - instance access static
}
}

Ví dụ: Utility Class

class StringUtils {
// Private constructor - ngăn không cho new object
private StringUtils() {
throw new UnsupportedOperationException("Utility class");
}

public static boolean isEmpty(String str) {
return str == null || str.trim().isEmpty();
}

public static String capitalize(String str) {
if (isEmpty(str)) {
return str;
}
return str.substring(0, 1).toUpperCase() + str.substring(1).toLowerCase();
}

public static String reverse(String str) {
if (isEmpty(str)) {
return str;
}
return new StringBuilder(str).reverse().toString();
}

public static int countWords(String str) {
if (isEmpty(str)) {
return 0;
}
return str.trim().split("\\s+").length;
}
}

// Sử dụng
System.out.println(StringUtils.capitalize("hello")); // Hello
System.out.println(StringUtils.reverse("Java")); // avaJ
System.out.println(StringUtils.countWords("Hello World")); // 2
// StringUtils utils = new StringUtils(); // ❌ Error - constructor private

Static Blocks (Static Initializers)

Static block được chạy một lần duy nhất khi class được load vào bộ nhớ.

Cú pháp

class ClassName {
static {
// Code chạy khi class load
}
}

Ví dụ: Khởi tạo Static Variables

class Configuration {
private static final String CONFIG_FILE = "config.properties";
private static String appName;
private static int maxUsers;
private static boolean initialized;

// Static block - chạy khi class load
static {
System.out.println("Loading configuration...");
appName = "MyApp";
maxUsers = 100;
initialized = true;
System.out.println("Configuration loaded!");
}

public static void displayConfig() {
System.out.println("App: " + appName);
System.out.println("Max users: " + maxUsers);
System.out.println("Initialized: " + initialized);
}
}

// Sử dụng
Configuration.displayConfig();
// Output:
// Loading configuration...
// Configuration loaded!
// App: MyApp
// Max users: 100
// Initialized: true

Nhiều Static Blocks

class Demo {
private static int value;

static {
System.out.println("Static block 1");
value = 10;
}

static {
System.out.println("Static block 2");
value += 5;
}

static {
System.out.println("Static block 3");
value *= 2;
}

public static void main(String[] args) {
System.out.println("Main method");
System.out.println("Value: " + value);
}
}

// Output:
// Static block 1
// Static block 2
// Static block 3
// Main method
// Value: 30

Thứ tự thực thi:

  1. Static blocks (theo thứ tự khai báo)
  2. main() method

Static Initialization Order Across Classes

Khi có nhiều classes, thứ tự khởi tạo static members phức tạp hơn.

🔥 Bẫy OCP: Static initialization order
class ClassA {
static {
System.out.println("ClassA static block");
}

static int valueA = getValueA();

static int getValueA() {
System.out.println("ClassA getValueA()");
return 10;
}
}

class ClassB {
static {
System.out.println("ClassB static block");
}

static int valueB = ClassA.valueA * 2; // Tham chiếu ClassA

static {
System.out.println("ClassB valueB = " + valueB);
}
}

public class StaticInitOrder {
public static void main(String[] args) {
System.out.println("Main started");
System.out.println("Accessing ClassB.valueB: " + ClassB.valueB);
System.out.println("Accessing ClassA.valueA: " + ClassA.valueA);
}
}

Output:

Main started
ClassB static block
ClassA static block
ClassA getValueA()
ClassB valueB = 20
Accessing ClassB.valueB: 20
Accessing ClassA.valueA: 10

Giải thích:

  1. Main chạy, truy cập ClassB.valueB
  2. ClassB load → chạy static block 1
  3. Khởi tạo valueB → cần ClassA.valueA
  4. ClassA load → static block → getValueA()
  5. Quay lại ClassB → static block 2
  6. Truy cập ClassA.valueA → ClassA đã load, không chạy lại static

Ví dụ: Circular static dependency (bẫy nguy hiểm)

class ClassX {
static int x = ClassY.y + 1; // Tham chiếu ClassY

static {
System.out.println("ClassX static: x = " + x);
}
}

class ClassY {
static int y = ClassX.x + 1; // Tham chiếu ClassX

static {
System.out.println("ClassY static: y = " + y);
}
}

public class CircularInit {
public static void main(String[] args) {
System.out.println("Final x = " + ClassX.x);
System.out.println("Final y = " + ClassY.y);
}
}

Output (bất ngờ!):

ClassX static: x = 1
ClassY static: y = 2
Final x = 1
Final y = 2

Giải thích:

  1. Truy cập ClassX.x
  2. ClassX load → khởi tạo x = ClassY.y + 1
  3. ClassY chưa load → y mặc định = 0
  4. x = 0 + 1 = 1
  5. ClassY load → khởi tạo y = ClassX.x + 1
  6. ClassX đã load → x = 1
  7. y = 1 + 1 = 2
Tránh circular static dependencies

Đừng để classes phụ thuộc lẫn nhau trong static initialization - rất khó debug!

Class Loading Mechanism (Cơ chế nạp class)

🔬 Nâng cao

Phần này giới thiệu cơ chế class loading của JVM. Nếu bạn mới bắt đầu, có thể bỏ qua và quay lại sau. Chi tiết sẽ được trình bày ở Module JVM Internals.

Java sử dụng lazy loading - class chỉ được load khi cần thiết.

Khi nào class được load?

  1. Tạo instance với new
  2. Truy cập static member (field hoặc method)
  3. Reflection (Class.forName())
  4. JVM startup (main class)
  5. Subclass được load → superclass cũng load
class Parent {
static {
System.out.println("Parent loaded");
}
}

class Child extends Parent {
static {
System.out.println("Child loaded");
}
}

public class ClassLoadingDemo {
public static void main(String[] args) {
System.out.println("Main started");

// Class chưa được load
System.out.println("Before new Child()");

new Child(); // Load Child → tự động load Parent trước
}
}

Output:

Main started
Before new Child()
Parent loaded
Child loaded

Class loading phases:

  1. Loading: Đọc .class file vào memory
  2. Linking: Verify, prepare, resolve
  3. Initialization: Chạy static blocks và khởi tạo static fields

Singleton Pattern Basics

Singleton đảm bảo một class chỉ có đúng 1 instance duy nhất.

Ẩn dụ thực tế

Hãy nghĩ về Tổng thống:

  • Một quốc gia chỉ có 1 tổng thống tại một thời điểm
  • Ai cũng truy cập cùng 1 người (cùng instance)

Singleton với Static

public class Database {
// 1. Static instance - chỉ có 1 instance duy nhất
private static Database instance;

// 2. Private constructor - ngăn new từ bên ngoài
private Database() {
System.out.println("Database connection created");
}

// 3. Public static method - truy cập instance
public static Database getInstance() {
if (instance == null) {
instance = new Database();
}
return instance;
}

public void query(String sql) {
System.out.println("Executing: " + sql);
}
}

// Sử dụng
public class SingletonDemo {
public static void main(String[] args) {
// Database db = new Database(); // ❌ Compile error - private constructor

Database db1 = Database.getInstance(); // Tạo instance lần đầu
Database db2 = Database.getInstance(); // Lấy instance đã tạo

System.out.println(db1 == db2); // true - cùng 1 instance

db1.query("SELECT * FROM users");
}
}

Output:

Database connection created
true
Executing: SELECT * FROM users

Eager vs Lazy Singleton

// Eager initialization - tạo ngay khi class load
class EagerSingleton {
private static final EagerSingleton INSTANCE = new EagerSingleton();

private EagerSingleton() {}

public static EagerSingleton getInstance() {
return INSTANCE;
}
}

// Lazy initialization - tạo khi cần (có thể thread-unsafe)
class LazySingleton {
private static LazySingleton instance;

private LazySingleton() {}

public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}

🔬 Nâng cao — Thread-safe Singleton

Phần này thảo luận về thread safety trong Singleton pattern. Nếu bạn mới bắt đầu, có thể bỏ qua và quay lại sau khi học Module Multithreading.

// Thread-safe lazy (Double-checked locking)
class ThreadSafeSingleton {
private static volatile ThreadSafeSingleton instance;

private ThreadSafeSingleton() {}

public static ThreadSafeSingleton getInstance() {
if (instance == null) {
synchronized (ThreadSafeSingleton.class) {
if (instance == null) {
instance = new ThreadSafeSingleton();
}
}
}
return instance;
}
}
Lưu ý

Chi tiết về Singleton pattern sẽ được học trong Module 15: Design Patterns.

Static Context Restrictions

Static methods có những hạn chế quan trọng.

Tại sao static method không thể dùng this?

class Example {
private int instanceVar = 10;
private static int staticVar = 20;

// Instance method - có 'this'
public void instanceMethod() {
System.out.println(this.instanceVar); // ✅ OK - this tồn tại
System.out.println(instanceVar); // ✅ OK
System.out.println(staticVar); // ✅ OK
}

// Static method - KHÔNG có 'this'
public static void staticMethod() {
// System.out.println(this.instanceVar); // ❌ 'this' không tồn tại
// System.out.println(instanceVar); // ❌ Cần object để access
System.out.println(staticVar); // ✅ OK - static access static

// Muốn access instance member → cần object
Example obj = new Example();
System.out.println(obj.instanceVar); // ✅ OK - có object
}
}

Giải thích:

  • this = reference đến object hiện tại
  • Static method không thuộc object nào → không có this
  • Static method chạy được mà không cần tạo object

Static không thể gọi instance members trực tiếp

class Calculator {
private int lastResult = 0; // Instance variable

public int add(int a, int b) { // Instance method
lastResult = a + b;
return lastResult;
}

public static int multiply(int a, int b) { // Static method
// return add(a, b); // ❌ COMPILE ERROR - static không gọi instance method
// lastResult = a * b; // ❌ COMPILE ERROR - static không access instance field

// ✅ Phải tạo object
Calculator calc = new Calculator();
return calc.add(a, b);
}
}

Quy tắc:

  • ✅ Instance → instance: OK
  • ✅ Instance → static: OK
  • ✅ Static → static: OK
  • ❌ Static → instance: KHÔNG (phải qua object)
🔥 Bẫy OCP: Static method hiding (NOT overriding!)
class Parent {
public static void display() {
System.out.println("Parent static");
}

public void show() {
System.out.println("Parent instance");
}
}

class Child extends Parent {
// Static method hiding (NOT overriding!)
public static void display() {
System.out.println("Child static");
}

// Instance method overriding
@Override
public void show() {
System.out.println("Child instance");
}
}

public class HidingVsOverriding {
public static void main(String[] args) {
Parent p1 = new Parent();
Parent p2 = new Child(); // Upcast
Child c = new Child();

// Static method hiding - gọi theo TYPE của reference
p1.display(); // Parent static
p2.display(); // Parent static (!!!) - hiding, not polymorphism
c.display(); // Child static

// Instance method overriding - polymorphism
p1.show(); // Parent instance
p2.show(); // Child instance (✅ polymorphism works!)
c.show(); // Child instance
}
}

Kết luận OCP:

  • Static methods KHÔNG thể override (chỉ hiding)
  • Static method resolution dựa trên compile-time type (reference type)
  • Instance method resolution dựa trên runtime type (actual object)
  • @Override với static method → compile warning/error

Static import pitfalls

import static java.lang.Math.PI;
import static java.lang.Math.sqrt;
import static java.lang.System.out;

public class StaticImportDemo {
public static void main(String[] args) {
out.println(PI); // 3.141592653589793
out.println(sqrt(16)); // 4.0
}

// ❌ Bẫy: Shadowing
private static final double PI = 3.14; // Che khuất Math.PI

public static void test() {
System.out.println(PI); // 3.14 (local PI, not Math.PI!)
}
}
Static import best practices
  • Chỉ dùng cho constants rõ ràng (Math.PI, Color.RED...)
  • Tránh import * (ambiguity)
  • Tránh shadowing

Khi nào dùng static vs instance?

Dùng static khi:

  1. Utility methods không phụ thuộc vào object state

    Math.max(), Arrays.sort(), Collections.shuffle()
  2. Constants chung cho tất cả

    public static final double PI = 3.14159;
  3. Factory methods tạo objects

    LocalDate.now(), Integer.valueOf(), Optional.of()
  4. Counters đếm số lượng objects

    private static int instanceCount = 0;
  5. Main method - entry point

    public static void main(String[] args)

Dùng instance khi:

  1. Mỗi object có state riêng

    class Student {
    private String name; // Mỗi sinh viên có tên khác nhau
    private int age;
    }
  2. Behavior phụ thuộc vào object state

    public void deposit(double amount) {
    this.balance += amount; // Phụ thuộc vào balance của object
    }

Ví dụ tổng hợp: Bank System

class BankAccount {
// Static members - chung cho tất cả accounts
private static int totalAccounts = 0;
private static double totalBalance = 0.0;
private static final double INTEREST_RATE = 0.05; // 5%

// Instance members - riêng cho mỗi account
private String accountNumber;
private String ownerName;
private double balance;

// Static block
static {
System.out.println("Bank System Initialized");
System.out.println("Interest Rate: " + (INTEREST_RATE * 100) + "%");
}

// Constructor
public BankAccount(String accountNumber, String ownerName, double initialBalance) {
this.accountNumber = accountNumber;
this.ownerName = ownerName;
this.balance = initialBalance;

totalAccounts++;
totalBalance += initialBalance;
}

// Instance method
public void deposit(double amount) {
if (amount > 0) {
this.balance += amount;
totalBalance += amount; // Cập nhật static variable
System.out.println("Deposited: $" + amount);
}
}

// Instance method
public void withdraw(double amount) {
if (amount > 0 && amount <= this.balance) {
this.balance -= amount;
totalBalance -= amount;
System.out.println("Withdrawn: $" + amount);
} else {
System.out.println("Insufficient funds!");
}
}

// Instance method
public void applyInterest() {
double interest = this.balance * INTEREST_RATE;
this.balance += interest;
totalBalance += interest;
System.out.println("Interest applied: $" + interest);
}

// Static method - không cần object
public static int getTotalAccounts() {
return totalAccounts;
}

public static double getTotalBalance() {
return totalBalance;
}

public static void displayBankStats() {
System.out.println("=== Bank Statistics ===");
System.out.println("Total Accounts: " + totalAccounts);
System.out.println("Total Balance: $" + totalBalance);
System.out.println("Interest Rate: " + (INTEREST_RATE * 100) + "%");
}

// Instance method
public void displayAccountInfo() {
System.out.println("=== Account Info ===");
System.out.println("Account: " + this.accountNumber);
System.out.println("Owner: " + this.ownerName);
System.out.println("Balance: $" + this.balance);
}
}

public class BankDemo {
public static void main(String[] args) {
// Static method - không cần object
BankAccount.displayBankStats();

System.out.println("\n" + "=".repeat(40) + "\n");

// Tạo accounts
BankAccount acc1 = new BankAccount("ACC001", "Nguyễn Văn An", 1000);
BankAccount acc2 = new BankAccount("ACC002", "Trần Thị Bình", 2000);
BankAccount acc3 = new BankAccount("ACC003", "Lê Văn Cường", 1500);

// Instance methods
acc1.deposit(500);
acc2.withdraw(300);
acc1.applyInterest();

System.out.println("\n" + "=".repeat(40) + "\n");

// Display account info
acc1.displayAccountInfo();

System.out.println("\n" + "=".repeat(40) + "\n");

// Static method
BankAccount.displayBankStats();
}
}

static import

static import cho phép truy cập static members mà không cần tên class.

Cú pháp

import static package.ClassName.staticMember;
import static package.ClassName.*; // Import all static members

Ví dụ

// Không dùng static import
import java.lang.Math;

public class Demo {
public static void main(String[] args) {
double result = Math.sqrt(16) + Math.pow(2, 3);
System.out.println(Math.PI);
}
}

// Dùng static import
import static java.lang.Math.*;

public class Demo {
public static void main(String[] args) {
double result = sqrt(16) + pow(2, 3); // Không cần Math.
System.out.println(PI); // Không cần Math.
}
}
Cảnh báo

Đừng lạm dụng static import! Có thể gây nhầm lẫn khi không rõ method/variable đến từ đâu.

import static java.lang.Math.*;
import static com.myapp.MathUtils.*;

// max() đến từ Math hay MathUtils? 🤔
int result = max(5, 10);

Bài tập thực hành

Bài 1: Counter Class

Tạo class Counter với:

  • Static field: totalCount (đếm tổng số lần tất cả counters đếm)
  • Instance field: count (đếm riêng cho mỗi object)
  • Methods:
    • void increment(): Tăng cả counttotalCount
    • void reset(): Reset count về 0
    • static void resetAll(): Reset totalCount về 0
    • Getters cho cả hai

Bài 2: ID Generator

Tạo class Product tự động generate ID:

  • Static field: nextId (bắt đầu từ 1)
  • Instance field: id, name, price
  • Constructor tự động gán id = nextId++
  • Static method getNextId() trả về ID sẽ được gán tiếp theo

Bài 3: MathUtils Utility Class

Tạo utility class MathUtils với:

  • Private constructor (ngăn tạo object)
  • Static methods:
    • int factorial(int n)
    • boolean isPrime(int n)
    • int gcd(int a, int b) (greatest common divisor)
    • double average(int... numbers) (varargs)

Bài 4: Method Chaining

Tạo class Person hỗ trợ method chaining:

Person person = new Person()
.setName("An")
.setAge(20)
.setEmail("[email protected]")
.build();

Tổng kết

Key Takeaways

this Keyword:

  • Reference đến current object
  • Phân biệt instance variable vs parameter
  • Constructor chaining: this()
  • Method chaining: return this;
  • Truyền current object: method(this)

static Keyword:

  • Static field: Chung cho tất cả objects
  • Static method: Thuộc class, không cần object
  • Static block: Chạy khi class load
  • static final: Constants
  • Không dùng this trong static context

Khi nào dùng static:

  • Utility methods
  • Constants
  • Counters
  • Factory methods

Khi nào dùng instance:

  • Mỗi object có state riêng
  • Behavior phụ thuộc object state

Module hoàn thành

Chúc mừng! Bạn đã hoàn thành Module 2: OOP Cơ bản. Bạn đã học:

  • ✅ Giới thiệu OOP và 4 tính chất
  • ✅ Class và Object
  • ✅ Constructor và Methods
  • ✅ Encapsulation và Access Modifiers
  • ✅ this và static Keywords

Module tiếp theo

Tiếp tục hành trình với Module 3: OOP Nâng cao để tìm hiểu về Inheritance, Polymorphism, Abstract Classes, và Interfaces.

Module 3: OOP Nâng cao

Đọc thêm


"Static methods are like functions in procedural programming - they don't need an object to work. Instance methods are the heart of object-oriented programming - they operate on object state." — Effective Java