Hướng đối tượng trong Java – part I

Phần này của hướng dẫn Java là một phần giới thiệu về lập trình hướng đối tượng trong Java. Chúng ta đề cập đến các đối tượng Java, đối tượng (object) các thuộc tính và phương thức (method), khởi tạo object, và quyền truy cập. Hơn nữa, chúng ta nói về từ khóa supper, hàm tạo, lớp constant, thừa kế, lớp final và hàm tạo private.

Có ba mô hình programming được sử dụng rộng rãi: lập trình hướng thủ tục, lập trình chức năng, và lập trình hướng đối tượng. Java chủ yếu là một ngôn ngữ lập trình hướng đối tượng. Từ Java 8, nó có một số hỗ trợ programming theo hướng chức năng.

Lập trình hướng đối tượng

Lập trình hướng đối tượng (OOP) là một mô hình lập trình sử dụng các đối tượng và các tương tác của chúng trong việc thiết kế ứng dụng và chương trình máy tính.

Sau đây là các khái niệm lập trình cơ bản trong OOP:
Trừu tượng (Abstraction)
Đa hình (Polymorphism)
Đóng gói (Encapsulation)
Kế thừa (Inheritance)

Sự trừu tượng là đơn giản hóa thực tế phức tạp bằng cách mô hình hóa các lớp học phù hợp với vấn đề. Đa hình là quá trình sử dụng toán tử hoặc chức năng theo những cách khác nhau cho dữ liệu đầu vào. Việc đóng gói ẩn các chi tiết thực hiện của một class từ các đối tượng khác. Kế thừa là một cách để tạo class mới sử dụng các các thuộc tính và phương thức của class đã được định nghĩa trước đó.

Object

Đối tượng là block cơ bản trong chương trình Java OOP. Đối tượng là sự kết hợp của dữ liệu và phương thức. Trong một chương trình OOP, chúng ta tạo ra các đối tượng. Các đối tượng này giao tiếp với nhau qua các phương thức. Mỗi đối tượng có thể nhận tin message, gửi message, và xử lý dữ liệu.

Có 2 bước để tạo ra object. Trước tiên, chúng ta định nghĩa một class. Class là một khuôn mẫu cho một đối tượng. Đây là một bản thiết kế chi tiết mô tả trạng thái và hành vi mà các đối tượng của class đều chia sẻ. 1 class có thể được sử dụng để tạo ra nhiều đối tượng. Các đối tượng được tạo ra trong thời gian chạy chương trình (runtime) từ class được gọi là thể hiện (instances) của lớp đó.

Ví dụ 1: Minh họa việc tạo object

SimpleObject.java

package net.vncoding;

class Being {}

public class SimpleObject {

    public static void main(String[] args) {
        
        Being b = new Being();
        System.out.println(b);
    }
}
class Being {}

Đây là một định nghĩa class đơn giản. Body của class rỗng, không define thuộc tính và phương thức nào.

Being b = new Being();

Chúng ta tạo instance của class Being. Biến ‘b’ là handle trỏ tới object được tạo.

System.out.println(b);

In đối tượng ‘b’ ra màn hình console để lấy thông tin cơ bản về object. Thông tin cơ bản object là gì?
Khi chúng ta in 1 object, bản chất là gọi phương thức toString(). Nhưng chúng ta chưa define phương thức này bởi vì mọi object đều được kết thừa từ Object cơ sở. Object cơ sở có các phương thức cơ bản được share cho tất cả các object kết thừa. Một trong số các phương thức đó là toString().

Compiler sẽ build ra 2 file class: Being.class và SimpleObject.class. SimpleObject.class là application class, Being.class là custom class được sử dụng trong application.

Kết quả:
net.vncoding.Being@7852e922

Kết quả là tên class của ‘b’, kí tự @ và số nguyên mô tả hash code của object.

Thuộc tính

Thuộc tính của object chính là dữ liệu hay còn gọi là member của class.

Ví dụ 2: Minh họa thuộc tính của object

package net.vncoding;

class Person {

    public String name;
}

public class ObjectAttributes {

    public static void main(String[] args) {
        
        Person p1 = new Person();
        p1.name = "Viet";

        Person p2 = new Person();
        p2.name = "VnCoding";

        System.out.println(p1.name);
        System.out.println(p2.name);
    }
}

