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

Inheritance (Kế thừa)

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

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

  • Hiểu khái niệm inheritance và mối quan hệ IS-A trong OOP
  • Sử dụng từ khóa extends và super để kế thừa và gọi members của class cha
  • Áp dụng method overriding và annotation @Override đúng cách
  • Override các method quan trọng của Object class (toString, equals, hashCode)
  • Nắm vững constructor chaining và thứ tự khởi tạo trong inheritance

Kế thừa là gì?

Inheritance (Kế thừa) là một cơ chế trong OOP cho phép một class (class con - subclass/child class) kế thừa các thuộc tính và phương thức từ một class khác (class cha - superclass/parent class).

IS-A Relationship

Kế thừa thể hiện mối quan hệ IS-A (là một):

  • Manager IS-A Employee (Manager là một Employee)
  • Circle IS-A Shape (Circle là một Shape)
  • Dog IS-A Animal (Dog là một Animal)
Lợi ích của Kế thừa
  • Tái sử dụng code: Không cần viết lại code đã có ở class cha
  • Dễ bảo trì: Thay đổi ở class cha tự động áp dụng cho các class con
  • Tính mở rộng: Dễ dàng thêm chức năng mới thông qua class con
  • Đa hình: Cho phép xử lý các đối tượng cùng loại một cách linh hoạt

Cú pháp: extends keyword

Sử dụng từ khóa extends để kế thừa:

// Class cha (Superclass/Parent class)
class Employee {
private String name;
private double salary;

public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}

public void work() {
System.out.println(name + " is working...");
}

public String getName() {
return name;
}

public double getSalary() {
return salary;
}
}

// Class con (Subclass/Child class)
class Manager extends Employee {
private String department;

public Manager(String name, double salary, String department) {
super(name, salary); // Gọi constructor của class cha
this.department = department;
}

public void manage() {
System.out.println(getName() + " is managing " + department);
}

public String getDepartment() {
return department;
}
}

super keyword

Từ khóa super được dùng để:

1. Gọi constructor của class cha

class Animal {
private String name;

public Animal(String name) {
this.name = name;
System.out.println("Animal constructor called");
}

public String getName() {
return name;
}
}

class Dog extends Animal {
private String breed;

public Dog(String name, String breed) {
super(name); // Phải gọi đầu tiên trong constructor
this.breed = breed;
System.out.println("Dog constructor called");
}
}
Quy tắc với super()
  • super() phải là câu lệnh đầu tiên trong constructor của class con
  • Nếu không gọi super() tường minh, Java tự động gọi super() (constructor không tham số của class cha)
  • Nếu class cha không có constructor không tham số, bạn phải gọi super() với tham số phù hợp

2. Gọi method của class cha

class Vehicle {
protected int maxSpeed = 100;

public void displayInfo() {
System.out.println("Max speed: " + maxSpeed + " km/h");
}
}

class Car extends Vehicle {
private String brand;

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

@Override
public void displayInfo() {
super.displayInfo(); // Gọi method của class cha
System.out.println("Brand: " + brand);
}
}

Method Overriding

Method Overriding là việc class con định nghĩa lại method đã có ở class cha với cùng signature.

Quy tắc Overriding

class Shape {
public void draw() {
System.out.println("Drawing a shape");
}

public double getArea() {
return 0.0;
}
}

class Circle extends Shape {
private double radius;

public Circle(double radius) {
this.radius = radius;
}

@Override // Annotation khuyến khích sử dụng
public void draw() {
System.out.println("Drawing a circle");
}

@Override
public double getArea() {
return Math.PI * radius * radius;
}
}

class Rectangle extends Shape {
private double width;
private double height;

public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}

@Override
public void draw() {
System.out.println("Drawing a rectangle");
}

@Override
public double getArea() {
return width * height;
}
}

Quy tắc quan trọng khi Override

Yêu cầuGiải thích
Cùng tên methodTên method phải giống hệt class cha
Cùng parametersSố lượng, kiểu dữ liệu, thứ tự tham số phải giống
Cùng hoặc rộng hơn access modifierprivate → ❌, protectedprotected/public, publicpublic
Cùng hoặc hẹp hơn return typeCó thể return subtype (covariant return type)
Không được throw checked exception mớiCó thể throw ít exception hơn hoặc subtype exception
@Override Annotation

Nên luôn sử dụng @Override annotation khi override method:

  • Compiler sẽ báo lỗi nếu method không thực sự override
  • Giúp code dễ đọc và maintain hơn
  • Tránh lỗi đánh máy tên method

