Phương thức trong Java

Trong phần này, chúng ta cùng tìm hiểu về phương thức trong Java.
Trong OOP, chúng ta làm việc với object. Các object là block cơ bản của chương trình. Các object bao gồm dữ liệu và phương thức. Các phương thức thay đổi trạng thái của các object. Có thể coi phương thức là phần động (dynamic) và dữ liệu là phần tĩnh (static) của object.

Định nghĩa phương thức

Phương thức là block code bao gồm 1 chuỗi lệnh. Phương thức phải khai báo bên trong class. Nên để mỗi phương thức thực hiện 1 công việc riêng rẽ. Phương thức làm cho chương trình có tính module hóa. Lợi ích của việc dùng phương thức.
– Giảm code lặp lại
– Phân chia chương trình phức tạp thành các module nhỏ đơn giản hơn
– Nâng cao tính sáng sủa của source code
– Sử dụng lại code
– Ẩn thông tin

Đặc tính của phương thức

Các đặc tính của phương thức gồm có:
– Mức độ truy cập (public, protected, private)
– Giá trị trả về
– Tên phương thức
– Các tham số đầu vào
– Dấu {}
– Body của phương thức

Ví dụ 1: Định nghĩa 1 phương thức

public void setValue(int x){
    this.value = x;
}

– Mức độ truy cập: public
– Giá trị trả về: void
– Tên phương thức: setValue
– Các tham số đầu vào: int
– Dấu {}: bao quanh body của phương thức
– Body của phương thức: chỉ chứa 1 lệnh.

Method signature

Method signature là cách nhận dạng duy nhất của phương thức cho Java compiler. Chữ kí bao gồm: tên phương thức và kiểu và loại dữ liệu của tham số đầu vào. Method signature không bao gồm kiểu trả về.

Tên phương thức

Mọi kí tự được dùng trong Java có thể được dùng đặt tên cho phương thức. Theo coding convention,
– Tên phương thức nên bắt đầu với kí tự in thường.
– Tên phương thức là động từ hoặc động từ theo sau bởi tính từ hoặc danh từ.
– Mỗi từ trong tên phương thức bắt đầu bằng kí tự in hoa.

Ví dụ về tên phương thức được đặt theo coding convention.
execute
findId
setName
getName
checkIfValid
testValidity

Ví dụ về sử dụng phương thức

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

package net.vncoding;

class Base {
    
    public void showInfo() {
        
        System.out.println("This is Base class");
    }
}

public class ShowInfoMethod {

    public static void main(String[] args) {
        
        Base bs = new Base();
        bs.showInfo();    
    }
}

Giải thích:
Phương thức showInfo() in tên class.

class Base {
    
    public void showInfo() {
        
        System.out.println("This is Base class");
    }
}

Mỗi phương thức phải được định nghĩa trong class. Phương thức showInfo() không có tham số đầu vào không trả về giá trị.

public static void main(String[] args) {
...        
}

Phương thức main() là điểm bắt đầu chương trình console hoặc GUI Java application. Phương thức main() có tham số đầu vào làm mảng các String.

Base bs = new Base();
bs.showInfo(); 

Chúng ta tạo object của class Base. Gọi phương thức showInfo() thông qua object.
Sử dụng toán tử chấm (.) để gọi phương thức từ object.

Tham số của phương thức

Tham số là giá trị được truyền vào cho phương thức. Phương thức có thể nhận 1 hoặc nhiều tham số. Nếu phương phương thức làm việc với số nguyên, chúng ta truyền vào dữ liệu kiểu số nguyên. Các tham số được khai báo trong cặp ngoặc đơn () đi sau tên phương thức. Trong định nghĩa phương thức, chúng ta phải cung cấp tên và kiểu dữ liệu cho mỗi tham số đầu vào.

Ví dụ 3: Minh họa việc sử dụng tham số của phương thức

package net.vncoding;

class AddValues {

    public int addTwoValues(int x, int y) {
        
        return x + y;
    }

    public int addThreeValues(int x, int y, int z) {
        
        return x + y + z;
    }
}

public class Addition {

    public static void main(String[] args) {

        AddValues a = new AddValues();
        int x = a.addTwoValues(12, 13);
        int y = a.addThreeValues(12, 13, 14);

        System.out.println(x);
        System.out.println(y);
    }
}

Giải thích:
Class AddValues có 2 phương thức. Phương thức thứ nhất nhận 2 tham số đầu vào, phương thức còn lại nhận 3 tham số đầu vào.

public int addTwoValues(int x, int y) {
    
    return x + y;
}

