+3

Hàm trong Java (phần 1)

Giới thiệu

Trong ngôn ngữ lập trình Java, một hàm (function) là một khối code chứa một tập hợp các câu lệnh được đặt tên và thường thực hiện một nhiệm vụ cụ thể. Hàm được sử dụng để thực hiện một tác vụ cụ thể và có thể được gọi từ bất kỳ đâu trong chương trình Java. Việc sử dụng hàm giúp tái sử dụng code, tạo cấu trúc rõ ràng và dễ quản lý trong các ứng dụng phức tạp.

Dưới đây là một ví dụ giới thiệu về một hàm trong Java:

public class HelloWorld {
    // Đây là một hàm đơn giản có tên là "printHello"
    public static void printHello() {
        System.out.println("Hello, world!");
    }

    public static void main(String[] args) {
        // Gọi hàm "printHello" để in chuỗi "Hello, world!"
        printHello();
    }
}

Trong ví dụ trên:

  • Ta định nghĩa một hàm có tên là "printHello" bằng cách sử dụng khai báo "public static void." Đoạn code bên trong hàm là System.out.println("Hello, world!");, và hàm sẽ in ra chuỗi "Hello, world!" khi hàm được gọi.

  • Hàm "printHello" có thể được gọi từ bất kỳ đâu trong chương trình Java. Trong phần main của chương trình, ta gọi hàm "printHello" để thực hiện nhiệm vụ in chuỗi "Hello, world!" ra màn hình.

  • Hàm "printHello" trong ví dụ này là một hàm đơn giản, một hàm có thể có tham số (parameters) và trả về giá trị (return values) tùy theo yêu cầu cụ thể của chương trình.

Sử dụng hàm giúp tạo modular và dễ dàng quản lý code. Bạn có thể định nghĩa hàm để thực hiện các tác vụ cụ thể, sau đó gọi hàm này từ các phần khác của chương trình mà cần thực hiện các tác vụ đó.

Định nghĩa một hàm

Công thức tổng quát cho định nghĩa một hàm trong Java có thể được mô tả như sau:

[Quyền truy cập] [Kiểu dữ liệu trả về] [Tên hàm]([Danh sách tham số]) {
    // Khối lệnh (Body)
    [Các câu lệnh và logic thực hiện bởi hàm]

    // Lệnh trả về (nếu có)
    [return Giá_trị];
}

Hãy xét các thành phần chính trong công thức này:

  1. Quyền truy cập: Đây là modifier như public, private, protected, hoặc không có modifier. Nó xác định ai có thể truy cập hàm.

  2. Kiểu dữ liệu trả về: Đây là kiểu dữ liệu của giá trị mà hàm trả về. Sử dụng void nếu hàm không trả về giá trị.

  3. Tên hàm: Đây là tên bạn đặt cho hàm, tuân theo quy tắc đặt tên biến của Java.

  4. Danh sách tham số: Đây là danh sách các tham số đầu vào mà hàm nhận. Mỗi tham số bao gồm kiểu dữ liệu và tên.

  5. Khối lệnh (Body): Đây là nơi bạn đặt logic của hàm, bao gồm các câu lệnh và tính toán.

  6. Lệnh trả về: Nếu kiểu trả về không phải void, bạn cần sử dụng lệnh return để trả về giá trị phù hợp với kiểu dữ liệu đã định.

Dưới đây là một ví dụ về cách sử dụng công thức này để định nghĩa một hàm:

public int addNumbers(int a, int b) {
    int sum = a + b;
    return sum;
}

Trong ví dụ này:

  • public là quyền truy cập.
  • int là kiểu dữ liệu trả về.
  • addNumbers là tên hàm.
  • (int a, int b) là danh sách tham số.
  • Khối lệnh (body) tính tổng của hai tham số và trả về giá trị bằng lệnh return.

Gọi hàm

