Bỏ qua điều hướng
EN
Tất cả bài viết

Java cơ bản #2: biến, kiểu dữ liệu, toán tử và ép kiểu

Tám kiểu nguyên thuỷ của Java và khi nào dùng kiểu nào, biến và hằng, var, phép chia nguyên và phần dư, tràn số, so sánh số thực, ép kiểu rộng–hẹp, toán tử logic, độ ưu tiên — kèm các bẫy kinh điển người mới hay mắc.

12 phút đọc

Ôn tập bài nàyThẻ ghi nhớ & câu hỏi ôn tập7 thẻ ghi nhớ · 7 câu trắc nghiệm
Mục lục

Chương trình nào cũng làm việc với dữ liệu: tuổi, giá tiền, tên, trạng thái đúng/sai. Java là ngôn ngữ kiểu tĩnh (statically typed): mỗi biến có một kiểu cố định, trình biên dịch kiểm tra trước khi chạy. Ban đầu có vẻ rườm rà, nhưng nhờ vậy rất nhiều lỗi bị bắt ngay lúc biên dịch thay vì lúc khách hàng đang dùng.

Bài này đi qua biến, các kiểu dữ liệu, toán tử, ép kiểu — và mấy cái bẫy mà gần như ai học Java cũng từng rơi vào.

Biến

Biến là một vùng nhớ có tên và kiểu:

Bien.java
void main() {
    int tuoi = 20;              // khai báo + gán giá trị ban đầu
    double diemTb = 8.25;
    String ten = "Lan";
    boolean daTotNghiep = false;
 
    tuoi = tuoi + 1;            // gán lại giá trị mới
    IO.println(ten + " năm nay " + tuoi + " tuổi, điểm " + diemTb + ", tốt nghiệp: " + daTotNghiep);
}

Quy tắc:

  • Phải khai báo kiểu trước khi dùng; không đổi được kiểu sau đó (tuoi = "hai mươi" là lỗi biên dịch).
  • Biến cục bộ phải được gán giá trị trước khi đọc, nếu không trình biên dịch báo variable might not have been initialized.
  • Tên bắt đầu bằng chữ cái, _ hoặc $; không trùng từ khoá (int, class, new…).

Hằng số với final

HangSo.java
void main() {
    final double VAT = 0.1;          // không gán lại được
    final int SO_NGAY_TRONG_TUAN = 7;
    double gia = 250_000;            // dấu _ giúp đọc số lớn dễ hơn
    IO.println("Giá sau thuế: " + gia * (1 + VAT));
    // VAT = 0.08;  → lỗi biên dịch: cannot assign a value to final variable VAT
}

var: để trình biên dịch tự suy kiểu

DungVar.java
void main() {
    var soLuong = 3;            // int
    var donGia = 45_000.0;      // double
    var tenSp = "Bút bi";       // String
    IO.println(tenSp + ": " + soLuong * donGia);
}

var không biến Java thành ngôn ngữ kiểu động — kiểu vẫn cố định, chỉ là không phải gõ ra. Dùng khi kiểu đã hiển nhiên từ vế phải; khi không rõ, viết kiểu đầy đủ cho người đọc.

Tám kiểu nguyên thuỷ

KiểuKích thướcKhoảng giá trịDùng cho
byte8 bit−128 … 127Dữ liệu nhị phân, file
short16 bit−32.768 … 32.767Hiếm dùng
int32 bitkhoảng ±2,1 tỉSố nguyên mặc định
long64 bitkhoảng ±9,2 × 10¹⁸Số rất lớn, thời gian (ms), ID
float32 bit~7 chữ số có nghĩaĐồ hoạ, ít dùng
double64 bit~15–16 chữ số có nghĩaSố thực mặc định
boolean—true / falseĐiều kiện
char16 bitMột ký tự Unicode'A', 'đ'

Ngoài ra là các kiểu tham chiếu: String, mảng, và mọi lớp (bài 6). Khác biệt quan trọng: biến kiểu nguyên thuỷ chứa giá trị, biến kiểu tham chiếu chứa địa chỉ trỏ tới đối tượng.

Literal.java
void main() {
    long danSo = 8_100_000_000L;       // số vượt int phải có hậu tố L
    float pi = 3.14f;                  // float cần hậu tố f
    char kyTu = 'J';                   // nháy đơn cho char, nháy kép cho String
    int nhiPhan = 0b1010;              // 10
    int hex = 0xFF;                    // 255
    IO.println(danSo + " " + pi + " " + kyTu + " " + nhiPhan + " " + hex);
    IO.println("int lớn nhất: " + Integer.MAX_VALUE);
}

Toán tử số học

Toán tửÝ nghĩaVí dụKết quả
+ - *Cộng, trừ, nhân7 * 321
/Chia7 / 23 (chia nguyên!)
%Phần dư7 % 21
++ --Tăng/giảm 1i++
+= -= *= /=Phép toán rồi gánx += 5x = x + 5

Bẫy 1: chia hai số nguyên