Giải thích:

class Person {
    public String name;
}

Class Person gồm có 1 thuộc tính ‘name’ kiểu String. Từ khóa public có ý nghĩa là có thể truy cập thuộc tính ‘name’ từ ngoài class Person.

Person p1 = new Person();
p1.name = "Viet";

Tạo instance của class Person và gán chuỗi “Viet” cho thuộc tính ‘name’ của class.
Sử dụng dấu chấm (.) để truy cập đến thuộc tính của object.

Person p2 = new Person();
p2.name = "VnCoding";

Tạo 1 instance khác của class Person và gán chuỗi “VnCoding” cho thuộc tính ‘name’ của class.

System.out.println(p1.name);
System.out.println(p2.name);

In nội dung của 2 biến ra màn hình console.

Kết quả:
Viet
VnCoding

Phương thức

Phương thức là các function được định nghĩa bên trong body của class. Phương thức được sử dụng để thực hiện xử lý với thuộc tính của object.
Phương thức làm cho chương trình có tính module, là cần thiết trong thuộc tính đóng gói của hướng đối tượng (OOP). Ví dụ, chúng ta có thể có phương thức connect() trong lớp AccessDatabase. Chúng ta không cần thông báo phương thức connect() sẽ kết nối đến database như thế nào. Chúng ta chỉ phải biết rằng nó được sử dụng để kết nối tới database.
Nói cách khác, chúng ta chỉ cần biết interface của phương thức (tên hàm số, tham số đầu vào, giá trị trả về)

Ví dụ 3:Minh họa sử dụng phương thức trong class

package net.vncoding;

class Circle {

    private int radius;

    public void setRadius(int radius) {
        
        this.radius = radius;
    }

    public double area() {
        
        return this.radius * this.radius * Math.PI;
    }
}

public class Methods {

    public static void main(String[] args) {
        
        Circle c = new Circle();
        c.setRadius(5);

        System.out.println(c.area());
    }
}

Giải thích:
Trong ví dụ này, chúng ta define 1 class Circle. Trong đó, chúng ta định nghĩa 2 phương thức. Phương thức setRadius() gán giá trị cho thuộc tính ‘radius’ và phương thức area() tính diện tích đường trong của thuộc tính ‘radius’ và hằng số PI = 3.14

private int radius;

Định nghĩa 1 thuộc tính ‘radius’ trong class Circle. Nó là bán kính của đường tròn. Từ khóa private là chỉ định quyền truy xuất, có nghĩa là không được phép truy xuất tới thuộc tính ‘radius’ bên ngoài class Circle. Nếu muốn modify giá trị của ‘radius’ từ bên ngoài class, chúng ta phải sử dụng phương thức setRadius(). Wow, Đây chính là cách mà chúng ta protect dữ liệu.

public void setRadius(int radius) {
    this.radius = radius;
}

Đây là phương thức setRadius(). Từ khóa this là biến đặc biệt, được sử dụng bên trong phương thức để truy xuất đến thuộc tính của class. ‘this.radius’ là biến instance, trong khi ‘radius’ là biến local, valid chỉ bên trong phương thức setRadius().

Circle c = new Circle();
c.setRadius(5);

Chúng ta tạo ra instance của class Circle và set giá trị cho ‘radius’ bằng gọi phương thức setRadius() từ đối tượng ‘c’. Sử dụng toán tử chấm (.) để gọi hàm từ object.

public double area() {
    
    return this.radius * this.radius * Math.PI;
}

Phương thức area() trả về diện tích của hình tròn.

Kết quả:
78.53981633974483

Quyền truy xuất tới member của class

Quyền truy xuất(Access modifier) set quyền truy cập tới phương thức và thuộc tính dữ liệu của class. Java có 3 quyền truy xuất: public, protectedprivate.
– Member public có thể được truy xuất từ bất cứ đâu trong chương trình.
– Member protected có thể được truy xuất chỉ bên trong class của member đó, các class kế thừa và từ các class cùng package
– Member private chỉ có thể truy xuất bên trong class đó hoặc interface.
Chú ý: nếu không chỉ rõ quyền truy xuất khi khai báo member (phương thức và thuộc tính), thì quyền truy xuất mặc định là package-private. Tức là, các thuộc tính và phương thức có thể được truy xuất bên trong cùng 1 package.
Quyền truy xuất protect dữ liệu khỏi việc truy xuất và chỉnh sửa dữ liệu.