Phương thức addTwoValues() nhận 2 tham số đầu vào. Các tham số này là kiểu int. Phương thức cũng trả về kiểu int.
Chúng ta sử dụng từ khóa return để trả về giá trị của phương thức.

public int addThreeValues(int x, int y, int z) {
    
    return x + y + z;
}

Phương thức addThreeValues() nhận 3 tham số đầu vào.

int x = a.addTwoValues(12, 13);

Chúng ta gọi phương thức addTwoValues() của object AddValues. Nó nhận 2 tham số đầu vào. Các tham số này được truyền vào phương thức. Giá trị trả về của phương thức được gán cho biến ‘x’.

Số lượng đối số không cố định

Kể từ Java 5, phương thức có thể nhận số lượng đối số không cố định.

Ví dụ 4: Minh họa việc truyền số lượng đối số không cố định cho phương thức.

package net.vncoding;

public class SumOfValues {

    public static int sum(int...vals) {
        
        int sum = 0;
        
        for (int val : vals) {
            sum += val;
        }
        
        return sum;
    }

    public static void main(String[] args) {
        
        int s1 = sum(1, 2, 3);
        int s2 = sum(1, 2, 3, 4, 5);
        int s3 = sum(1, 2, 3, 4, 5, 6, 7);
        
        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s3);
    }
}

Giải thích:
Chúng ta tạo phương thức sum() có thể nhận số lượng đối số không cố định. Phương thức tính tổng các số nguyên được truyền vào phương thức.

int s1 = sum(1, 2, 3);
int s2 = sum(1, 2, 3, 4, 5);
int s3 = sum(1, 2, 3, 4, 5, 6, 7);

Chúng ta gọi phương thức sum() 3 lần. Mỗi lần, truyền số lượng đối số tới phương thức là khác nhau.

public static int sum(int...vals) {
...
}

Phương thức sum() có thể nhận số lượng đối số khác nhau. Tất cả các đối số được lưu vào mảng ‘vals’

int sum = 0;

for (int val : vals) {
    sum += val;
}

return sum;

Tính toán tổng các đối số truyền vào và trả về giá trị tổng.

Kết quả:
6
15
28

Truyền tham trị

Trong Java, đối số luôn luôn được truyền cho phương thức kiểu tham trị. Khi truyền dữ liệu nguyên thủy cho phương thức, bản copy của giá trị được truyền cho phương thức. Trong trường hợp object, bản copy của tham chiếu được truyền cho phương thức.
Khác với C# và C++, Java không support truyền đối số kiểu tham biến.

Ví dụ 5: Minh họa truyền tham trị cho phương thức.

package net.vncoding;

class Cat {}
class Dog {}

public class PassByValue {
    
    private static void tryChangeInteger(int x) {
        
        x = 15;        
    }
    
    private static void tryChangeObject(Object o) {
        
        Dog d = new Dog();
        o = d;
    }

    public static void main(String[] args) {
        
        int n = 10;
        tryChangeInteger(n);
        System.out.println(n);                
        
        Cat c = new Cat();
        tryChangeObject(c);
        System.out.println(c.getClass());            
    }
}

Giải thích:
Ví dụ chỉ ra không thể thay đổi giá trị của đối số và tham chiếu tới object bên trong phương thức.

private static void tryChangeInteger(int x) {
    
    x = 15;        
}

Giá trị của đối số truyền vào được copy vào biến local ‘x’. Việc gán giá trị 15 cho biến ‘x’ không làm thay đổi đối số truyền vào.

private static void tryChangeObject(Object o) {
    
    Dog d = new Dog();
    o = d;
}

Điều tương tự áp dụng cho object. Chúng ta truyền bản copy của tham chiếu tới phương thức ‘o’ ở đây là biến local trỏ tới object Dog. Object được định nghĩa bên ngoài phương thức tryChangeObject() không bị thay đổi.

int n = 10;
tryChangeInteger(n);
System.out.println(n); 

Chúng ta định nghĩa biến ‘n’ và truyền nó vào phương thức tryChangeInteger(). Sau đó,in biến ‘x’ để kiểm tra xem biến ‘x’ có thay đổi không.

Cat c = new Cat();
tryChangeObject(c);
System.out.println(c.getClass());   

Chúng ta định nghĩa object Cat và truyền object cho phương thức tryChangeObject().

Kết quả:
10
class net.vncoding.Cat

Overload phương thức