Object Class

Trong Java, tất cả các class đều kế thừa từ java.lang.Object (ngầm định).

Các method quan trọng của Object class

class Student {
private String id;
private String name;
private int age;

public Student(String id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}

// Override toString() để in thông tin đối tượng dễ đọc
@Override
public String toString() {
return "Student{id='" + id + "', name='" + name + "', age=" + age + "}";
}

// Override equals() để so sánh nội dung đối tượng
@Override
public boolean equals(Object obj) {
if (this == obj) return true; // Cùng reference
if (obj == null || getClass() != obj.getClass()) return false;

Student student = (Student) obj;
return age == student.age &&
id.equals(student.id) &&
name.equals(student.name);
}

// Override hashCode() khi override equals()
@Override
public int hashCode() {
int result = id.hashCode();
result = 31 * result + name.hashCode();
result = 31 * result + age;
return result;
}
}

Sử dụng

public class TestStudent {
public static void main(String[] args) {
Student s1 = new Student("S001", "Nguyen Van A", 20);
Student s2 = new Student("S001", "Nguyen Van A", 20);
Student s3 = s1;

// toString()
System.out.println(s1); // Student{id='S001', name='Nguyen Van A', age=20}

// equals()
System.out.println(s1.equals(s2)); // true (nội dung giống nhau)
System.out.println(s1 == s2); // false (khác reference)
System.out.println(s1 == s3); // true (cùng reference)

// hashCode()
System.out.println(s1.hashCode() == s2.hashCode()); // true
}
}
equals() và hashCode() Contract

Khi override equals(), phải override hashCode() để đảm bảo:

  • Nếu a.equals(b)true thì a.hashCode() == b.hashCode()
  • Điều này quan trọng khi dùng object trong HashSet, HashMap

Single Inheritance trong Java

Java chỉ hỗ trợ single inheritance (kế thừa đơn): một class chỉ có thể kế thừa từ một class cha duy nhất.

// ✅ Hợp lệ
class A { }
class B extends A { }
class C extends B { }

// ❌ Không hợp lệ - Multiple inheritance
class D extends A, B { } // COMPILE ERROR

Tại sao Java không cho phép Multiple Inheritance?

Để tránh Diamond Problem:

      Animal
/ \
Dog Cat
\ /
DogCat ← Nếu Dog và Cat đều override eat(), DogCat kế thừa eat() nào?
Giải pháp

Java cho phép implement nhiều interface để đạt được tính linh hoạt tương tự multiple inheritance (sẽ học ở bài Interface).

Constructor Chaining trong Kế thừa

Constructor chaining là quá trình gọi constructor từ class con đến class cha theo chuỗi.

class Animal {
public Animal() {
System.out.println("1. Animal constructor");
}
}

class Mammal extends Animal {
public Mammal() {
super(); // Tự động gọi nếu không viết
System.out.println("2. Mammal constructor");
}
}

class Dog extends Mammal {
public Dog() {
super(); // Tự động gọi nếu không viết
System.out.println("3. Dog constructor");
}
}

public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
// Output:
// 1. Animal constructor
// 2. Mammal constructor
// 3. Dog constructor
}
}

Thứ tự khởi tạo chi tiết

Theo JLS §12.5, quá trình khởi tạo trong kế thừa diễn ra theo thứ tự:

  1. Static blocks của class cha → class con (chỉ 1 lần khi load class)
  2. Constructor của class cha (từ trên xuống theo hierarchy)
  3. Instance initializer blocks của class con
  4. Instance variables của class con
  5. Constructor body của class con
class Parent {
static { System.out.println("1. Parent static block"); }
{ System.out.println("3. Parent instance block"); }

public Parent() {
System.out.println("4. Parent constructor");
}
}

class Child extends Parent {
static { System.out.println("2. Child static block"); }
{ System.out.println("5. Child instance block"); }

public Child() {
System.out.println("6. Child constructor");
}
}

public class Test {
public static void main(String[] args) {
Child c = new Child();
// Output theo thứ tự 1 → 6
}
}

Initialization Order: Ví dụ phức tạp

class Grandparent {
static int staticGP = initStaticGP();
int instanceGP = initInstanceGP();

static int initStaticGP() {
System.out.println("1. Grandparent static init");
return 1;
}

int initInstanceGP() {
System.out.println("4. Grandparent instance init");
return 1;
}

public Grandparent() {
System.out.println("5. Grandparent constructor");
}
}