Access modifiers set the visibility of methods and member fields. Java has three access modifiers: public, protected, and private. The public members can be accessed from anywhere. The protected members can be accessed only within the class itself, by inherited classes, and other classes from the same package. Finally, the private members are limited to the containing type, e.g. only within its class or interface. If we do not specify an access modifier, we have a package-private visibility. In such a case, members and methods are accessible within the same package. Giúp chương trình được bảo mật hơn.

Class Package Subclass (same package) Subclass (other package) World
public Y Y Y Y Y
protected Y Y Y Y N
no modifier Y Y Y N N
private Y N N N N

 
Từ bảng này, chúng ta có thể thấy được mức độ truy xuất bị hạn chế dần từ public –> private.
Y: được quyền truy xuất
N: không được phép truy xuất
no modifier: không chỉ định rõ quyền truy xuất khi khai báo member của class.

Ví dụ 4: Minh họa quyền truy xuất tới member của class.

package net.vncoding;

class Person {

    public String name;
    private int age;

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

    public void setAge(int age) {
        
        this.age = age;
    }
}

public class AccessModifiers {

    public static void main(String[] args) {

        Person p = new Person();
        p.name = "Jane";

        p.setAge(17);

        System.out.println(String.format("%s is %d years old",
                p.name, p.getAge()));
    }
}

Giải thích:
Ví dụ này, chúng ta định nghĩa 2 thuộc tính: publicprivate.

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

Nếu member là private, truy xuất qua phương thức là cách duy nhất. Nếu chúng ta muốn modify thuộc tính bên ngoài class, phương thức phải là kiểu public.

public void setAge(int age) {
    
    this.age = age;
}

Phương thức setAge() cho phép chúng ta thay đổi thuộc tính ‘age’ từ bên ngoài class.

Person p = new Person();
p.name = "Jane";

Chúng ta tạo instance của class Person. Bởi vì thuộc tính ‘name’ là kiểu public, chúng ta có thể truy xuất trực tiếp. Tuy nhiên, không nên sử dụng cách này.

p.setAge(17);

Phương thwucs setAge() modify thuộc tính ‘age’. Không được phép truy xuất và modify trực tiếp vì thuộc tính ‘age’ là kiểu private.

System.out.println(String.format("%s is %d years old",
        p.name, p.getAge()));

In thuộc tính ‘name’ và ‘age’ ra màn hình console.

Kết quả:
Jane is 17 years old

Ví dụ 6: Minh họa quyền truy xuất ảnh hưởng tới member của class kế thừa.

package net.vncoding;

class Base {

    public String name = "Base";
    protected int id = 5323;
    private boolean isDefined = true;
}

class Derived extends Base {

    public void info() {
        
        System.out.println("This is Derived class");
        System.out.println("Members inherited:");
        System.out.println(this.name);
        System.out.println(this.id);
        // System.out.println(this.isDefined);
    }
}

public class ProtectedMember {

    public static void main(String[] args) {
        
        Derived drv = new Derived();
        drv.info();
    }
}

Giải thích:
Trong ví dụ này, chúng ta có class Derived kế thừa từ class Base. Class Base có 3 member với quyền truy xuất khác nhau. Member ‘isDefined’ không được kế thừa vì do quyền truy xuất private.

class Derived extends Base {

Class Derived kế thừa từ class Base. Để kế thừa class khác, chúng ta sử dụng từ khóa extends.

System.out.println(this.name);
System.out.println(this.id);
// System.out.println(this.isDefined);

Member public và protected được kế thừa bởi class Derived. Do vậy, có thể truy xuất trực tiếp các member này từ class Derived.
Member private không được kế thừa. Nếu bỏ comment dòng code thứ 3, compiler report error.

Kết quả:
This is Derived class
Members inherited:
Base
5323

Hàm tạo

Hàm tạo là 1 phương thức đặc biệt. Nó tự động được gọi khi đối tượng được tạo. Hàm tạo không trả về giá trị và cũng không sử dụng từ khóa void. Mục đích của hàm tạo là khởi tạo trạng thái của object. Hàm tạo có cùng tên với class. Hàm tạo là phương thức, nên cũng có thể overload. Hàm tạo không được gọi trực tiếp mà toán tử new gọi chúng. Hàm tạo không được khai báo với các từ khóa sau: synchronized, final, abstract, native, hoặc static.
Hàm tạo không được kế thừa. Chúng được gọi theo thứ tự của kế thừa. Nếu chúng ta định nghĩa 1 hàm tạo cho class, Java sẽ cung cấp hàm tạo default. Nếu chúng ta định nghĩa hàm tạo, hàm tạo mặc định sẽ không được sử dụng.

Ví dụ 7: Minh họa việc sử dụng hàm tạo

package net.vncoding;

class Being {

