99 Ngày Java — Ngày 05: Ép kiểu (casting)
Hôm qua ta để lại một câu hỏi treo: chuyện gì xảy ra khi các kiểu khác nhau gặp nhau trong một biểu thức? Hôm nay trả lời trọn vẹn bằng ép kiểu (casting): khi nào Java tự đổi kiểu giúp bạn (widening), khi nào bạn phải tự tay ép và tự chịu rủi ro mất dữ liệu (narrowing), và mối quan hệ đặc biệt giữa char với int.

Widening — nước chảy chỗ trũng, tự động và an toàn
Đổ nước từ ly nhỏ sang ly to thì không bao giờ tràn — đưa giá trị từ kiểu hẹp sang kiểu rộng hơn cũng vậy, nên Java làm tự động, không cần hỏi:
int i = 100;
long l = i; // int → long: tự động, luôn an toàn
double d = l; // long → double: tự động
// Chuỗi mở rộng đầy đủ:
// byte → short → int → long → float → double
// char → intĐây chính là lý do 7.0 / 2 hôm qua ra 3.5: thấy một vế là double, Java lặng lẽ thăng cấp (promote) vế còn lại lên double trước khi tính. Mọi phép toán số học đều thăng cấp toán hạng lên ít nhất int — chi tiết này sẽ quay lại ở phần bẫy bên dưới.
Narrowing — phải cast tường minh, và có giá
Chiều ngược lại — ly to đổ vào ly nhỏ — có thể tràn, nên Java bắt bạn ký giấy cam kết: cú pháp (kiểu_đích) giá_trị. Chữ ký đó nghĩa là: "tôi hiểu dữ liệu có thể mất, cứ làm đi":
double price = 9.99;
int rounded = (int) price; // 9 — phần thập phân bị CẮT, không làm tròn!
long big = 3_000_000_000L;
int oops = (int) big; // -1294967296 — tràn số lặng lẽ!
byte tiny = (byte) 130; // -126 — vượt phạm vi byte là quay vòngdouble→int: phần thập phân bị cắt bỏ (truncate), không phải làm tròn — muốn làm tròn dùngMath.round().long→int,int→byte: giá trị vượt phạm vi thì quay vòng như overflow Ngày 03 — không hề có cảnh báo runtime.Cast chỉ đổi cách đọc bit, không kiểm tra tính hợp lệ — trách nhiệm nằm ở người viết.
char ↔ int — ký tự cũng chỉ là con số
char letter = 'A';
int code = letter; // 65 — char lên int: tự động
System.out.println('A' + 1); // 66 — phép cộng đẩy char thành int!
char next = (char) ('A' + 1); // 'B' — muốn quay về char phải cast
char viet = 'ế';
System.out.println((int) viet); // 7871 — Unicode tiếng Việt vẫn là sốMỗi char là một mã Unicode 16-bit, nên char lên int là widening tự nhiên. Thủ thuật đáng nhớ: 'z' - 'a' ra khoảng cách 25 giữa hai chữ cái, (char) (c + 1) ra ký tự kế tiếp — nền tảng của các bài toán xử lý chuỗi mà ta sẽ gặp từ Ngày 09.
Ba cái bẫy gặp hoài trong code thật
// Bẫy 1: chia nguyên rồi mới ép — quá muộn!
int total = 7, count = 2;
double avg1 = total / count; // 3.0 — 7/2 đã ra 3 trước khi lên double
double avg2 = (double) total / count; // 3.5 — ép 1 vế TRƯỚC khi chia ✓
// Bẫy 2: byte + byte là... int
byte a = 10, b = 20;
// byte sum = a + b; // ❌ lỗi compile: a + b là int
byte sum = (byte) (a + b); // phải tự cast — và tự chịu overflow
// Bẫy 3: float nhận long ngon lành (widening!) nhưng mất độ chính xác
long exact = 123_456_789_012_345_678L;
float f = exact; // 1.23456788E17 — trôi mất đuôi!Bẫy 1 là vua của mọi bug tính trung bình: cast phải xảy ra trước phép chia, không phải sau. Bẫy 2 đến từ quy tắc thăng cấp lên int — và cũng giải thích chuyện hôm qua: b += 1 chạy được vì toán tử gán kết hợp tự cast ngầm hộ bạn. Bẫy 3 tinh vi nhất: long → float là widening hợp lệ nhưng float chỉ có ~7 chữ số chính xác — số lớn sẽ trôi mất đuôi mà compiler không nói một lời.
Bài tập nhỏ
Đoán rồi chạy:
(int) 9.99,(byte) 200,(char) 66,'A' + 2.Viết công thức tính điểm trung bình của
int soLanThi = 3vàint tongDiem = 25sao cho ra8.33chứ không phải8.0.In bảng chữ cái A–Z bằng một vòng lặp duy nhất dùng
char(gợi ý: Ngày 07 học vòng lặp, hôm nay cứ thửwhiletheo trực giác).Giải thích vì sao
float f = 3_000_000_000L;compile được nhưngint i = 3_000_000_000L;thì không.
Kết luận
Bạn đã phân biệt widening tự động với narrowing phải ký cam kết, biết cast cắt chứ không làm tròn, hiểu char chỉ là mã Unicode và thuộc lòng 3 cái bẫy kinh điển. Ngày 06, code bắt đầu biết rẽ nhánh: if–else và switch — gồm cả switch expression đời mới của Java. Hẹn gặp lại!
Site Admin
Engineer and writer. Building things with TypeScript and distributed systems.
Bình luận (0)
Bạn cần đăng nhập bằng Google để bình luận.
Hãy là người bình luận đầu tiên.