class Parent extends Grandparent {
static int staticP = initStaticP();
int instanceP = initInstanceP();

static int initStaticP() {
System.out.println("2. Parent static init");
return 2;
}

int initInstanceP() {
System.out.println("6. Parent instance init");
return 2;
}

public Parent() {
System.out.println("7. Parent constructor");
}
}

class Child extends Parent {
static int staticC = initStaticC();
int instanceC = initInstanceC();

static int initStaticC() {
System.out.println("3. Child static init");
return 3;
}

int initInstanceC() {
System.out.println("8. Child instance init");
return 3;
}

public Child() {
System.out.println("9. Child constructor");
}
}

public class InitializationOrderDemo {
public static void main(String[] args) {
System.out.println("Creating first Child...");
Child c1 = new Child();
// Output: 1→2→3→4→5→6→7→8→9

System.out.println("\nCreating second Child...");
Child c2 = new Child();
// Static blocks không chạy lại! Output: 4→5→6→7→8→9
}
}
📖 Theo JLS §12.5

Initialization order đảm bảo rằng superclass luôn được khởi tạo trước subclass, và static initialization chỉ chạy 1 lần khi class được load vào JVM lần đầu.

💡 Cách nhớ: "Static trước Instance, Cha trước Con"

Giống như xây nhà: phải có móng (static) trước khi xây tường (instance), và phải xây tầng dưới (cha) trước tầng trên (con).

Ví dụ thực tế

Ví dụ 1: Employee Hierarchy

class Employee {
protected String name;
protected String id;
protected double baseSalary;

public Employee(String name, String id, double baseSalary) {
this.name = name;
this.id = id;
this.baseSalary = baseSalary;
}

public double calculateSalary() {
return baseSalary;
}

public void displayInfo() {
System.out.println("ID: " + id);
System.out.println("Name: " + name);
System.out.println("Salary: $" + calculateSalary());
}
}

class Manager extends Employee {
private double bonus;
private String department;

public Manager(String name, String id, double baseSalary,
double bonus, String department) {
super(name, id, baseSalary);
this.bonus = bonus;
this.department = department;
}

@Override
public double calculateSalary() {
return baseSalary + bonus;
}

@Override
public void displayInfo() {
super.displayInfo();
System.out.println("Department: " + department);
System.out.println("Bonus: $" + bonus);
}
}

class Developer extends Employee {
private String programmingLanguage;
private int projectCount;

public Developer(String name, String id, double baseSalary,
String programmingLanguage, int projectCount) {
super(name, id, baseSalary);
this.programmingLanguage = programmingLanguage;
this.projectCount = projectCount;
}

@Override
public double calculateSalary() {
return baseSalary + (projectCount * 500); // Bonus per project
}

@Override
public void displayInfo() {
super.displayInfo();
System.out.println("Language: " + programmingLanguage);
System.out.println("Projects: " + projectCount);
}
}

Ví dụ 2: Shape Hierarchy

abstract class Shape {
protected String color;

public Shape(String color) {
this.color = color;
}

public abstract double getArea();
public abstract double getPerimeter();

public void displayInfo() {
System.out.println("Color: " + color);
System.out.println("Area: " + getArea());
System.out.println("Perimeter: " + getPerimeter());
}
}

class Circle extends Shape {
private double radius;

public Circle(String color, double radius) {
super(color);
this.radius = radius;
}

@Override
public double getArea() {
return Math.PI * radius * radius;
}

@Override
public double getPerimeter() {
return 2 * Math.PI * radius;
}
}

class Rectangle extends Shape {
private double width;
private double height;

public Rectangle(String color, double width, double height) {
super(color);
this.width = width;
this.height = height;
}

@Override
public double getArea() {
return width * height;
}

@Override
public double getPerimeter() {
return 2 * (width + height);
}
}

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

Bài 1: Vehicle Hierarchy

Tạo hệ thống quản lý phương tiện:

  • Class Vehicle: brand, year, price
  • Class Car extends Vehicle: thêm numberOfDoors, fuelType
  • Class Motorcycle extends Vehicle: thêm engineCapacity
  • Override toString()equals() cho tất cả các class
  • Tạo method displayInfo() cho mỗi class

Bài 2: Bank Account System

