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
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:
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
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
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ểu | Kích thước | Khoảng giá trị | Dùng cho |
|---|---|---|---|
byte | 8 bit | −128 … 127 | Dữ liệu nhị phân, file |
short | 16 bit | −32.768 … 32.767 | Hiếm dùng |
int | 32 bit | khoảng ±2,1 tỉ | Số nguyên mặc định |
long | 64 bit | khoảng ±9,2 × 10¹⁸ | Số rất lớn, thời gian (ms), ID |
float | 32 bit | ~7 chữ số có nghĩa | Đồ hoạ, ít dùng |
double | 64 bit | ~15–16 chữ số có nghĩa | Số thực mặc định |
boolean | — | true / false | Điều kiện |
char | 16 bit | Mộ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.
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ĩa | Ví dụ | Kết quả |
|---|---|---|---|
+ - * | Cộng, trừ, nhân | 7 * 3 | 21 |
/ | Chia | 7 / 2 | 3 (chia nguyên!) |
% | Phần dư | 7 % 2 | 1 |
++ -- | Tăng/giảm 1 | i++ | |
+= -= *= /= | Phép toán rồi gán | x += 5 | x = x + 5 |
Bẫy 1: chia hai số nguyên
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ố
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
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
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.
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
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:
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ố
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àStringthì nối chuỗi."Tổng: " + 1 + 2cho"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
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
- Nhập số giây (ví dụ 3725), in ra dạng
1 giờ 2 phút 5 giây(gợi ý:/và%). - 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). - Nhập một số nguyên có 3 chữ số, in tổng các chữ số (
123→6). - Giải thích vì sao
System.out.println(1 + 2 + "3" + 4 + 5);in ra3345.
Tổng kết
- Java kiểu tĩnh: mỗi biến có kiểu cố định;
varchỉ là suy kiểu, không phải kiểu động. - Dùng
int,long,double,boolean,char,Stringcho 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).
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.