Java Foundations/Tư duy lập trình — input, xử lý, output
6/39
Bài 6 / 39~13 phútNhập môn & Tư duy lập trìnhMiễn phí lượt xem

Tư duy lập trình — input, xử lý, output

Mô hình input-processing-output, cách phân rã bài toán thành bước nhỏ, và 3 construct cơ bản: sequence, selection, iteration.

TL;DR: Mọi chương trình — dù đơn giản hay phức tạp — đều theo đúng một khung: nhận input, biến đổi qua processing, rồi tạo ra output. Đặt 3 câu hỏi "Input là gì? Processing làm gì? Output đi đâu?" trước khi viết một dòng code sẽ cho bạn 80% bản thiết kế. Bài toán lớn cũng chỉ là nhiều bước I-P-O nhỏ xếp lại. Mọi thuật toán được xây từ đúng 3 construct: sequence (tuần tự), selection (rẽ nhánh), iteration (lặp) — đây là định lý Böhm-Jacopini (1966). Và thói quen quan trọng nhất: nghĩ edge case trước khi code.

Trước khi học thêm syntax, bạn cần một khung tư duy để tiếp cận mọi bài toán lập trình. Không có khung này, bạn sẽ biết nhiều lệnh Java nhưng không biết bắt đầu từ đâu khi đứng trước một bài toán mới.

Bài này trang bị cho bạn mô hình đơn giản nhất mà lập trình viên giàu kinh nghiệm vẫn dùng mỗi ngày: input → processing → output.

1. Analogy — "Máy xay sinh tố"

Hình dung chiếc máy xay sinh tố:

  1. Nhét trái cây vào (input) — chuối, dâu, sữa
  2. Máy xay (processing) — xay nhuyễn, trộn đều
  3. Đổ ra cốc (output) — cốc sinh tố hoàn chỉnh

Mọi chương trình máy tính đều theo đúng 3 bước này. Dù là app điện thoại, trang web, hay hệ thống ngân hàng — tất cả đều nhận input, xử lý, rồi tạo ra output.

Máy xay sinh tốChương trình (I-P-O)
Trái cây/sữa bỏ vàoInput (dữ liệu vào)
Lưỡi dao xayProcessing (xử lý/biến đổi)
Cốc sinh tố đổ raOutput (kết quả ra)
Cách nhớ

Máy xay sinh tố — input là trái cây, processing là lưỡi dao quay, output là cốc sinh tố. Mọi chương trình bạn viết là một dạng máy xay: nhận nguyên liệu, biến đổi, trả kết quả.

2. Mô hình I-P-O — ba câu hỏi đầu tiên là gì?

Khi đứng trước bất kỳ bài toán nào, hỏi ngay 3 câu:

flowchart LR
  inp["INPUT<br/>Du lieu dau vao"]
  proc["PROCESSING<br/>Bien doi du lieu"]
  out["OUTPUT<br/>Ket qua dau ra"]

  inp --> proc --> out

Câu 1 — Input là gì?

  • User nhập từ bàn phím?
  • Đọc từ file?
  • Nhận từ API?
  • Dữ liệu cứng trong code?

Câu 2 — Processing làm gì?

  • Lọc, tìm kiếm?
  • Tính toán, biến đổi?
  • Sắp xếp, nhóm?
  • Kiểm tra điều kiện?

Câu 3 — Output đi đâu?

  • In ra console?
  • Ghi vào file?
  • Trả về cho người gọi?
  • Hiển thị trên màn hình?

Trả lời 3 câu này trước khi viết một dòng code — bạn đã có 80% bản thiết kế.

3. Ví dụ 1 — Tính diện tích hình chữ nhật

Bài toán: Nhập chiều dài và chiều rộng, tính và in diện tích.

Phân tích I-P-O:

  • Input: 2 số thực (chiều dài, chiều rộng) — người dùng nhập
  • Processing: nhân 2 số với nhau
  • Output: in kết quả ra console
Thử đoán

Trước khi chạy đoạn code dưới, hãy đoán: nếu nhập chiều dài 5.0 và chiều rộng 3.5, output sẽ là gì?

import java.util.Scanner;

public class TinhDienTich {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in); // read input from keyboard

        // --- INPUT ---
        System.out.print("Nhap chieu dai: ");
        double dai = sc.nextDouble();         // read decimal from user

        System.out.print("Nhap chieu rong: ");
        double rong = sc.nextDouble();

        // --- PROCESSING ---
        double dienTich = dai * rong;         // calculate area

        // --- OUTPUT ---
        System.out.println("Dien tich: " + dienTich);

        sc.close(); // release resources
    }
}

Chạy thử:

Nhap chieu dai: 5.0
Nhap chieu rong: 3.5
Dien tich: 17.5