Để gọi một hàm trong Java, bạn cần sử dụng tên của hàm cùng với đối số (arguments) phù hợp với danh sách tham số (parameters) đã định nghĩa trong hàm đó. Dưới đây là cách gọi một hàm trong Java:

  1. Gọi hàm trong cùng một lớp (class):

    returnType result = functionName(argument1, argument2, ...);
    

    Ví dụ:

    int sum = addNumbers(5, 3);
    

    Trong ví dụ này, addNumbers là tên hàm, ta thực hiện gọi nó với hai đối số, 53. Kết quả của hàm (nếu có) sẽ được lưu trong biến sum.

  2. Gọi hàm từ một lớp (class) khác: Để gọi một hàm từ một lớp (class) khác, bạn cần tạo một đối tượng của lớp đó (nếu hàm không phải là static) và sau đó sử dụng đối tượng đó để gọi hàm.

    Ví dụ:

    // Tạo một đối tượng của lớp khác
    OtherClass obj = new OtherClass();
    
    // Gọi hàm từ lớp khác
    int result = obj.someMethod(argument1, argument2);
    
  3. Gọi hàm tĩnh (static): Nếu một hàm được định nghĩa là tĩnh (static), bạn có thể gọi nó trực tiếp từ tên lớp mà hàm thuộc về, không cần tạo đối tượng.

    Ví dụ:

    int result = MyClass.staticMethod(argument1, argument2);
    

    Trong ví dụ này, staticMethod là một hàm tĩnh thuộc lớp MyClass, và ta thực hiện gọi hàm này trực tiếp từ tên lớp.

Ngoài ra, hãy chắc chắn rằng bạn đã tuân theo kiểu dữ liệu và số lượng đối số đúng với định nghĩa của hàm để tránh lỗi biên dịch.

Hàm void và hàm trả về giá trị

Trong lập trình Java (cũng như trong nhiều ngôn ngữ lập trình khác), có hai loại hàm chính: hàm không giá trị trả về (void methods) và hàm có giá trị trả về (value-returning methods). Dưới đây là mô tả chi tiết về cả hai loại:

  1. Hàm không giá trị trả về (void methods):

    • Định nghĩa: Hàm không giá trị trả về (void method) là một loại hàm mà khi được gọi, nó thực hiện một chuỗi các câu lệnh, thường là để thực hiện một tác vụ cụ thể mà không trả về kết quả. Hàm không có kiểu dữ liệu trả về, thường được khai báo với kiểu dữ liệu void. Một ví dụ phổ biến là phương thức main trong Java.

    • Cú pháp định nghĩa hàm không giá trị trả về:

      modifiers void methodName(parameters) {
          // Các câu lệnh để thực hiện tác vụ
      }
      
    • Ví dụ:

      public void printHello() {
          System.out.println("Hello, World!");
      }
      
  2. Hàm có giá trị trả về (value-returning methods):

    • Định nghĩa: Hàm có giá trị trả về (value-returning method) là một loại hàm mà khi được gọi, nó thực hiện một chuỗi các câu lệnh và trả về một giá trị kết quả dựa trên tác vụ mà nó thực hiện. Hàm này được khai báo với một kiểu dữ liệu cụ thể (ngoài kiểu void) để chỉ rõ kiểu dữ liệu của giá trị trả về.

    • Cú pháp định nghĩa hàm có giá trị trả về:

      returnType methodName(parameters) {
          // Các câu lệnh để thực hiện tác vụ
          return value; // Trả về giá trị
      }
      
    • Ví dụ:

      public int add(int a, int b) {
          return a + b;
      }
      

Hàm không giá trị trả về thường được sử dụng khi bạn muốn thực hiện một tác vụ mà không cần kết quả trả về, trong khi hàm có giá trị trả về được sử dụng khi bạn cần thực hiện một tác vụ và thu thập kết quả của nó.

Các ví dụ

Ví dụ 1

Dưới đây là một ví dụ về cách sử dụng một hàm Java để tính ước chung lớn nhất (GCD) của hai số sử dụng phương pháp Euclid. Để làm điều này, ta sẽ tạo một hàm findGCD và sau đó sử dụng nó trong phương thức main để tính GCD của hai số nguyên.

public class GreatestCommonDivisorMethod {