Overloading phương thức cho phép tạo nhiều phương thức với cùng tên phương thức và khác kiểu dữ liệu của tham số đầu vào.
Vậy, overloading phương thức được sử dụng trong ngữ cảnh nào?
Thư viện Qt5 đưa ra ví dụ về việc sử dụng overloading. Class QPainter có 3 phương thức vẽ hình chữ nhật. Tên của các phương thức này đều là drawRect() và các tham số đầu vào là khác nhau. 1 phương thức nhận tham chiếu tới đối tượng hình chữ nhật kiểu số dấu phẩy động, phương thức khác nhận tham chiếu đến đối tượng hình chữ nhật kiểu số nguyên, và phương thức drawRect() cuối cùng lấy 4 tham số: x, y, width và height. Giả sử ngôn ngữ C++ là ngôn ngữ để phát triển Qt không support overloading phương thức, việc tạo thư viện sẽ phải đặt tên khác nhau cho các phương thức như drawRectRectF(), drawRectRect(), drawRectXYWH(). Solution sử dụng overloading phương thức gọn hơn.

Ví dụ 6: Minh họa việc khai báo và sử dụng overloading phương thức.

package net.vncoding;

class Sum {

    public int getSum() {
        
        return 0;
    }

    public int getSum(int x) {
        
        return x;
    }

    public int getSum(int x, int y) {
        
        return x + y;
    }
}

public class Overloading {

    public static void main(String[] args) {
        
        Sum s = new Sum();
        System.out.println(s.getSum());
        System.out.println(s.getSum(5));
        System.out.println(s.getSum(5, 10));        
    }
}

Giải thích:
Định nghĩa 3 phương thức getSum() với tham số khác nhau.

public int getSum(int x) {
    
    return x;
}

Phương thức này nhận 1 tham số đầu vào.

System.out.println(s.getSum());
System.out.println(s.getSum(5));
System.out.println(s.getSum(5, 10));

Chúng ta gọi 3 phương thức với cùng tên. Compiler sẽ phân biệt được các phương thức dựa vào số lượng và kiểu dữ liệu của tham số.

Kết quả:
0
5
15

Đệ quy phương thức trong Java

Đệ quy là khái niệm quen thuộc trong toán hoặc và khoa học máy tính. Nói 1 cách đơn giản đệ quy là phương thức gọi chính nó trong body. Đệ quy được sử dụng rộng rãi để giải quyết các task programming. Các vấn đề được giải quyết bằng đề quy, đều có thể giải quyết bằng vòng lặp.

Ví dụ 7: Sử dụng đệ quy tính giai thừa số nguyên

package net.vncoding;

public class Recursion {

    static int factorial(int n) {
        
        if (n == 0) {
            
            return 1;            
        } else {

            return n * factorial(n - 1);
        }
    }

    public static void main(String[] args) {
        
        System.out.println(factorial(6));
        System.out.println(factorial(15));        
    }
}

Giải thích:
Trong ví dụ này, minh họa việc sử dụng đệ quy để tính giai thừa của số nguyên.

return n * factorial(n - 1);

Bên trong body của phương thức factorial(), chúng ta gọi phương thức factorial() với đối số thay đổi. Hàm số gọi chính nó. Đây là bản chất của thuật toán đệ quy.

Kết quả:
720
2004310016

Phạm vi của phương thức

Biến được khai báo bên trong phương thức gọi là biến local, có phạm vi trong phương thức.

Ví dụ 8: Minh họa sử dụng biến local

package net.vncoding;

class Test {

    int x = 1;

    public void exec1() {
        
        System.out.println(this.x);
        System.out.println(x);
    }
    
    public void exec2() {
        
        int z = 5;
        
        System.out.println(x);
        System.out.println(z);
    }    
}

public class MethodScope {

    public static void main(String[] args) {

        Test ts = new Test();
        ts.exec1();
        ts.exec2();
    }
}

Giải thích:
Trong ví dụ, chúng ta có biến ‘x’ được định nghĩa bên ngoài phương thức. Biến ‘x’ có phạm vi class. Biến ‘x’ có giá trị với mọi nơi bên trong định nghĩa class Test.

public void exec1() {
    
    System.out.println(this.x);
    System.out.println(x);
}

Biến ‘x’, được gọi là biến instance, có thể được truy xuất thông qua từ khóa this và gọi biến ‘x’ trực tiếp.

public void exec2() {
    
    int z = 5;
    
    System.out.println(x);
    System.out.println(z);
}

Biến ‘x’ có thể được truy xuất trong phương thức exec2(). Biến ‘z’ được định nghĩa bên trong phương thức exec2(), có phạm vi phương thức (method scope). Và biến ‘z’ chỉ valid trong phương thức exec2().

Kết quả:
1
1
1
5

Biến được định nghĩa trong phương thức có phạm vi phương thức (local scope). Nếu biến local có cùng tên với biến instance. Biến instance vẫn có thể truy xuất từ bên trong phương thức bằng từ khóa this.