Scanner là class trong java.util — công cụ đọc input từ bàn phím (hoặc file). sc.nextDouble() đọc một số thập phân mà user nhập rồi nhấn Enter.

4. Ví dụ 2 — Chào user bằng tên

Bài toán: Hỏi tên người dùng, in lời chào cá nhân hoá.

Phân tích I-P-O:

  • Input: tên (chuỗi) — người dùng nhập
  • Processing: ghép chuỗi "Xin chao, " + ten + "!"
  • Output: in ra console
Thử đoán

Nếu người dùng nhập Nguyen Van An, output sẽ là gì?

import java.util.Scanner;

public class Chao {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        // --- INPUT ---
        System.out.print("Ban ten gi? ");
        String ten = sc.nextLine(); // read entire line -- including spaces

        // --- PROCESSING ---
        String loiChao = "Xin chao, " + ten + "! Chuc ban hoc tot.";

        // --- OUTPUT ---
        System.out.println(loiChao);

        sc.close();
    }
}

Chạy thử:

Ban ten gi? Nguyen Van An
Xin chao, Nguyen Van An! Chuc ban hoc tot.

sc.nextLine() vs sc.nextDouble():

  • nextDouble() đọc một số, dừng lại trước Enter
  • nextLine() đọc cả dòng kể cả khoảng trắng, tiêu thụ cả ký tự Enter

5. Phân rã bài toán lớn

Bài toán thực tế không đơn giản như 2 ví dụ trên. Nhưng dù phức tạp đến đâu, bạn vẫn phân rã thành nhiều I-P-O nhỏ hơn, xử lý từng bước.

Ví dụ: Tính trung bình của danh sách số nguyên.

BướcLoạiMô tả
Đọc N số từ userInputScanner.nextInt() mỗi lần
Cộng tất cả vào biến tongProcessingLặp và cộng dồn
Chia tong cho NProcessingMột phép tính
In kết quảOutputSystem.out.println
Thử đoán

Nếu người dùng nhập 3 số: 4, 7, 10, output Trung binh sẽ là bao nhiêu? Và nếu không ép kiểu (double), kết quả thay đổi không?

import java.util.Scanner;

public class TinhTrungBinh {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        // --- INPUT ---
        System.out.print("Nhap so luong phan tu: ");
        int n = sc.nextInt();

        // --- PROCESSING (part 1) -- read and accumulate ---
        int tong = 0;
        for (int i = 1; i <= n; i++) {
            System.out.print("Nhap phan tu " + i + ": ");
            int soNguyen = sc.nextInt();
            tong += soNguyen;  // tong = tong + soNguyen
        }

        // --- PROCESSING (part 2) -- compute average ---
        double trungBinh = (double) tong / n;

        // --- OUTPUT ---
        System.out.println("Trung binh: " + trungBinh);

        sc.close();
    }
}

Chú ý (double) tong / n: ép kiểu tong sang double trước khi chia để tránh integer division (chia lấy phần nguyên). 5 / 2 = 2 (int), nhưng (double)5 / 2 = 2.5.

6. Ba construct cơ bản của lập trình

Mọi thuật toán — dù đơn giản hay phức tạp — đều được xây từ 3 construct và chỉ 3:

Định lý Böhm-Jacopini

Năm 1966, hai nhà khoa học máy tính chứng minh rằng bất kỳ thuật toán nào cũng có thể biểu diễn bằng đúng 3 construct: sequence, selection, iteration. Đây là nền tảng của mọi ngôn ngữ lập trình mệnh lệnh (imperative).

ConstructTiếng ViệtTrong JavaDùng khi
SequenceTuần tựCác câu lệnh từ trên xuống dướiLuôn luôn — mặc định
SelectionRẽ nhánhif, else if, else, switchKhi cần chọn giữa các trường hợp
IterationLặpfor, while, do-whileKhi cần làm đi làm lại nhiều lần

6.1 Sequence — tuần tự

// Lines run top-to-bottom in order
int a = 5;          // step 1
int b = 10;         // step 2
int c = a + b;      // step 3 -- must come after steps 1 and 2
System.out.println(c); // step 4

Mặc định Java chạy tuần tự — dòng trước xong rồi mới đến dòng sau.

6.2 Selection — rẽ nhánh

Thử đoán

Với diem = 75, đoạn code dưới sẽ in ra gì?

int diem = 75;

if (diem >= 90) {
    System.out.println("Xuat sac");
} else if (diem >= 70) {
    System.out.println("Kha");     // runs because 75 >= 70
} else {
    System.out.println("Trung binh");
}