    // Hàm để tính ước chung lớn nhất sử dụng phương pháp Euclid
    public static int findGCD(int num1, int num2) {
        if (num2 == 0) {
            return num1;
        } else {
            return findGCD(num2, num1 % num2);
        }
    }

    public static void main(String[] args) {
        int number1 = 48;
        int number2 = 18;

        // Gọi hàm findGCD để tính ước chung lớn nhất của number1 và number2
        int gcd = findGCD(number1, number2);

        System.out.println("Ước chung lớn nhất của " + number1 + " và " + number2 + " là " + gcd);
    }
}

Trong ví dụ này, chúng ta có một hàm findGCD(int num1, int num2) để tính ước chung lớn nhất của hai số nguyên num1num2 bằng cách sử dụng phương pháp Euclid. Phương pháp này lặp lại việc chia số lớn cho số nhỏ và thay thế số lớn bằng số nhỏ cho đến khi số nhỏ trở thành 0, lúc đó số lớn chính là GCD.

Trong phương thức main, ta gọi hàm findGCD để tính ước chung lớn nhất của hai số 48 và 18 và in kết quả ra màn hình.

Ví dụ 2

Dưới đây là một ví dụ về cách sử dụng hàm trong một bài toán thực tế để tính tiền lương hàng tháng cho các nhân viên trong một công ty. Trong bài toán này, ta sẽ tạo một hàm để tính tiền lương dựa trên lương cơ bản và số giờ làm việc hàng tháng của mỗi nhân viên.

public class EmployeeSalaryCalculator {

    // Hàm để tính tiền lương của một nhân viên
    public static double calculateMonthlySalary(double hourlyWage, int hoursWorked) {
        // Số giờ làm việc mà một nhân viên làm trong tháng
        int hoursInMonth = 160; // Giả sử một tháng có 160 giờ làm việc

        // Tính tiền lương hàng tháng
        double monthlySalary = hourlyWage * hoursWorked;

        // Kiểm tra xem nhân viên làm quá giờ và tính tiền thêm
        if (hoursWorked > hoursInMonth) {
            int overtimeHours = hoursWorked - hoursInMonth;
            double overtimePay = overtimeHours * (hourlyWage * 1.5); // Tiền thêm giờ làm việc
            monthlySalary += overtimePay;
        }

        return monthlySalary;
    }

    public static void main(String[] args) {
        // Thông tin của nhân viên
        String employeeName = "Nguyen Van A";
        double hourlyWage = 12.5; // Lương cơ bản mỗi giờ
        int hoursWorked = 176;    // Số giờ làm việc trong tháng

        // Gọi hàm calculateMonthlySalary để tính tiền lương hàng tháng
        double monthlySalary = calculateMonthlySalary(hourlyWage, hoursWorked);

        // In kết quả
        System.out.println("Nhân viên: " + employeeName);
        System.out.println("Tiền lương hàng tháng: $" + monthlySalary);
    }
}

Trong ví dụ này, ta có một hàm calculateMonthlySalary, nhận vào lương cơ bản mỗi giờ (hourlyWage) và số giờ làm việc trong tháng (hoursWorked) của một nhân viên. Hàm này tính tiền lương hàng tháng dựa trên thông tin này. Nếu nhân viên làm quá giờ (số giờ làm việc vượt quá 160 giờ), hàm sẽ tính tiền thêm cho giờ làm việc thêm giờ.

Trong phương thức main, ta thực hiện khai báo thông tin của nhân viên, sau đó gọi hàm calculateMonthlySalary để tính tiền lương hàng tháng và in kết quả ra màn hình.

Ví dụ 3

Thuật toán Kruskal là một thuật toán quan trọng trong lý thuyết đồ thị và được sử dụng để tìm cây bao trùm tối thiểu (Minimum Spanning Tree) trong một đồ thị có trọng số. Dưới đây là một ví dụ về việc sử dụng hàm trong thuật toán Kruskal để tìm cây bao trùm tối thiểu của một đồ thị:

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

class Edge {
    int source, destination, weight;

    public Edge(int source, int destination, int weight) {
        this.source = source;
        this.destination = destination;
        this.weight = weight;
    }
}