    public Being() {
        
        System.out.println("Being is created");
    }

    public Being(String being) {
        
        System.out.println(String.format("Being %s is created", being));
    }
}

public class Constructor {

    @SuppressWarnings("ResultOfObjectAllocationIgnored")
    public static void main(String[] args) {
        
        new Being();
        new Being("Tom");
    }
}

Giải thích:
Trong ví dụ này, class có 2 hàm tạo. Hàm tạo thứ nhất không có tham số đầu vào, hàm tạo thứ 2 có 1 tham số đầu vào

public Being() {
    
    System.out.println("Being is created");
}

Đây là hàm tạo không tham số đầu vào.

public Being(String being) {
    
    System.out.println(String.format("Being %s is created", being));
}

Hàm tạo thứ 2 với tham số đầu vào là kiểu String.

@SuppressWarnings("ResultOfObjectAllocationIgnored")

Dòng lệnh này loại bỏ warning từ compiler rằng không gán object được tạo cho biến.

new Being();

Instance của class Being được tạo. Hàm tạo không đối số được gọi.

new Being("Tom");

Instance khác của class Being được tạo. Hàm tạo có đối số được gọi.

Kết quả:
Being is created
Being Tom is created

Ví dụ 8: Minh họa việc khởi tạo thuộc tính của class bên trong hàm tạo.

package net.vncoding;

import java.util.Calendar;
import java.util.GregorianCalendar;

class MyFriend {

    private GregorianCalendar born;
    private String name;

    public MyFriend(String name, GregorianCalendar born) {
        
        this.name = name;
        this.born = born;
    }

    public void info() {
        
        System.out.format("%s was born on %s/%s/%s\n",
                this.name, this.born.get(Calendar.DATE),
                this.born.get(Calendar.MONTH),
                this.born.get(Calendar.YEAR));
    }
}

public class MemberInit {

    public static void main(String[] args) {
        
        String name = "Lenka";
        GregorianCalendar born = new GregorianCalendar(1990, 3, 5);

        MyFriend fr = new MyFriend(name, born);
        fr.info();
    }
}

Giải thích:
Chúng ta có class MyFriend với thuộc tính và phương thức.

private GregorianCalendar born;
private String name;

2 thuộc tính private được định nghĩa bên trong class.

public MyFriend(String name, GregorianCalendar born) {
    
    this.name = name;
    this.born = born;
}

Trong hàm tạo, chúng ta khởi tạo 2 thuộc tính. Biến ‘this’ là hanlder được sử dụng để tham chiếu object từ phương thức. Khi tên tham số của hàm tạo giống với tên member của class, phải sử dụng từ khóa this, và ngược lại thì không bắt buộc.

MyFriend fr = new MyFriend(name, born);
fr.info();

Tạo object MyFriend với 2 đối số. Sau đó gọi phương thức info().

Kết quả:
Lenka was born on 5/3/1990

Từ khóa super

Từ khóa super là biến tham chiếu được sử dụng trong subclass để tham chiếu tới object của class cha. Nó có thể được sử dụng để tham chiếu tới biến instance, hàm tạo, phương thức cha.

Ví dụ 9: Minh họa sử dụng từ khóa super

package net.vncoding;

class Shape {
    
    int x = 50;
    int y = 50;
}

class Rectangle extends Shape {
    
    int x = 100;
    int y = 100;
    
    public void info() {
        
        System.out.println(x);
        System.out.println(super.x);
    }
}

public class SuperVariable {