Selection cho phép chương trình đưa ra quyết định — chỉ chạy đoạn code phù hợp với điều kiện. Chi tiết sẽ có ở module câu lệnh điều kiện.

6.3 Iteration — lặp

Thử đoán

Vòng lặp dưới sẽ in ra bao nhiêu dòng, và dòng cuối là số mấy?

// Print numbers from 1 to 5
for (int i = 1; i <= 5; i++) {
    System.out.println(i);  // runs 5 times
}
// Output: 1 2 3 4 5 (each number on its own line)

Iteration cho phép lặp đi lặp lại mà không cần viết code nhiều lần. Chi tiết về for, while sẽ có ở module vòng lặp.

7. Tư duy test — nghĩ edge case trước khi code

Một thói quen quan trọng của lập trình viên giỏi: trước khi viết code, nghĩ trước 2-3 input đặc biệt:

Bài toánInput bình thườngEdge case cần nghĩ
Tính diện tíchchiều dài = 5, rộng = 3Chiều dài = 0? Số âm?
Tính trung bình5 số dươngN = 0 (chia cho 0!)? Số âm?
Tìm max trong mảng[3, 1, 4, 1, 5]Mảng rỗng? Mảng 1 phần tử?
Đếm ký tự trong chuỗi"hello"Chuỗi rỗng ""? Chuỗi null?

Vì sao quan trọng? Code chạy đúng với input bình thường là điều hiển nhiên. Code xử lý đúng edge case là thước đo của lập trình viên giỏi. Bug trong production thường đến từ những trường hợp "không ai nghĩ tới".

// Example: compute average with edge case guard
if (n == 0) {
    System.out.println("Khong co phan tu nao de tinh trung binh.");
} else {
    double trungBinh = (double) tong / n;
    System.out.println("Trung binh: " + trungBinh);
}

8. Bài toán đầy đủ — kết hợp I-P-O

Dưới đây là ví dụ kết hợp tất cả: input từ user, xử lý có điều kiện, output rõ ràng.

Thử đoán

Với input 85, chương trình in gì? Với input -5, kết quả thay đổi thế nào?

import java.util.Scanner;

public class PhanLoaiDiem {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        // --- INPUT ---
        System.out.print("Nhap diem (0-100): ");
        int diem = sc.nextInt();

        // --- PROCESSING + SELECTION ---
        String xepLoai;
        if (diem < 0 || diem > 100) {
            xepLoai = "Diem khong hop le";    // edge case
        } else if (diem >= 90) {
            xepLoai = "Xuat sac";
        } else if (diem >= 70) {
            xepLoai = "Kha";
        } else if (diem >= 50) {
            xepLoai = "Trung binh";
        } else {
            xepLoai = "Yeu";
        }

        // --- OUTPUT ---
        System.out.println("Xep loai: " + xepLoai);

        sc.close();
    }
}

Chạy thử:

Nhap diem (0-100): 85
Xep loai: Kha

Nhap diem (0-100): -5
Xep loai: Diem khong hop le

9. Áp dụng tư duy I-P-O

Nên làm:

  • Vẽ I-P-O ra giấy trước khi gõ code — 1 phút vẽ tiết kiệm 30 phút debug
  • Nghĩ edge case ngay từ đầu — số 0, số âm, chuỗi rỗng, mảng rỗng
  • Đặt comment // INPUT, // PROCESSING, // OUTPUT khi code phức tạp
  • Kiểm tra từng phần — test input đúng chưa trước khi lo processing

Tránh:

  • Nhảy thẳng vào code khi chưa hiểu output cần là gì
  • Viết code dài rồi mới test — bug sẽ khó tìm hơn
  • Bỏ qua edge case vì "chắc không ai nhập số âm đâu"

10. Luyện phân tích I-P-O

Hãy tự phân tích I-P-O cho 3 bài toán sau trước khi đọc gợi ý:

Bài 1: Sắp xếp danh sách tên theo thứ tự alphabet.

  • Input: ?
  • Processing: ?
  • Output: ?

Bài 2: Tìm giá trị lớn nhất trong mảng số nguyên.

  • Input: ?
  • Processing: ?
  • Output: ?

Bài 3: Đếm số lần ký tự 'a' xuất hiện trong một chuỗi.

  • Input: ?
  • Processing: ?
  • Output: ?
Gợi ý

Bài 1: Input = danh sách N tên (String), Processing = thuật toán sắp xếp (so sánh, đổi chỗ), Output = danh sách đã sắp xếp.

Bài 2: Input = mảng số nguyên, Processing = duyệt từng phần tử, giữ lại số lớn nhất tìm được, Output = 1 số nguyên.

Bài 3: Input = 1 chuỗi String, Processing = duyệt từng ký tự, đếm số ký tự bằng 'a', Output = 1 số nguyên (bộ đếm).