class Graph {
    int vertices;
    List<Edge> edges;

    public Graph(int vertices) {
        this.vertices = vertices;
        edges = new ArrayList<>();
    }

    public void addEdge(int source, int destination, int weight) {
        edges.add(new Edge(source, destination, weight));
    }

    public List<Edge> kruskalMST() {
        List<Edge> minimumSpanningTree = new ArrayList<>();

        // Sắp xếp danh sách các cạnh theo trọng số
        edges.sort(Comparator.comparingInt(e -> e.weight));

        // Tạo một mảng để theo dõi thành phần kết nối của mỗi đỉnh
        int[] parent = new int[vertices];
        for (int i = 0; i < vertices; i++) {
            parent[i] = i;
        }

        int edgeCount = 0;
        int i = 0;
        while (edgeCount < vertices - 1) {
            Edge currentEdge = edges.get(i);
            int sourceParent = find(parent, currentEdge.source);
            int destinationParent = find(parent, currentEdge.destination);

            if (sourceParent != destinationParent) {
                minimumSpanningTree.add(currentEdge);
                union(parent, sourceParent, destinationParent);
                edgeCount++;
            }

            i++;
        }

        return minimumSpanningTree;
    }

    private int find(int[] parent, int vertex) {
        if (parent[vertex] != vertex) {
            return find(parent, parent[vertex]);
        }
        return vertex;
    }

    private void union(int[] parent, int x, int y) {
        int xSet = find(parent, x);
        int ySet = find(parent, y);
        parent[ySet] = xSet;
    }
}

public class KruskalExample {
    public static void main(String[] args) {
        Graph graph = new Graph(6);
        graph.addEdge(0, 1, 4);
        graph.addEdge(0, 2, 4);
        graph.addEdge(1, 2, 2);
        graph.addEdge(1, 3, 3);
        graph.addEdge(1, 4, 7);
        graph.addEdge(2, 3, 5);
        graph.addEdge(2, 4, 7);
        graph.addEdge(3, 4, 2);
        graph.addEdge(3, 5, 5);
        graph.addEdge(4, 5, 2);

        List<Edge> minimumSpanningTree = graph.kruskalMST();
        for (Edge edge : minimumSpanningTree) {
            System.out.println(edge.source + " - " + edge.destination + " : " + edge.weight);
        }
    }
}

Trong ví dụ này, chúng ta sử dụng một lớp Edge để đại diện cho các cạnh trong đồ thị và một lớp Graph để thực hiện thuật toán Kruskal. Hàm kruskalMST trong lớp Graph tính toán cây bao trùm tối thiểu sử dụng thuật toán Kruskal. Hàm findunion được sử dụng để thực hiện việc tìm kiếm và kết hợp các thành phần kết nối trong đồ thị.

Lời kết

Trong bài viết này, chúng ta đã tìm hiểu về cách sử dụng hàm trong ngôn ngữ lập trình Java. Hàm là một khối code có thể thực hiện một tác vụ cụ thể và có thể được gọi lại từ bất kỳ đâu trong chương trình. Chúng ta đã xem xét cách định nghĩa hàm cũng như cú pháp chung cho việc khai báo hàm. Chúng ta cũng đã tìm hiểu về hai loại hàm chính trong Java: hàm không giá trị trả về và hàm có giá trị trả về, và cách sử dụng chúng.

Cuối cùng, ta đã xem xét một số ví dụ về cách sử dụng hàm trong các tình huống thực tế, từ việc tính toán ước chung lớn nhất đến tính tiền lương hàng tháng của nhân viên và cả việc tìm cây bao trùm tối thiểu trong một đồ thị. Sử dụng hàm giúp tạo modular trong code, giúp quản lý và tái sử dụng code một cách hiệu quả. Hy vọng rằng bài viết này đã giúp bạn hiểu hơn về cách sử dụng hàm trong Java và cách chúng có thể được áp dụng trong việc giải quyết các vấn đề lập trình phức tạp.


©️ Tác giả: Trần Quang Hiệp từ Viblo


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.