Ví dụ 9:Minh họa khi sử dụng biến local và biến instance cùng tên biến.

package net.vncoding;

class Test {

    int x = 1;

    public void exec() {
        
        int x = 3;

        System.out.println(this.x);
        System.out.println(x);
    }
}

public class Shadowing {

    public static void main(String[] args) {
        
        Test t = new Test();
        t.exec();
    }
}

Giải thích:
Chúng ta khai biến instance ‘x’. Chúng ta định nghĩa 1 biến local ‘x’ bên trong phương thức exec(). Cả 2 biến có cùng tên, nhưng chúng không xung đột vì phạm vi của mỗi biến là khác nhau.

System.out.println(this.x);
System.out.println(x);

Các biến được truy cập theo cách khác nhau. Biến ‘x’ bên trong phương thức, được gọi là biến local, truy xuất bởi tên biến. Còn biến instance ‘x’, có phạm vi clas, truy xuất bởi từ khóa this.

Kết quả:
1
3

Phương thức static

Phương thức static được gọi không cần thông qua object. Để gọi phương thức tĩnh, chúng ta sử dụng tên của class và toán tử chấm (.). Phương thức static chỉ có thể làm việc với biến static. Phương thức static thường được sử dụng để mô tả dữ liệu và tính toán mà không thay đổi trạng thái của object. 1 ví dụ là thư viện math bao gồm nhiều phương thức static cho phép toán khác nhau.
Chúng ta sử dụng từ khóa static để khai báo phương thức static và biến static. Khi không có từ khóa static, phương thức được gọi là phương thức instance. Chúng ta không thể sử dụng từ khóa this bên trong phương thức static. Từ khóa this chỉ được phép sử dụng trong phương thức instance.

Ví dụ 10: Minh họa cách sử dụng phương thức static.

package net.vncoding;

class Basic {

    static int id = 2321;

    public static void showInfo() {

        System.out.println("This is Basic class");
        System.out.format("The Id is: %d%n", id);
    }
}

public class StaticMethod {

    public static void main(String[] args) {

        Basic.showInfo();
    }
}

Giải thích:
Trong ví dụ, chúng ta định nghĩa phương thức tĩnh showInfo()

static int id = 2321;

Phương thức static chỉ có thể làm việc với biến static. Biến static không hợp lệ với phương thức instance.

public static void showInfo() {

    System.out.println("This is Basic class");
    System.out.format("The Id is: %d%n", id);
}

Đây là phương thức showInfo(). Nó làm việc với biến static ‘id’.

Basic.showInfo();

Để gọi phương thức static, chúng ta không cần object. Chúng ta gọi phương thức static bằng việc sử dụng tên class và toán tử chấm (.)

Kết quả:
This is Basic class
The Id is: 2321

Phương thức ẩn

Trong trường hợp phương thức tĩnh, phương thức trong class dẫn xuất (giống với signature như trong class base) ẩn phương thức trong class base. Phương thức được gọi được xác định khi thời điểm compile. Thủ tục này được gọi là early binding hoặc static binding.

Ví dụ 11: Minh họa sử dụng static binding.

package net.vncoding;

class Base {
    
    public static void showInfo() {
        
        System.out.println("This is Base class");
    }
}

class Derived extends Base {
    
    public static void showInfo() {
        
        System.out.println("This is Derived class");
    }
}

public class Hiding {

    public static void main(String[] args) {
    
        Base.showInfo();
        Derived.showInfo();
    }
}

Giải thích:
Chúng ta có 2 class: ‘Derived’ và ‘Base’. Class ‘Derived’ kế thừa từ class ‘Base’. Cả 2 class có phương thức showInfo().

class Derived extends Base {
    
    public static void showInfo() {
        
        System.out.println("This is Derived class");
    }
}

Phương thức static showInfo() của class ‘Derived’ ẩn phương thức showInfo() của class ‘Base’

Base.showInfo();
Derived.showInfo();

Chúng ta gọi phương thức showInfo() cho cả 2 class. Mỗi class gọi phương thức của nó.

Kết quả:
This is Base class
This is Derived class

Overriding phương thức

Overriding xảy ra khi chúng ta tạo phương thức instance của lớp dẫn xuất với (cùng signature và kiểu dữ liệu trả về như phương thức instance trong class cơ sở). Phương thức được thực thi được quyết định tại thời điểm chạy chương trình (runtime). Việc xác định phương thức được thực thi tại thời điểm runtime, được gọi là late binding hoặc dynamic binding.
Chúng ta có thể sử dụng @Override để thông báo cho compiler về việc sử dụng override phương thức trong superclass. Nó giúp prevent một vài lỗi lập trình.