11. Deep Dive Oracle

Deep Dive Oracle (optional)

Tài liệu tham khảo:

Ghi chú thực tế: Scanner là class hay gặp khi mới học nhưng không dùng nhiều trong production (thường đọc input qua HTTP request, file, database thay vì bàn phím). Tuy nhiên, hiểu Scanner giúp bạn nắm khái niệm stream input/output — nền tảng để sau này làm việc với InputStream, BufferedReader, và Files.readAllLines.

12. Liên hệ các bài khác

  • Hello World — bài trước viết chương trình in ra màn hình lần đầu; bài này mở rộng sang đọc input từ user với Scanner và xây I-P-O đầy đủ.
  • Compile & Run — hiểu javac biên dịch code thành bytecode và JVM thực thi; giúp bạn hiểu vì sao Scanner.nextLine() chờ input từ stdin và khi nào chương trình kết thúc.
  • Mini-challenge: in lịch tháng — bài tập áp dụng I-P-O tự lực: phân tích bài toán lịch tháng thành các bước sequence + iteration trước khi code.

13. Tóm tắt

  • Mọi chương trình = Input → Processing → Output. Hỏi 3 câu này trước khi code.
  • Scanner đọc input từ bàn phím: nextInt(), nextDouble(), nextLine().
  • Bài toán lớn = nhiều bước I-P-O nhỏ xếp lại — phân rã từng phần.
  • 3 construct cơ bản: sequence (tuần tự), selection (rẽ nhánh), iteration (lặp) — đủ để xây mọi thuật toán.
  • Tư duy test: nghĩ edge case (số 0, số âm, chuỗi rỗng) trước khi viết code — thói quen của dev giỏi.

14. Tự kiểm tra

Tự kiểm tra
Q1
Phân tích I-P-O cho bài toán: "Kiểm tra một số có phải số chẵn không". Input là gì, processing làm gì, output là gì?
Input: 1 số nguyên n (từ bàn phím / tham số). Processing: tính n % 2; so sánh với 0. Output: true/false hoặc chuỗi "chẵn"/"lẻ". Lưu ý edge case: số âm vẫn hoạt động (-4 % 2 == 0), số 0 cũng được coi là chẵn theo toán học.
Q2
Vì sao cần (double) tong / n thay vì chỉ tong / n? Điều gì xảy ra nếu không ép kiểu?

Nếu cả tongn đều int, /integer division: phần thập phân bị cắt. 7 / 2 == 3, không phải 3.5.

Ép kiểu (double) tong chuyển tong thành double trước, rồi JLS §5.6 tự promote n lên double → phép chia là double division → giữ phần thập phân.

Q3
sc.nextLine() khác sc.nextInt() ở điểm nào? Khi nào dùng cái nào?
nextInt() đọc 1 token số nguyên, dừng ở khoảng trắng và để lại ký tự trong buffer. nextLine() đọc toàn bộ đến hết dòng (bao gồm ). Bẫy kinh điển: gọi nextInt() rồi nextLine()nextLine() chỉ đọc được còn sót, trả chuỗi rỗng. Fix: thêm sc.nextLine() "ăn" bỏ giữa hai lần đọc, hoặc dùng nextLine() rồi Integer.parseInt.
Q4
3 construct sequence/selection/iteration — cái nào mặc định luôn có? Cái nào chỉ xuất hiện khi cần?
Sequence luôn có mặc định — mọi chương trình là chuỗi statement thực thi tuần tự từ trên xuống. Selection (if/switch) và iteration (for/while) chỉ xuất hiện khi bài toán yêu cầu rẽ nhánh hoặc lặp. Định lý Böhm-Jacopini (1966) chứng minh 3 construct này đủ để biểu diễn mọi thuật toán.
Q5
Bạn viết chương trình tính căn bậc hai. Edge case nào cần xử lý trước khi tính?
  • Số âm → không có căn bậc hai thực; Math.sqrt(-4) trả NaN. Cần validate hoặc dùng Complex nếu cho phép số phức.
  • Số 0 → kết quả 0, nhưng nhớ rằng 0 là biên.
  • Input là NaN / Infinity nếu đọc từ user (qua Double.parseDouble).
  • Số rất lớnMath.sqrt vẫn ổn với double, nhưng nếu input là chuỗi quá lớn có thể overflow khi parse.
  • Input là ký tự / chuỗi rỗng → NumberFormatException.

Bài tiếp theo: Compile & Run — javac, bytecode, vòng đời chương trình

Bài này có giúp bạn hiểu bản chất không?

Hỏi đáp về bài này

Chưa có câu hỏi

Đặt câu hỏi

Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).

Đặt câu hỏi đầu tiên