    public static void main(String[] args) {
        
        Rectangle r = new Rectangle();
        r.info();
    }
}

Giải thích:
Chúng ta tham chiếu tới member của parent class bằng từ khóa super.

public void info() {
    
    System.out.println(x);
    System.out.println(super.x);
}

Bên trong thân phương thức info(), chúng ta tham chiếu biến instance của parent class với ‘super.x’
Nếu hàm tạo không ngầm định gọi hàm tạo superclass, Java tự động chèn việc gọi hàm tạo không đối số của superclass. Nếu superclass không có hàm tạo không đối số, chương trình báo lỗi khi compile source code.

Kết quả:
100
50

Ví dụ 10: Minh họa việc gọi hàm tạo của class parent.

package net.vncoding;

class Vehicle {

    public Vehicle() {

        System.out.println("Vehicle created");
    }
}

class Bike extends Vehicle {
    
    public Bike() {
        
        // super();
        System.out.println("Bike created");
    }
 }

public class ImplicitSuper {

    public static void main(String[] args) {
        
        Bike bike = new Bike();
        System.out.println(bike);
    }
}

Giải thích:
Ví dụ minh họa việc gọi hàm tạo của class partent.

public Bike() {
    
    // super();
    System.out.println("Bike created");
}

Nếu bỏ comment dòng code super(), chúng ta sẽ có kết quả giống nhau.
2 hàm tạo được gọi khi object Bike được tạo.

Kết quả:
Vehicle created
Bike created
com.zetcode.Bike@15db9742

Ví dụ 10: Sử dụng super() để gọi hàm tạo của class parent.

package net.vncoding;

class Vehicle {
    
    protected double price;

    public Vehicle() {

        System.out.println("Vehicle created");
    }
    
    public Vehicle(double price) {
        
        this.price = price;

        System.out.printf("Vehicle created, price %.2f set%n", price);
    }    
}

class Bike extends Vehicle {
    
    public Bike() {
        
        super();
        System.out.println("Bike created");
    }
    
    public Bike(double price) {
        
        super(price);
        System.out.printf("Bike created, its price is: %.2f %n", price);
    }    
 }

public class SuperCalls {

    public static void main(String[] args) {
        
        Bike bike1 = new Bike();
        Bike bike2 = new Bike(45.90);
    }
}

Giải thích:
Ví dụ sử dụng cú pháp khác của super để gọi hàm tạo của class parent.

super();

Đây là cách gọi hàm tạo không đối số của class parent.

super(price);

Đây là cách gọi hàm tạo có đối số của class parent.

Kết quả:
Vehicle created
Bike created
Vehicle created, price 45.90 set
Bike created, its price is: 45.90

Gọi hàm tạo từ một hàm tạo khác

Để gọi hàm tạo khác từ cùng 1 class, chúng ta sử dụng từ khóa this. Để gọi hàm tạo từ class parent, chúng ta sử dụng từ khóa super.

Ví dụ 11: Minh họa việc gọi hàm tạo từ hàm tạo khác trong cùng 1 class.

package net.vncoding;

class Shape {
    
    private int x;
    private int y;   
    
    public Shape(int x, int y) {
        
        this.x = x;
        this.y = y;
    }
    
    protected int getX() {
        
        return this.x;
    }
    
    protected int getY() {
        
        return this.y;
    }            
}

class Circle extends Shape {
    
    private int r;

    public Circle(int r, int x, int y) {
        
        super(x, y);        
        this.r = r;
    }

    public Circle() {
        
        this(1, 1, 1);
    }
    
    @Override
    public String toString() {
        
        return String.format("Circle: r:%d, x:%d, y:%d", r, getX(), getY());
    }
}

public class ConstructorChaining {

    public static void main(String[] args) {
        
        Circle c1 = new Circle(5, 10, 10);
        Circle c2 = new Circle();
        
        System.out.println(c1);
        System.out.println(c2);
    }
}

Giải thích:
Class Circle có 2 hàm tạo. Hàm tạo thứ nhất có 1 tham số đầu vào, hàm tạo thứ 2 không có tham số đầu vào.

class Shape {
    