ChiaNguyen.java
void main() {
    int tong = 17, soMon = 2;
    IO.println(tong / soMon);              // 8 — phần thập phân bị CẮT BỎ
    IO.println((double) tong / soMon);     // 8.5 — ép một vế sang double trước khi chia
    IO.println(tong / 2.0);                // 8.5
    IO.println(17 % 5);                    // 2  — phần dư
    IO.println(-17 % 5);                   // -2 — dấu theo số bị chia
}

Khi cả hai vế là số nguyên, Java chia nguyên. Muốn kết quả thập phân, ít nhất một vế phải là số thực. Phép % rất hay dùng: kiểm tra chẵn lẻ (n % 2 == 0), lấy chữ số cuối (n % 10), quay vòng ((i + 1) % n).

Bẫy 2: tràn số

TranSo.java
void main() {
    int max = Integer.MAX_VALUE;
    IO.println(max + 1);                   // -2147483648: tràn, quay về số âm, KHÔNG báo lỗi
    long dung = (long) max + 1;
    IO.println(dung);                      // 2147483648
 
    int giayMotNam = 365 * 24 * 60 * 60;
    IO.println(giayMotNam * 1000);         // sai: tràn int
    IO.println(giayMotNam * 1000L);        // đúng: 31536000000
    IO.println(Math.addExact(1, 2));       // các hàm *Exact ném lỗi khi tràn thay vì im lặng
}

Bẫy 3: số thực không chính xác tuyệt đối

SoThuc.java
import java.math.BigDecimal;
 
void main() {
    IO.println(0.1 + 0.2);                          // 0.30000000000000004
    IO.println(0.1 + 0.2 == 0.3);                   // false!
    IO.println(Math.abs((0.1 + 0.2) - 0.3) < 1e-9); // so sánh với sai số cho phép
 
    // Tiền tệ: dùng BigDecimal (tạo từ chuỗi) hoặc lưu số nguyên theo đơn vị nhỏ nhất (đồng, xu)
    BigDecimal a = new BigDecimal("0.1");
    BigDecimal b = new BigDecimal("0.2");
    IO.println(a.add(b));                           // 0.3
}

double lưu số ở hệ nhị phân, mà 0,1 không biểu diễn chính xác được ở hệ 2 (giống 1/3 ở hệ 10). Quy tắc: không dùng == với số thực; không dùng double cho tiền.

i++ và ++i

TangGiam.java
void main() {
    int i = 5;
    int a = i++;   // a = 5, sau đó i = 6 (dùng rồi mới tăng)
    int b = ++i;   // i = 7, sau đó b = 7 (tăng rồi mới dùng)
    IO.println(a + " " + b + " " + i);   // 5 7 7
}

Đứng một mình (i++;) thì hai cách như nhau. Tránh lồng ++ vào biểu thức phức tạp — khó đọc và dễ sai.

Toán tử so sánh và logic

Toán tửÝ nghĩa
== !=Bằng, khác
< <= > >=So sánh
&&VÀ — cả hai đúng
||HOẶC — ít nhất một đúng
!PHỦ ĐỊNH

&& và || đoản mạch: nếu vế trái đã đủ quyết định kết quả, vế phải không được tính. Nhờ vậy viết được n != 0 && 10 / n > 1 mà không sợ chia cho 0.

Logic.java
void main() {
    int tuoi = 19;
    boolean coBangLai = true;
    boolean duocLaiXe = tuoi >= 18 && coBangLai;
    boolean ngayNghi = false, troiMua = true;
    IO.println("Được lái xe: " + duocLaiXe);
    IO.println("Ở nhà: " + (ngayNghi || troiMua));
 
    int n = 0;
    IO.println(n != 0 && 10 / n > 1);   // false, không lỗi chia 0 nhờ đoản mạch
}

Toán tử ba ngôi

BaNgoi.java
void main() {
    int diem = 7;
    String ketQua = diem >= 5 ? "Đậu" : "Rớt";
    IO.println(ketQua);
}

Ép kiểu

Ép kiểu rộng (từ kiểu nhỏ sang kiểu lớn) diễn ra tự động, an toàn: byte → short → int → long → float → double.

Ép kiểu hẹp phải viết rõ và có thể mất dữ liệu:

EpKieu.java
void main() {
    int i = 100;
    long l = i;            // tự động
    double d = l;          // tự động
 
    double gia = 9.99;
    int phanNguyen = (int) gia;         // 9 — cắt bỏ, KHÔNG làm tròn
    long lamTron = Math.round(gia);     // 10
    IO.println(phanNguyen + " " + lamTron);
 
    int lon = 300;
    byte b = (byte) lon;                // 44 — tràn: 300 - 256
    IO.println(b);
 
    char c = 'A';
    IO.println(c + 1);                  // 66 — char được nâng lên int khi tính
    IO.println((char) (c + 1));         // B
}

Chuyển đổi giữa chuỗi và số