Tạo hệ thống tài khoản ngân hàng:

  • Class BankAccount: accountNumber, balance, ownerName
    • Methods: deposit(), withdraw(), getBalance()
  • Class SavingsAccount extends BankAccount: thêm interestRate
    • Override withdraw(): limit số lần withdraw mỗi tháng
    • Thêm method calculateInterest()
  • Class CheckingAccount extends BankAccount: thêm overdraftLimit
    • Override withdraw(): cho phép rút âm trong giới hạn

Bài 3: Person Hierarchy

Tạo class hierarchy cho người:

  • Class Person: name, age, address
  • Class Student extends Person: studentId, major, gpa
  • Class Teacher extends Person: teacherId, subject, salary
  • Implement toString(), equals(), và hashCode() đúng cách

Inheritance vs Composition

Theo Effective Java Item 18 ("Favor composition over inheritance"), kế thừa không phải lúc nào cũng là lựa chọn tốt nhất.

Khi nào dùng Inheritance?

Dùng kế thừa khi:

  • Có mối quan hệ IS-A rõ ràng (Dog IS-A Animal)
  • Subclass là specialized version của superclass
  • Superclass được thiết kế for inheritance (documented và stable)

Khi nào dùng Composition?

Dùng composition khi:

  • Có mối quan hệ HAS-A (Car HAS-A Engine)
  • Muốn tái sử dụng code mà không cần IS-A
  • Superclass có thể thay đổi trong tương lai
// ❌ Inheritance không phù hợp - Stack không phải là Vector
class Stack extends Vector {
public void push(Object item) {
addElement(item);
}
// Vấn đề: Stack expose tất cả Vector methods (add, remove, get...)
// → Phá vỡ encapsulation của Stack!
}

// ✅ Composition tốt hơn
class Stack {
private List<Object> elements = new ArrayList<>();

public void push(Object item) {
elements.add(item);
}

public Object pop() {
if (elements.isEmpty()) {
throw new EmptyStackException();
}
return elements.remove(elements.size() - 1);
}
// Chỉ expose methods cần thiết, ẩn implementation
}

Fragile Base Class Problem

Vấn đề: Thay đổi ở superclass có thể phá vỡ subclass mà không ai biết.

Hãy tưởng tượng bạn mua một chiếc tủ (superclass) và đặt đồ vào ngăn kéo (subclass). Một ngày, nhà sản xuất thay đổi kích thước ngăn kéo — đồ của bạn không vừa nữa mà bạn không hề được thông báo. Đó chính là Fragile Base Class Problem.

class InstrumentedHashSet<E> extends HashSet<E> {
private int addCount = 0;

@Override
public boolean add(E e) {
addCount++;
return super.add(e);
}

@Override
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c); // ❌ BUG! addAll() gọi add() internally
}

public int getAddCount() {
return addCount;
}
}

// Test
InstrumentedHashSet<String> set = new InstrumentedHashSet<>();
set.addAll(List.of("A", "B", "C"));
System.out.println(set.getAddCount()); // Output: 6 (expected 3!)
// Giải thích: addAll() cộng 3, rồi gọi add() 3 lần → mỗi lần cộng 1 → tổng 6
🔥 Bẫy OCP: Fragile Base Class

Câu hỏi OCP: Tại sao InstrumentedHashSet.addAll(List.of("A", "B", "C")) cho addCount = 6?

Đáp án: HashSet.addAll() internally gọi add() cho mỗi element. Vì add() đã bị override và tăng count, nên mỗi element được đếm 2 lần: 1 lần trong addAll(), 1 lần trong add().

Giải pháp: Dùng composition thay vì inheritance để kiểm soát đầy đủ behavior.

Method Hiding vs Method Overriding

Method hiding (ẩn method) xảy ra với static methods, khác với overriding (instance methods).

class Parent {
// Instance method - có thể override
public void instanceMethod() {
System.out.println("Parent instance method");
}

// Static method - bị hide, không override
public static void staticMethod() {
System.out.println("Parent static method");
}
}

class Child extends Parent {
@Override
public void instanceMethod() {
System.out.println("Child instance method");
}

// Method hiding - không phải override
public static void staticMethod() {
System.out.println("Child static method");
}
}

public class HidingDemo {
public static void main(String[] args) {
Parent p = new Child();

// Runtime polymorphism - gọi Child's method
p.instanceMethod(); // Output: Child instance method

// Compile-time binding - gọi Parent's method
p.staticMethod(); // Output: Parent static method (!)

// Static methods được gọi theo reference type, không phải object type
Child.staticMethod(); // Output: Child static method
}
}
📖 Theo JLS §8.4.8.2