    private int x;
    private int y;
...
}

class Shape đảm nhận việc xử lí tọa độ x,y của các hình nói chung.

public Shape(int x, int y) {
    
    this.x = x;
    this.y = y;
}

Hàm tạo của class Shape khởi tạo 2 thuộc tính ‘x’ và ‘y’

protected int getX() {
    
    return this.x;
}

protected int getY() {
    
    return this.y;
} 

Định nghĩa 2 phương thức lấy giá trị của 2 thuộc tính ‘x’ và ‘y’. Vì ‘x’ và ‘y’ là private, nên phải truy xuất thông qua phương thức.

class Circle extends Shape {
    
    private int r;
...
}

Class Circle kế thừa từ class Shape. Nó định nghĩa thêm thuộc tính ‘radius’.

public Circle(int r, int x, int y) {
    
    super(x, y);        
    this.r = r;
}

Hàm tạo thứ nhất của class Circle lấy 3 tham số đầu vào: ‘radius’, ‘x’ và ‘y’. Với từ khóa super, hàm tạo của class Shape được gọi và truyền giá trị đối số x, y cho thuộc tính ‘x’ và ‘y’.
Chú ý: từ khóa super phải được khai báo dòng đầu trong hàm tạo.

public Circle() {
    
    this(1, 1, 1);
}

Hàm tạo thứ 2 không tham số. Trong trường hợp này, chúng ta sử dụng giá trị mặc định. Từ khóa this được sử dụng để gọi hàm tạo 3 tham số của cùng 1 class.

@Override
public String toString() {
    
    return String.format("Circle: r:%d, x:%d, y:%d", r, getX(), getY());
}

Vì phương thức toString() là phương thức đã được define trong class Base. Do vậy, muốn sử dụng overload phương thức toString(), chúng ta khai báo @Override
Bên trong phương thức toString(), chúng ta tạo ra 1 cách mô tả về class Circle.

Kết quả:
Circle: r:5, x:10, y:10
Circle: r:1, x:1, y:1

Constant class

Trong Java có thể tạo ra constant class. Các constant không thuộc object cụ thể nào. Theo quy chuẩn code, hằng số nên viết bằng chữ in hoa.

Ví dụ 12: Minh họa việc định nghĩa constant class

package net.vncoding;

class Math {

    public static final double PI = 3.14159265359;
}

public class ClassConstant {

    public static void main(String[] args) {
        
        System.out.println(Math.PI);
    }
}

Giải thích:
Chúng ta có constant class Math với hằng số PI

public static final double PI = 3.14159265359;

– Từ khóa final được sử dụng để định nghĩa hằng số.
– Từ khóa static cho phép tham chiếu tới member của class mà không cần object.
– Từ khóa public cho phép truy xuất hằng số bên ngoài class.

Kết quả:
3.14159265359

Phương thức toString()

Mỗi object có phương thức toString(). Nó trả về mô tả về object dưới dạng chuỗi. Khi chúng ta gọi phương thức System.out.println() với đối số object, phương thức toString() được gọi.

Ví dụ 13: Minh họa việc override phương thức toString()

package net.vncoding;

class Being {

    @Override
    public String toString() {

        return "This is Being class";
    }
}

public class ThetoStringMethod {

    public static void main(String[] args) {
        
        Being b = new Being();
        Object o = new Object();

        System.out.println(o.toString());
        System.out.println(b.toString());
        System.out.println(b);
    }
}

Giải thích:
Chúng ta override phương thức default toString().

@Override
public String toString() {

    return "This is Being class";
}

Mỗi class đều kế thừa từ class Object. Phương thức toString() thuộc object của class Being. @Override thông báo cho compiler that phương thức toString() được override trong superclass.

Being b = new Being();
Object o = new Object();

Chúng ta tạo 2 object: 1 object được customize và 1 object của class đã được định nghĩa trong library của Java.

System.out.println(o.toString());
System.out.println(b.toString());

Phương thức toString() được gọi tường minh.

System.out.println(b);

Phương thức toString() được gọi ngầm định.

Kết quả:
java.lang.Object@7852e922
This is Being class
This is Being class

Kế thừa trong Java

Kế thừa là cách tạo class mới từ class đã được define. Class mới được gọi mà class dẫn xuất (derived class). Class được kế thừa gọi là base class. Điều quan trọng của kế thừa trong OOP là việc sử dụng lại code và giảm độ phức tạp của chương trình. Class dẫn xuất có thể override hoặc extend các chức năng của base class.

Ví dụ 14: Minh họa sử dụng kế thừa trong Java

package net.vncoding;

class Being {