ChuoiSo.java
void main() {
    String nhap = "42";
    int so = Integer.parseInt(nhap);              // chuỗi → int
    double thuc = Double.parseDouble("3.5");      // chuỗi → double
    String chuoi = String.valueOf(so * 2);        // số → chuỗi (hoặc "" + so)
    IO.println(so + 1);                           // 43
    IO.println(chuoi + 1);                        // 841 — nối chuỗi, không phải cộng!
    IO.println(thuc);
    // Integer.parseInt("4a2") → NumberFormatException (bài 10 sẽ học cách xử lý)
}

Lưu ý: + với một vế là String thì nối chuỗi. "Tổng: " + 1 + 2 cho "Tổng: 12", còn "Tổng: " + (1 + 2) cho "Tổng: 3". Biểu thức được tính từ trái sang phải.

Độ ưu tiên toán tử

Từ cao xuống thấp (những thứ hay gặp): ++ -- → ! → * / % → + - → < > <= >= → == != → && → || → ?: → = += -=.

Không cần thuộc lòng: khi phân vân, thêm ngoặc. (a + b) * c rõ ràng hơn và không tốn gì.

Lớp Math

ToanHoc.java
void main() {
    IO.println(Math.sqrt(16));          // 4.0
    IO.println(Math.pow(2, 10));        // 1024.0
    IO.println(Math.abs(-7));           // 7
    IO.println(Math.max(3, 9));         // 9
    IO.println(Math.floor(2.7) + " " + Math.ceil(2.1));   // 2.0 3.0
    int xucXac = (int) (Math.random() * 6) + 1;            // số ngẫu nhiên 1–6
    IO.println("Xúc xắc: " + xucXac);
}

Bài tập

  1. Nhập số giây (ví dụ 3725), in ra dạng 1 giờ 2 phút 5 giây (gợi ý: / và %).
  2. Nhập chiều cao (m) và cân nặng (kg), tính BMI = cân nặng / chiều cao², in với 2 chữ số thập phân: String.format("%.2f", bmi).
  3. Nhập một số nguyên có 3 chữ số, in tổng các chữ số (123 → 6).
  4. Giải thích vì sao System.out.println(1 + 2 + "3" + 4 + 5); in ra 3345.

Tổng kết

  • Java kiểu tĩnh: mỗi biến có kiểu cố định; var chỉ là suy kiểu, không phải kiểu động.
  • Dùng int, long, double, boolean, char, String cho hầu hết trường hợp.
  • Ba bẫy: chia nguyên (7/2 = 3), tràn số im lặng, số thực không chính xác (không ==, không dùng cho tiền).
  • Ép kiểu rộng tự động; ép kiểu hẹp phải viết (kiểu) và có thể mất dữ liệu; (int) cắt bỏ chứ không làm tròn.
  • + với chuỗi là nối chuỗi; khi phân vân độ ưu tiên, thêm ngoặc.
  • Bài tiếp theo: rẽ nhánh và vòng lặp để chương trình biết "ra quyết định" và "lặp lại".

Nguồn tham khảo: dev.java — "Language Basics"; The Java Language Specification (Java SE 25), chương 4 và 15; Java Programming MOOC — University of Helsinki; Bloch — Effective Java, mục 60 (tránh float/double khi cần kết quả chính xác).

Đang tải câu hỏi luyện tập…

Bài này thế nào?

Bài viết có hữu ích với bạn?

Đánh giá giúp mình biết nên viết thêm về chủ đề nào.

Đang tải đánh giá…
Chọn số sao từ 1 đến 5

Bình luận

Đăng nhập để bình luận không cần nhập tên và được duyệt nhanh hơn.

0/2000
Đang tải bình luận…

Đọc tiếp

Bài số № 55

Java cơ bản #12: dự án cuối — ứng dụng quản lý chi tiêu với Maven và JUnit

Gom toàn bộ kiến thức vào một dự án thật: cấu trúc Maven và pom.xml, chia lớp model–kho dữ liệu–service–giao diện, lưu file CSV, thống kê bằng Stream, cảnh báo ngân sách, viết kiểm thử JUnit 5, đóng gói file jar chạy được — và lộ trình học tiếp sau Java cơ bản.

19 phút đọc

Bài số № 54

Java cơ bản #11: lambda, tham chiếu phương thức, Stream API và Optional

Lập trình kiểu hàm trong Java: lambda và functional interface (Predicate, Function, Consumer, Supplier), tham chiếu phương thức ::, Stream với filter–map–reduce, sorted, collect, groupingBy, Optional thay cho null — cùng lời khuyên khi nào vòng lặp vẫn tốt hơn stream.

14 phút đọc

Bài số № 53

Java cơ bản #10: xử lý ngoại lệ và đọc ghi file với java.nio.file

Khi chương trình gặp lỗi: đọc stack trace, try/catch/finally, checked và unchecked exception, throw và throws, tự định nghĩa ngoại lệ nghiệp vụ, try-with-resources tự đóng tài nguyên; đọc ghi file văn bản và CSV bằng Files, Path, UTF-8 — kèm các thói quen xử lý lỗi nên tránh.

15 phút đọc