Static methods cannot be overridden, chỉ có thể bị hidden. Method call resolution cho static methods diễn ra tại compile-time dựa trên reference type, không phải object type.

💡 Cách nhớ: Static = Compile-time, Instance = Runtime

Static methods: "Tĩnh" như ngôi nhà cố định — xác định địa chỉ ngay khi xây (compile). Instance methods: "Động" như taxi — chỉ biết đích đến khi đang chạy (runtime).

Sealed Classes (Java 17+)

Sealed classes (class niêm phong) cho phép kiểm soát ai được phép kế thừa class.

// Sealed class - chỉ cho phép 3 subclasses
public sealed class Shape
permits Circle, Rectangle, Triangle {

public abstract double getArea();
}

// Các subclasses phải là: final, sealed, hoặc non-sealed
public final class Circle extends Shape {
private double radius;

@Override
public double getArea() {
return Math.PI * radius * radius;
}
}

public non-sealed class Rectangle extends Shape {
// non-sealed → cho phép subclasses khác kế thừa tiếp
private double width, height;

@Override
public double getArea() {
return width * height;
}
}

public sealed class Triangle extends Shape
permits EquilateralTriangle {
// Sealed → chỉ EquilateralTriangle được kế thừa
@Override
public double getArea() {
// implementation
return 0;
}
}

// ❌ Compile error - Square không được phép kế thừa Shape
// public class Square extends Shape { }

Lợi ích của Sealed Classes

  • Exhaustive switch (Java 17+): Compiler kiểm tra tất cả cases
  • Domain modeling: Mô hình hóa tập hợp hữu hạn các subclasses
  • Security: Ngăn chặn inheritance không mong muốn
public double calculateArea(Shape shape) {
// Compiler biết tất cả subclasses → kiểm tra exhaustiveness
return switch (shape) {
case Circle c -> c.getArea();
case Rectangle r -> r.getArea();
case Triangle t -> t.getArea();
// Không cần default - compiler đảm bảo đã cover hết
};
}
🔥 Bẫy OCP: Implicit super() và Constructors

Bẫy 1: Implicit super() call

class Parent {
public Parent(String name) {
// Không có no-arg constructor
}
}

class Child extends Parent {
public Child() {
// ❌ Compile error! Implicit super() không tìm thấy no-arg constructor
}
}

// ✅ Phải gọi super(String) tường minh
class Child extends Parent {
public Child() {
super("default"); // OK
}
}

Bẫy 2: Constructors are NOT inherited

class Parent {
public Parent(int x) { }
}

class Child extends Parent {
// ❌ Child không tự động có constructor(int x)
// Phải tự định nghĩa
}

// Test
Child c = new Child(5); // ❌ Compile error!

Bẫy 3: Method overriding không thể ném broader checked exception

class Parent {
public void read() throws IOException { }
}

class Child extends Parent {
// ✅ OK — cùng exception
public void read() throws IOException { }

// ✅ OK — hẹp hơn (subtype)
// public void read() throws FileNotFoundException { }

// ✅ OK — không throw gì cả
// public void read() { }

// ❌ Compile error! — broader exception
// public void read() throws Exception { }
}

Chi tiết 6 scenarios ở bài Exception Handling.

Bẫy 4: final class và final method

final class FinalClass { }

// ❌ Compile error - không thể extend final class
class SubClass extends FinalClass { }

class Parent {
public final void finalMethod() { }
}

class Child extends Parent {
// ❌ Compile error - không thể override final method
@Override
public void finalMethod() { }
}

Protected Access xuyên Package

protected member có thể truy cập từ:

  1. Cùng package — bất kỳ class nào
  2. Khác package — chỉ qua subclass, và chỉ qua reference của chính subclass đó

Quy tắc thứ 2 hay gây nhầm lẫn. Hãy xem 6 scenarios:

// package com.parent
public class Parent {
protected int value = 42;
protected void show() {
System.out.println("Parent show");
}
}
// package com.child — KHÁC package với Parent
public class Child extends Parent {

public void test() {
// Scenario 1: Qua this → ✅ OK
System.out.println(this.value);
this.show();

// Scenario 2: Qua Child reference → ✅ OK
Child other = new Child();
System.out.println(other.value);
other.show();

// Scenario 3: Implicit this → ✅ OK
System.out.println(value);
show();

// Scenario 4: Qua Parent reference → ❌ COMPILE ERROR!
Parent p = new Parent();
// System.out.println(p.value); // ❌
// p.show(); // ❌

// Scenario 5: Qua Parent ref trỏ đến Child → ❌ vẫn ERROR!
Parent p2 = new Child();
// System.out.println(p2.value); // ❌

// Scenario 6: Qua sibling subclass reference → ❌ ERROR!
// SiblingChild s = new SiblingChild();
// System.out.println(s.value); // ❌
}
}
ScenarioAccess quaKết quảLý do
this.valuethis (Child)✅ OKSubclass truy cập inherited member
other.valueChild ref✅ OKCùng class type
valueImplicit this✅ OKTương đương this.value
p.valueParent ref❌ FAILKhác package, không qua subclass ref
p2.valueParent ref❌ FAILCompiler nhìn reference type, không phải object type
sibling.valueSiblingChild ref❌ FAILKhác nhánh subclass
OCP Trap

Quy tắc vàng: Protected access từ khác package chỉ hoạt động khi reference type là chính class đó hoặc subclass — không phải superclass reference. Compiler kiểm tra reference type, không phải object type.

Clone và Cloneable

Object.clone() tạo bản sao của object. Nhưng cơ chế clone trong Java có nhiều "bẫy".

Shallow Copy vs Deep Copy

public class Address implements Cloneable {
String city;

public Address(String city) { this.city = city; }

@Override
public Address clone() throws CloneNotSupportedException {
return (Address) super.clone();
}
}

public class Person implements Cloneable {
String name;
Address address; // Reference type

public Person(String name, Address address) {
this.name = name;
this.address = address;
}

// Shallow copy — address vẫn trỏ đến cùng object
@Override
public Person clone() throws CloneNotSupportedException {
return (Person) super.clone();
}
}

Person p1 = new Person("An", new Address("Hanoi"));
Person p2 = p1.clone();

p2.address.city = "HCMC";
System.out.println(p1.address.city); // HCMC! ← p1 cũng bị ảnh hưởng

Deep copy — clone cả các reference bên trong:

@Override
public Person clone() throws CloneNotSupportedException {
Person cloned = (Person) super.clone();
cloned.address = this.address.clone(); // Deep copy address
return cloned;
}

Tại sao clone() là protected?

Object.clone() được khai báo protected — nghĩa là bạn không thể gọi obj.clone() từ bên ngoài class trừ khi class đó override clone() thành public.

Quy tắc khi dùng Cloneable

  1. Class phải implements Cloneable (marker interface — không có method)
  2. Override clone() và gọi super.clone()
  3. Nếu không implement CloneableCloneNotSupportedException tại runtime
  4. Nên dùng copy constructor hoặc static factory thay vì clone (Effective Java Item 13)
// ✅ Copy constructor — an toàn hơn clone
public class Person {
public Person(Person other) {
this.name = other.name;
this.address = new Address(other.address.city); // Deep copy
}
}
OCP Tip

Trong OCP, câu hỏi về clone() thường kiểm tra: (1) Cloneable là marker interface, (2) shallow vs deep copy, (3) CloneNotSupportedException khi thiếu implements Cloneable.

Tổng kết

Khái niệmMô tả
InheritanceCơ chế cho phép class con kế thừa từ class cha
extendsTừ khóa để kế thừa class
superTừ khóa để truy cập members của class cha
Method OverridingĐịnh nghĩa lại instance method của class cha (runtime polymorphism)
Method HidingStatic method ở subclass che static method của superclass (compile-time)
Object classClass cha mặc định của tất cả các class
Single InheritanceJava chỉ cho phép kế thừa từ một class cha
Constructor ChainingQuá trình gọi constructor từ class con → cha
Sealed ClassesKiểm soát subclass nào được phép kế thừa (Java 17+)
Composition over InheritanceƯu tiên HAS-A hơn IS-A để tránh fragile base class
Protected xuyên packageChỉ access qua subclass reference, không qua parent reference
Clone / CloneableShallow copy mặc định, cần deep copy thủ công cho reference types
Lưu ý quan trọng
  • Luôn sử dụng @Override annotation
  • Override hashCode() khi override equals()
  • Sử dụng kế thừa khi có mối quan hệ IS-A rõ ràng
  • Ưu tiên composition over inheritance khi có thể (Effective Java Item 18)
  • Constructor của superclass phải được gọi đầu tiên (implicit hoặc explicit)
  • Static methods bị hidden, không bị overridden

Đọc thêm