    public Being() {

        System.out.println("Being is created");
    }
}

class Human extends Being {

    public Human() {

        System.out.println("Human is created");
    }
}

public class Inheritance {

    @SuppressWarnings("ResultOfObjectAllocationIgnored")
    public static void main(String[] args) {
        
        new Human();
    }
}

Giải thích:
Chúng ta có 2 class: base class Being và class dẫn xuất Human. Class dẫn xuất kế thừa từ base class.

class Human extends Being {

Sử dụng từ khóa extends để tạo ra mối quan hệ kế thừa.

new Human();

Tạo 1 đối tượng của class Human.

Kết quả:
Being is created
Human is created

Chúng ta có thể thấy cả 2 hàm tạo được gọi. Thứ nhất, hàm tạo của base class được gọi, sau đó hàm tạo của lớp dẫn xuất được gọi.

Ví dụ 15: Minh họa sử dụng kế thừa phức tạp trong java.

package net.vncoding;

class Being {

    static int count = 0;

    public Being() {
        
        count++;
        System.out.println("Being is created");
    }

    public void getCount() {
        
        System.out.format("There are %d Beings%n", count);
    }
}

class Human extends Being {

    public Human() {
        
        System.out.println("Human is created");
    }
}

class Animal extends Being {

    public Animal() {
        
        System.out.println("Animal is created");
    }
}

class Dog extends Animal {

    public Dog() {
        
        System.out.println("Dog is created");
    }
}

public class Inheritance2 {

    @SuppressWarnings("ResultOfObjectAllocationIgnored")
    public static void main(String[] args) {
        
        new Human();
        Dog dog = new Dog();
        dog.getCount();
    }
}

Giải thích:
Với 4 class, việc kế thừa kiểu cấp bậc trở lên phức tạp hơn. Class Human và Animal kế thừa từ class Being và class Dog kế thừa trực tiếp từ class Animal và gián tiếp từ class Being.

static int count = 0;

Chúng ta biến static. Chỉ static được share giữa các instance của class.

public Being() {
    
    count++;
    System.out.println("Being is created");
}

Mỗi khi object của class được khởi tạo, biến ‘count’ được tăng lên 1 đơn vị. Đây là cách giúp chúng ta biết được số lần các object được khởi tạo.

class Animal extends Being {
...

class Dog extends Animal {
...

Class Animal kế thừa từ Being và Dog kế thừa từ Animal. Một cách gián tiếp, Dog cũng kế thừa từ Being

new Human();
Dog dog = new Dog();
dog.getCount();

Chúng tạo object của class Human và Dog. Gọi phương thức getCount() của object Dog.

Kết quả:
Being is created
Human is created
Being is created
Animal is created
Dog is created
There are 2 Beings

Object Human gọi 2 hàm tạo. Object Dog gọi 3 hàm tạo. Hàm tạo Being() được gọi 2 lần.

Final class và hàm tạo private

– Class được define với từ khóa final không thể subclass.
– Class with hàm tạo kiểu private không thể được khởi tạo.

Ví dụ 16: Minh họa sử dụng Final class

package net.vncoding;

final class MyMath {

    public static final double PI = 3.14159265358979323846;
    
    // other static members and methods
}


public class FinalClass {

    public static void main(String[] args) {

        System.out.println(MyMath.PI);
    }
}

Giải thích:
Trong ví dụ này define class MyMath. Class này có các member và phương thức kiểu static. Chúng ta không muốn cho ai đó kế thừa class MyMath. Do đó, chúng ta khai báo từ khóa final cho class MyMath.

Hơn nữa, chúng ta cũng không muốn cho tạo object từ class MyMath. Chúng ta khai báo private cho hàm tạo.

Ví dụ 17: Minh họa sử dụng hàm tạo private

package net.vncoding;

final class MyMath {
    
    private MyMath() {}

    public static final double PI = 3.14159265358979323846;
    
    // other static members and methods
}


public class PrivateConstructor {

    public static void main(String[] args) {
        
        System.out.println(MyMath.PI);
    }
}

Giải thích:
Không thể tạo object và subclass từ class MyMath. Đây là cách implement library java.lang.Math trong Java.

Kết quả:
3.141592653589793

Be the first to comment

Leave a Reply