Ví dụ 12: Minh họa việc sử dụng override phương thức.

package net.vncoding;

class Base {

    public void showInfo() {
        
        System.out.println("This is Base class");
    }
}

class Derived extends Base {

    @Override
    public void showInfo() {
        
        System.out.println("This is Derived class");
    }
}

public class Overriding {

    public static void main(String[] args) {

        Base[] objs = { new Base(), new Derived(), new Base(),
            new Base(), new Base(), new Derived() };

        for (Base obj : objs) {
            
            obj.showInfo();
        }
    }
}

Giải thích:
Chúng ta tạo mảng các object của class ‘Base’ và ‘Derived’. Chúng ta duyệt mảng và gọi phương thức showInfo()

@Override
public void showInfo() {
    
    System.out.println("This is Derived class");
}

Chúng ta override phương thức showInfo() của class ‘Base’.

Base[] objs = { new Base(), new Derived(), new Base(),
    new Base(), new Base(), new Derived() };

Đây là chúng ta tạo 1 mảng các object ‘Base’ và ‘Derived’. Class ‘Derived’ có thể được convert thành class ‘Base’, vì nó kế thừa class ‘Base’. Điều ngược lại là không đúng. Cách duy nhất để định nghĩa các object khác nhau trong 1 mảng là sử dụng kiểu share bởi các object.

for (Base obj : objs) {
    
    obj.showInfo();
}

Chúng ta duyệt mảng và gọi showInfo() bởi tất cả các object trong mảng. Phương thức được gọi được quyết định tại thời điểm runtime.

Kết quả:
This is Base class
This is Derived class
This is Base class
This is Base class
This is Base class
This is Derived class

Với từ khóa super, có thể gọi phương thức được override.

Ví dụ 13: Minh họa về sử dụng overriding phương thức

package net.vncoding;

class Base {

    public void showInfo() {

        System.out.println("This is Base class");
    }
}

class Derived extends Base {

    @Override
    public void showInfo() {

        System.out.println("This is Derived class");
    }

    public void showBaseInfo() {
        
        super.showInfo();
    }
}

public class Overriding2 {

    public static void main(String[] args) {

        Derived d = new Derived();
        d.showBaseInfo();
    }
}

Giải thích:
Gọi phương thức showInfo() của class ‘Base’ với từ khóa super.

public void showBaseInfo() {
    
    super.showInfo();
}

Gọi phương thức showInfo() parent bằng từ khóa super.

Kết quả:
This is Base class

Phương thức final

Không thể override hoặc ẩn phương thức final bởi class dẫn xuất. Điều này được sử dụng để prevent các hoạt động không mong muốn từ subclass.

Ví dụ 14: Minh họa sử dụng phương thức final

package net.vncoding;

class Base {
    
    public void f1() {
        
        System.out.println("f1 of the Base");
    }
    
    public final void f2() {
        
        System.out.println("f2 of the Base");
    }    
}

class Derived extends Base {
    
    @Override
    public void f1() {
        
        System.out.println("f1 of the Derived");
    }
    
//    @Override
//    public void f2() {
//        
//        System.out.println("f2 of the Derived");
//    }     
}


public class FinalMethods {

    public static void main(String[] args) {
        
        Base b = new Base();
        b.f1();
        b.f2();
        
        Derived d = new Derived();
        d.f1();
        d.f2();           
    }
}

Giải thích:
Trong ví dụ này, chúng ta có phương thức final f2() trong class ‘Base’. Đây là phương thức không cho phép override.

public final void f2() {
    
    System.out.println("f2 of the Base");
} 

Phương thức f2() được khai báo với từ khóa final.

@Override
public void f1() {
    
    System.out.println("f1 of the Derived");
}

Trong class dẫn xuất, chúng ta có thể override phương thức f1() của lớp cơ sở ‘Base’. Chúng ta sử dụng @Override để thông báo cho compiler that chúng ta đang override phương thức.

//    @Override
//    public void f2() {
//        
//        System.out.println("f2 of the Derived");
//    } 

Dòng code này bị comment vì compiler sẽ thông báo lỗi khi biên dịch code nếu bỏ comment đi. Compiler thông báo như sau: “Exception in thread “main” java.lang.VerifyError: class net.vncoding.Derived overrides final method f2″

d.f2(); 

Vì không thể override phương thức final, dòng code này sẽ gọi f2() của class cơ sở ‘Base’

Kết quả:
f1 of the Base
f2 of the Base
f1 of the Derived
f2 of the Base

Be the first to comment

Leave a Reply