Học lập trình Java cơ bản cho người mới bắt đầu phần 2
Published: 2023-08-01 08:31:59

Ở phần đầu [Học lập trình Java cơ bản cho người mới bắt đầu phần 1](https://dev.to/phamtuanchip/hoc-lap-trinh-java-co-ban-cho-nguoi-moi-bat-dau-phan-1-5dl4)
các bạn đã được làm quen với việc học lập trình java cơ bản từ cài đặt môi trường lập trình, thử với 1 chương trình đơn giản, hiểu thế nào là lập trình hướng đối tượng, các tính chất và đặc điểm cơ bản. 
Ở phần tiếp theo mình sẽ bắt đầu đi sâu vào phần lập trình với java:


- Các kiểu dữ liệu, cấu trúc điều kiện, cấu trúc lặp
- Lập trình đa luồng
- Làm việc với dữ liệu vào ra
- Lập trình phân tán, thực hiện tính toán từ xa

## **Bước 4: Kiểu dữ liệu, vòng lặp và câu điều kiện**
Trong Java, có hai loại kiểu dữ liệu chính: kiểu dữ liệu nguyên thủy (Primitive Data Types) và kiểu dữ liệu tham chiếu (Reference Data Types).

**1. Kiểu dữ liệu nguyên thủy (Primitive Data Types):**
Kiểu dữ liệu nguyên thủy là các kiểu dữ liệu cơ bản được hỗ trợ bởi ngôn ngữ Java. Java cung cấp 8 kiểu dữ liệu nguyên thủy:

- `byte`: Kiểu số nguyên 8-bit, giá trị từ -128 đến 127.
- `short`: Kiểu số nguyên 16-bit, giá trị từ -32,768 đến 32,767.
- `int`: Kiểu số nguyên 32-bit, giá trị từ -2^31 đến 2^31 - 1.
- `long`: Kiểu số nguyên 64-bit, giá trị từ -2^63 đến 2^63 - 1.
- `float`: Kiểu số thực 32-bit, chứa dấu thập phân và số mũ.
- `double`: Kiểu số thực 64-bit, chứa dấu thập phân và số mũ (sử dụng phổ biến hơn float).
- `char`: Kiểu ký tự Unicode 16-bit, chứa một ký tự Unicode.
- `boolean`: Kiểu boolean chỉ có hai giá trị true hoặc false.

**2. Kiểu dữ liệu tham chiếu (Reference Data Types):**
Kiểu dữ liệu tham chiếu đại diện cho đối tượng và các kiểu dữ liệu phức tạp hơn so với kiểu dữ liệu nguyên thủy. Kiểu dữ liệu tham chiếu bao gồm:

- Kiểu lớp (Class Types): Đối tượng được tạo từ các lớp đã định nghĩa hoặc lớp mà người dùng tự định nghĩa.
- Kiểu mảng (Array Types): Kiểu dữ liệu dùng để lưu trữ một tập hợp các giá trị cùng kiểu.

Ví dụ về kiểu dữ liệu tham chiếu:

```java
public class MyClass {
    // Kiểu lớp
    String name = "John";
    // Kiểu mảng
    int[] numbers = {1, 2, 3, 4, 5};
}
```

Trong ví dụ trên, `name` là một đối tượng kiểu lớp `String`, và `numbers` là một mảng kiểu `int`.

Kiểu dữ liệu tham chiếu lưu trữ tham chiếu (địa chỉ bộ nhớ) tới vùng nhớ chứa giá trị thực sự của đối tượng. Nó không lưu trữ giá trị thực sự của đối tượng trong biến, mà chỉ lưu trữ địa chỉ mà đối tượng được tham chiếu tới.

**3. Các loại câu điều kiện logic**
Câu điều kiện là một cấu trúc lập trình trong Java (cũng như trong các ngôn ngữ lập trình khác) cho phép kiểm tra điều kiện và thực thi các mã khác nhau dựa trên kết quả của điều kiện đó. Có hai loại câu điều kiện chính trong Java: câu điều kiện if và câu điều kiện switch.

**1. Câu điều kiện if:**

Câu điều kiện if dùng để kiểm tra một biểu thức logic, nếu biểu thức này trả về true thì khối mã bên trong if sẽ được thực thi, ngược lại nếu biểu thức trả về false thì khối mã trong if sẽ được bỏ qua.

**Cú pháp:**
```java
if (biểu_thức_logic) {
    // Thực hiện khi biểu thức trả về true
} else {
    // Thực hiện khi biểu thức trả về false (tuỳ chọn)
}
```

**Ví dụ:**

```java
int age = 20;
if (age >= 18) {
    System.out.println("Bạn là người trưởng thành.");
} else {
    System.out.println("Bạn là người vị thành niên.");
}
```

**2. Câu điều kiện switch:**

Câu điều kiện switch dùng để kiểm tra giá trị của một biểu thức và thực hiện các hành động khác nhau dựa trên giá trị đó. Câu điều kiện switch hoạt động tốt khi có nhiều trường hợp khác nhau và sử dụng cho một giá trị cụ thể.

**Cú pháp:**
```java
switch (biểu_thức) {
    case giá_trị_1:
        // Thực hiện khi biểu_thức trả về giá_trị_1
        break;
    case giá_trị_2:
        // Thực hiện khi biểu_thức trả về giá_trị_2
        break;
    // và tiếp tục các trường hợp khác (tuỳ chọn)
    default:
        // Thực hiện khi không có trường hợp nào khớp (tuỳ chọn)
}
```

**Ví dụ:**

```java
int dayOfWeek = 2;
switch (dayOfWeek) {
    case 1:
        System.out.println("Hôm nay là Chủ nhật.");
        break;
    case 2:
        System.out.println("Hôm nay là Thứ Hai.");
        break;
    // và tiếp tục các trường hợp khác
    default:
        System.out.println("Hôm nay là một ngày khác.");
}
```

Câu điều kiện if và câu điều kiện switch là những công cụ quan trọng để kiểm soát luồng thực thi của chương trình dựa trên các điều kiện khác nhau.

**4. Cấu trúc lặp**
Trong Java, có ba loại cấu trúc lặp để thực hiện các vòng lặp khác nhau: cấu trúc lặp for, cấu trúc lặp while và cấu trúc lặp do-while.

**1. Cấu trúc lặp for:**
Cấu trúc lặp for được sử dụng khi bạn biết trước số lần lặp cụ thể mà bạn muốn thực hiện. Nó được sử dụng phổ biến để duyệt qua các phần tử trong mảng hoặc thực hiện một số lệnh trong một phạm vi xác định.

**Cú pháp:**
```java
for (biểu_thức_khởi_tạo; biểu_thức_điều_kiện; biểu_thức_tăng_giảm) {
    // Khối mã lệnh sẽ được thực thi trong vòng lặp
}
```

**Ví dụ:**
```java
for (int i = 1; i <= 5; i++) {
    System.out.println("Giá trị của i là: " + i);
}
```

**2. Cấu trúc lặp while:**
Cấu trúc lặp while được sử dụng khi số lần lặp không biết trước và lặp sẽ tiếp tục cho đến khi điều kiện không còn đúng (false).

**Cú pháp:**
```java
while (biểu_thức_điều_kiện) {
    // Khối mã lệnh sẽ được thực thi trong vòng lặp
}
```

**Ví dụ:**
```java
int count = 1;
while (count <= 5) {
    System.out.println("Giá trị của count là: " + count);
    count++;
}
```

**3. Cấu trúc lặp do-while:**
Cấu trúc lặp do-while cũng giống như cấu trúc lặp while, nhưng nó kiểm tra điều kiện sau khi thực hiện khối mã lệnh ít nhất một lần.

**Cú pháp:**
```java
do {
    // Khối mã lệnh sẽ được thực thi trong vòng lặp
} while (biểu_thức_điều_kiện);
```

**Ví dụ:**
```java
int count = 1;
do {
    System.out.println("Giá trị của count là: " + count);
    count++;
} while (count <= 5);
```

Cả ba loại cấu trúc lặp trong Java đều giúp bạn lặp lại các hành động nhiều lần và tùy chỉnh luồng thực thi của chương trình theo yêu cầu của bạn. Tùy chọn nào sẽ được sử dụng phụ thuộc vào các yêu cầu cụ thể của từng tình huống lập trình.

> Như vậy giống như tất cả các ngôn ngữ lập trình khác, các kiểu dữ liệu và cấu trúc điều kiện, vòng lặp là các CÔNG CỤ để hỗ trợ các bạn viết các chương trình theo cách mình mong muốn để thự hiện được một công việc nào đó như tính toán. Sự kết hợp những yếu tố cơ bản trên của bất kỳ ngôn ngữ lập trình nào cũng để giải quyết một yêu cầu cụ thể nào đó đấy chính là những khái niệm tiền thân của lập trình phần mềm.
Các bạn có thể tiếp tục thử nghiệm các khái niệm đó ở đây https://www.tutorialspoint.com/online_java_compiler.php

## **Bước 5: Đa luồng (Multithreading)**

Khái niệm "đa luồng" trong Java là một tính năng quan trọng của Lập trình đa luồng (Multithreading), cho phép chạy nhiều luồng (threads) cùng một lúc trong một chương trình. Mỗi luồng là một đơn vị thực thi riêng biệt, có thể thực hiện các tác vụ độc lập và đồng thời chia sẻ các tài nguyên của chương trình.

**Vì sao cần có đa luồng trong Java:**

1. **Tăng hiệu suất và đáp ứng:** Khi một chương trình chạy đa luồng, nó có thể thực hiện nhiều tác vụ cùng một lúc. Điều này giúp tăng hiệu suất chương trình và đáp ứng với các yêu cầu tác vụ người dùng nhanh hơn.

2. **Chia sẻ tài nguyên:** Đa luồng cho phép các luồng chia sẻ tài nguyên của chương trình như bộ nhớ và các biến dữ liệu. Điều này giúp tối ưu hóa việc sử dụng tài nguyên và tiết kiệm không gian bộ nhớ.

3. **Tận dụng các lần chờ đợi (waiting time):** Trong các trường hợp một luồng phải chờ đợi một tác vụ (ví dụ: đọc dữ liệu từ ổ đĩa, chờ phản hồi từ máy chủ), chương trình có thể chuyển sang thực hiện một tác vụ khác trong khi đợi, tận dụng hiệu quả thời gian chờ đợi đó.

4. **Phân chia tác vụ phức tạp:** Đa luồng giúp phân chia các tác vụ phức tạp thành các phần nhỏ và thực hiện chúng đồng thời, dễ dàng quản lý và tái sử dụng mã.

**Cách thức thực hiện đa luồng trong Java:**

Java hỗ trợ đa luồng thông qua lớp Thread và giao diện Runnable. Để thực hiện đa luồng trong Java, bạn có thể:

1. **Kế thừa từ lớp Thread:** Định nghĩa một lớp con kế thừa từ lớp Thread và ghi đè phương thức run() để xác định công việc mà luồng cần thực hiện.

```java
public class MyThread extends Thread {
    public void run() {
        // Thực hiện công việc của luồng
    }
}
```

2. **Thực hiện giao diện Runnable:** Định nghĩa một lớp thực hiện giao diện Runnable và ghi đè phương thức run() để xác định công việc mà luồng cần thực hiện.

```java
public class MyRunnable implements Runnable {
    public void run() {
        // Thực hiện công việc của luồng
    }
}
```

Sau khi định nghĩa luồng, bạn có thể khởi tạo và chạy các luồng đó trong chương trình của mình. Việc chạy nhiều luồng cùng một lúc sẽ giúp chương trình của bạn thực hiện nhiều tác vụ một cách hiệu quả và tăng cường hiệu suất của ứng dụng.

**Ví dụ**
```java
public class MyThread extends Thread {
    public void run() {
        for (int i = 1; i <= 5; i++) {
            System.out.println("Thread: " + i);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread thread1 = new MyThread();
        MyThread thread2 = new MyThread();

        thread1.start();
        thread2.start();
    }
}
```

- Dòng 1 định nghĩa một lớp con `MyThread` kế thừa từ lớp `Thread`. Điều này cho phép lớp `MyThread` sử dụng tính năng đa luồng của Java.
- Trong lớp `MyThread`, chúng ta định nghĩa phương thức `run()`. Đây là nơi thực hiện công việc của luồng. Trong ví dụ này, chúng ta in ra các số từ 1 đến 5.
- Trong phương thức `main`, chúng ta tạo hai luồng mới (`thread1` và `thread2`) từ lớp `MyThread`. Sau đó, chúng ta gọi phương thức `start()` của mỗi luồng để bắt đầu thực thi phương thức `run()`.

## **Bước 6: Input/Output (IO) làm việc với file**

Trong Java, "IO" là viết tắt của "Input/Output", đại diện cho các hoạt động đọc và ghi dữ liệu vào các nguồn và đích dữ liệu. IO trong Java được sử dụng để truyền dữ liệu giữa chương trình Java và các nguồn/đích bên ngoài, như file, mạng, bàn phím và màn hình.

**Vì sao cần làm việc với IO và file trong Java:**

1. **Đọc và ghi dữ liệu:** IO cho phép bạn đọc dữ liệu từ các nguồn như file, mạng, và bàn phím và ghi dữ liệu vào các đích như file hoặc mạng. Điều này rất hữu ích để lấy dữ liệu từ nguồn bên ngoài hoặc lưu trữ dữ liệu của chương trình.

2. **Lưu trữ và truyền thông tin:** IO được sử dụng để lưu trữ dữ liệu vào các file và truyền thông tin qua mạng. Điều này cho phép bạn lưu trữ dữ liệu lâu dài và chia sẻ thông tin giữa các ứng dụng và người dùng.

3. **Xử lý tập tin và dữ liệu:** IO cho phép bạn thực hiện các hoạt động xử lý dữ liệu trong tập tin như đọc, ghi, cập nhật và xóa dữ liệu. Điều này giúp bạn quản lý dữ liệu một cách hiệu quả và thực hiện các tác vụ liên quan đến dữ liệu.

4. **Đọc và ghi dữ liệu từ mạng:** IO trong Java cung cấp các công cụ để đọc và ghi dữ liệu thông qua kết nối mạng. Điều này cho phép bạn giao tiếp với các dịch vụ web, máy chủ và nguồn dữ liệu từ xa.

**Cách làm việc với IO và file trong Java:**

Java cung cấp một số lớp và giao diện để làm việc với IO và file. Một số lớp phổ biến trong Java IO bao gồm:

- `InputStream` và `OutputStream`: Để đọc và ghi các byte dữ liệu từ/đến một nguồn/đích byte như file hoặc mạng.
- `Reader` và `Writer`: Để đọc và ghi dữ liệu văn bản từ/đến một nguồn/đích ký tự như file hoặc mạng.
- `File`: Để thao tác với các thông tin liên quan đến file như đường dẫn, kích thước, và quyền truy cập.
- `FileInputStream`, `FileOutputStream`, `FileReader`, và `FileWriter`: Các lớp con của InputStream và OutputStream, Reader và Writer, được sử dụng để thực hiện các hoạt động đọc và ghi dữ liệu từ/đến file.

Để làm việc với IO và file trong Java, bạn cần sử dụng các lớp và giao diện này, mở các luồng dữ liệu, thực hiện các hoạt động đọc/ghi dữ liệu và đóng luồng sau khi hoàn tất công việc.

**Ví dụ**

```java
import java.io.*;

public class FileExample {
    public static void main(String[] args) {
        try {
            // Ghi dữ liệu vào tệp
            FileWriter writer = new FileWriter("example.txt");
            writer.write("Hello, this is an example.");
            writer.close();

            // Đọc dữ liệu từ tệp
            FileReader reader = new FileReader("example.txt");
            int character;
            while ((character = reader.read()) != -1) {
                System.out.print((char) character);
            }
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
```

- Trong phần "Ghi dữ liệu vào tệp", chúng ta sử dụng lớp `FileWriter` để mở và ghi dữ liệu vào tệp "example.txt".
- Trong phần "Đọc dữ liệu từ tệp", chúng ta sử dụng lớp `FileReader` để mở và đọc dữ liệu từ tệp "example.txt". Dữ liệu được đọc một ký tự mỗi lần, và chúng ta sử dụng vòng lặp `while` để đọc đến khi gặp ký tự kết thúc tệp (-

1). Khi đọc, chúng ta in ra các ký tự đó lên màn hình.

## **Bước 7: Java Network RMI**
 
Java RMI (Remote Method Invocation) là một cơ chế trong Java cho phép các ứng dụng chạy trên máy tính khác nhau có thể gọi và thực thi các phương thức trên nhau qua mạng, giúp tạo ra sự tương tác giữa các ứng dụng phân tán. Cần sử dụng Java RMI vì một số lý do quan trọng sau đây:

1. **Kết nối giữa các ứng dụng phân tán:** Java RMI cho phép kết nối và giao tiếp giữa các ứng dụng chạy trên các máy tính khác nhau. Điều này rất hữu ích khi bạn muốn phân tán công việc của ứng dụng trên nhiều máy tính để tăng hiệu suất và tối ưu hóa tài nguyên.

2. **Tích hợp ứng dụng phân tán:** Java RMI giúp bạn tích hợp các thành phần và dịch vụ của ứng dụng từ nhiều máy tính và hệ thống khác nhau thành một ứng dụng lớn hoàn chỉnh. Điều này giúp bạn quản lý ứng dụng một cách hiệu quả và dễ dàng mở rộng chúng.

3. **Phân chia tác vụ phức tạp:** Java RMI cho phép bạn phân chia các tác vụ phức tạp thành các thành phần nhỏ và thực thi chúng trên các máy tính riêng biệt. Điều này giúp bạn quản lý tác vụ một cách hiệu quả và giảm tải công việc trên một máy tính duy nhất.

4. **Tạo dịch vụ phân tán:** Java RMI cho phép bạn xây dựng các dịch vụ phân tán, nơi các máy tính khác nhau có thể gọi và sử dụng các chức năng của dịch vụ đó thông qua mạng. Điều này rất hữu ích trong việc tạo ra các ứng dụng web phức tạp và các dịch vụ mạng phân tán.

5. **Quản lý mạng và giao tiếp từ xa:** Java RMI cung cấp một cơ chế dễ dàng để quản lý giao tiếp từ xa và điều khiển các ứng dụng từ xa qua mạng. Điều này rất hữu ích khi bạn cần thao tác và quản lý các ứng dụng từ xa, chẳng hạn như quản lý máy chủ từ xa hoặc lấy dữ liệu từ xa.

**Ví dụ**

```java
import java.rmi.*;

public interface MyRemote extends Remote {
    public String sayHello() throws RemoteException;
}

public class MyRemoteImpl implements MyRemote {
    public String sayHello() throws RemoteException {
        return "Hello, from the server!";
    }
}

import java.rmi.*;

public class Server {
    public static void main(String[] args) {
        try {
            MyRemote service = new MyRemoteImpl();
            Naming.rebind("RemoteHello", service);
            System.out.println("Server is ready.");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

import java.rmi.*;

public class Client {
    public static void main(String[] args) {
        try {
            MyRemote service = (MyRemote) Naming.lookup("rmi://localhost/RemoteHello");
            String message = service.sayHello();
            System.out.println(message);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

```

Chạy Server trước sau đó chạy Client, kết quả sẽ là:

`Hello, from the server!`

>Tóm lại, Java RMI là một công nghệ mạnh mẽ trong Java cho phép bạn tạo ra các ứng dụng phân tán, tích hợp các ứng dụng từ xa và quản lý tác vụ phức tạp. Điều này giúp bạn tận dụng tối đa tài nguyên và cải thiện hiệu suất của ứng dụng của mình.

Tôi hi vọng rằng những giải thích chi tiết này sẽ giúp bạn hiểu rõ hơn về từng phần của mã lập trình Java cơ bản. Nếu có bất kỳ câu hỏi hoặc cần thêm thông tin, hãy liên hệ để được hỗ trợ. Chúc bạn may mắn trong việc học tập và phát triển kỹ năng lập trình của mình!

> Còn tiếp ...