Ép kiểu trong Java: Widening, Narrowing và Safe Cast
Nắm vững cơ chế ép kiểu trong Java: widening tự động, narrowing tường minh có nguy cơ tràn số, autoboxing và pattern matching instanceof an toàn.
TL;DR: Java chia ép kiểu thành hai cơ chế chính: widening (mở rộng kiểu tự động, an toàn về phạm vi) và narrowing (thu hẹp kiểu, yêu cầu cú pháp cast tường minh và tiềm ẩn nguy cơ tràn số hoặc mất phần thập phân). Với kiểu tham chiếu, upcasting luôn tự động và an toàn, trong khi downcasting cần cú pháp (ChildType) và có thể ném ClassCastException nếu kiểu thực tế không tương thích. Hãy dùng pattern matching instanceof (Java 16+) để downcast an toàn và Math.toIntExact() để bắt lỗi tràn số khi thu hẹp kiểu số nguyên.
Bài 3 đã giới thiệu wrapper class và autoboxing, còn Bài 4 đề cập đến phép chia số nguyên cần ép kiểu. Bài này đi sâu vào toàn bộ cơ chế chuyển đổi kiểu dữ liệu trong Java: khi nào trình biên dịch tự động nâng kiểu, khi nào bạn phải ép kiểu tường minh, và cách phòng ngừa mất dữ liệu hoặc sập chương trình tại runtime.
1. Analogy đổ nước giữa các chai mô tả cơ chế ép kiểu như thế nào?
Hãy hình dung việc ép kiểu tương tự như đổ nước giữa các chai có thể tích khác nhau:
- Đổ chai 100 ml vào chai 500 ml: Quá trình diễn ra tự nhiên, không mất một giọt nước nào và không cần kiểm tra. Đây là cơ chế widening (mở rộng kiểu tự động).
- Đổ chai 500 ml vào chai 100 ml: Nước có nguy cơ tràn ra ngoài nếu lượng nước ban đầu vượt quá 100 ml. Java bắt buộc bạn phải viết cú pháp ép kiểu tường minh như lời cam kết chấp nhận rủi ro này. Đây là cơ chế narrowing (thu hẹp kiểu).
2. Cơ chế widening mở rộng kiểu tự động hoạt động ra sao?
Widening primitive conversion xảy ra hoàn toàn tự động khi bạn gán giá trị sang một kiểu dữ liệu có kích thước bit hoặc miền giá trị rộng hơn:
byte b = 42;
short s = b; // byte -> short: tu dong
int i = s; // short -> int: tu dong
long l = i; // int -> long: tu dong
float f = l; // long -> float: tu dong (co the mat precision)
double d = f; // float -> double: tu dong
Thứ tự mở rộng kiểu chuẩn giữa các kiểu nguyên thủy:

Kiểu char là số nguyên không dấu 16-bit (0–65535) nên có thể tự động widening sang int thành mã code point Unicode:
char c = 'A';
int code = c; // 65 -- Unicode code point cua 'A'
System.out.println(code); // 65
Chuyển đổi int -> float hoặc long -> double là widening hợp lệ về mặt phạm vi nhưng có thể làm rụng các chữ số có nghĩa do float chỉ có 23 bit mantissa (~7 chữ số thập phân) và double có 52 bit mantissa (~15 chữ số).
3. Cơ chế narrowing thu hẹp kiểu tiềm ẩn rủi ro mất dữ liệu gì?
Narrowing primitive conversion xảy ra khi chuyển từ kiểu lớn hơn về kiểu nhỏ hơn. Bạn bắt buộc phải chỉ định kiểu đích trong dấu ngoặc đơn:
double d = 3.99;
int i = (d); // COMPILE ERROR neu khong viet (int)
int i = (int) d; // 3 -- cat bo phan thap phan ve phia 0
long l = 100L;
int x = (int) l; // OK neu l nam trong khoang int
Khi giá trị vượt quá sức chứa của kiểu đích, Java áp dụng cơ chế cắt bit bù 2 (two's complement wrap-around) một cách im lặng:
int big = 130;
byte b = (byte) big; // byte chua tu -128 den 127
System.out.println(b); // -126 (130 - 256 = -126)
| Biểu thức | Kết quả | Bản chất cơ chế |
|---|---|---|
(int) 3.9 | 3 | Cắt bỏ phần thập phân về 0 (truncate toward zero) |
(int) -3.9 | -3 | Cắt bỏ phần thập phân về 0 |
(byte) 127 | 127 | Đúng giá trị cực đại của byte |
(byte) 128 | -128 | Tràn số bit bù 2 (wrap-around) |
(byte) 130 | -126 | 130 - 256 = -126 |
(short) 40_000 | -25536 | 40000 - 65536 = -25536 |
Thử ngẫmmột biến đếm số lượng giao dịch từ database trả về long được ép kiểu thô (int) total — khi dữ liệu vượt 2,1 tỷ bản ghi, hệ thống sẽ báo lỗi hay âm thầm ghi nhận số âm?
4. Ép kiểu kiểu tham chiếu upcasting và downcasting khác nhau thế nào?
Với kiểu tham chiếu (reference types), ép kiểu tuân thủ mối quan hệ kế thừa hướng đối tượng:
4.1 Upcasting: An toàn và tự động
Upcasting chuyển tham chiếu từ lớp con lên lớp cha. Quá trình này luôn an toàn vì lớp con luôn kế thừa đầy đủ hành vi của lớp cha:
String text = "OLHub Platform";
Object obj = text; // Upcasting tu dong -- String IS-A Object
4.2 Downcasting: Tiềm ẩn ngoại lệ ClassCastException
Downcasting chuyển từ lớp cha về lớp con. Do một biến kiểu cha có thể đang nắm giữ bất kỳ đối tượng con nào, JVM yêu cầu bạn viết ép kiểu tường minh và sẽ kiểm tra kiểu thực tế tại runtime:
Object obj = "Hello";
String s = (String) obj; // Hop le tai runtime vi obj thuc su la String
Object numberObj = Integer.valueOf(42);
String bad = (String) numberObj; // ClassCastException tai runtime!

5. Mối quan hệ giữa autoboxing, unboxing và type casting là gì?
Autoboxing và unboxing là cơ chế chuyển đổi giữa kiểu nguyên thủy và wrapper class tương ứng:
int primitive = 100;
Integer boxed = primitive; // Autoboxing: Integer.valueOf(100)
int unboxed = boxed; // Unboxing: boxed.intValue()
Nếu wrapper object mang giá trị null, quá trình unboxing sẽ kích hoạt boxed.intValue() và ném ngay NullPointerException. Luôn kiểm tra khác null trước khi unbox.
Lưu ý rằng hai wrapper class khác nhau không có quan hệ kế thừa trực tiếp, do đó không thể ép kiểu ngang hàng:
Integer i = 10;
// Long l = (Long) i; // COMPILE ERROR -- Integer khong the cast sang Long
long safeLong = i.longValue(); // Chuyen doi qua method hop le
6. Các mẫu hình safe cast và pattern matching instanceof hoạt động ra sao?
6.1 Thu hẹp an toàn với Math.toIntExact
Thay vì ép kiểu thô (int) bigLong khiến số bị tràn im lặng, hãy dùng Math.toIntExact() để kích hoạt cơ chế fail-fast:
long value = 3_000_000_000L;
try {
int safeInt = Math.toIntExact(value);
} catch (ArithmeticException e) {
System.out.println("Phat hien tran so: " + e.getMessage());
}
6.2 Pattern matching for instanceof từ Java 16
Java 16 chính thức giới thiệu pattern matching cho instanceof, giúp gộp thao tác kiểm tra kiểu và ép kiểu vào một biểu thức duy nhất:
Object item = "Java Foundations";
if (item instanceof String s) {
// Bien s tu dong mang kieu String ma khong can ep kieu thu cong
System.out.println("Do dai chuoi: " + s.length());
}
6.3 Chuyển đổi giữa String và số nguyên
Không thể ép kiểu trực tiếp giữa String và int. Hãy dùng các hàm tiện ích chuẩn:
// String sang int
int parsed = Integer.parseInt("12345"); // Nem NumberFormatException neu loi format
// int sang String
String text = String.valueOf(12345);
7. Mã nguồn ví dụ minh họa toàn diện cơ chế ép kiểu như thế nào?
public class TypeCastingDemo {
public static void main(String[] args) {
// 1. Widening tu dong
int count = 50;
double price = count; // int -> double: tu dong
System.out.println("Price: " + price);
// 2. Narrowing voi Math.round vs truncate
double rawScore = 8.75;
int truncated = (int) rawScore; // 8
int rounded = (int) Math.round(rawScore); // 9
System.out.println("Truncated: " + truncated + ", Rounded: " + rounded);
// 3. Narrowing fail-fast
long userCount = 5_000_000_000L;
try {
int safeCount = Math.toIntExact(userCount);
} catch (ArithmeticException ex) {
System.out.println("Bat tran so an toan voi Math.toIntExact");
}
// 4. Pattern matching instanceof an toan
Object data = "OLHub Platform 2026";
if (data instanceof String str && !str.isEmpty()) {
System.out.println("Noi dung hop le: " + str.toUpperCase());
}
}
}
8. Các bẫy pitfall thường gặp nhất khi ép kiểu trong Java là gì?
| Tình huống rủi ro | Cú pháp có lỗi | Hậu quả thực tế | Giải pháp an toàn |
|---|---|---|---|
| Cắt phần thập phân | (int) 4.9 | Nhận giá trị 4 thay vì 5 | Dùng (int) Math.round(d) |
| Tràn số nguyên ngầm | (byte) 130 | Nhận kết quả -126 im lặng | Kiểm tra cận biên hoặc Math.toIntExact() |
| Downcast sai kiểu | (String) objNumber | Ném ClassCastException | Dùng if (obj instanceof String s) |
| Unboxing biến null | int x = nullableInteger | Ném NullPointerException | Kiểm tra null hoặc gán giá trị mặc định |
| Ép kiểu String sang int | (int) "42" | Lỗi biên dịch (Compile error) | Dùng Integer.parseInt(str) |
9. Tài liệu Deep Dive Oracle quy định gì về type conversion?
- JLS §5.1.2 — Widening Primitive Conversion: Quy tắc mở rộng kiểu và cảnh báo mất precision khi chuyển sang float.
- JLS §5.1.3 — Narrowing Primitive Conversion: Quy định cắt bỏ phần thập phân và cơ chế lấy n bit thấp trong bù 2.
- JLS §5.5 — Casting Contexts: Toàn bộ điều kiện hợp lệ cho ép kiểu tham chiếu và runtime type check.
- JEP 394 — Pattern Matching for instanceof: Chuẩn hóa biến binding và loại bỏ hoàn toàn boilerplate casting.
10. Điểm cốt lõi cần nhớ về toàn bộ cơ chế ép kiểu là gì?
- Widening: Tự động mở rộng kiểu, an toàn về miền giá trị nhưng cần cẩn trọng rụng độ chính xác với float.
- Narrowing: Thu hẹp kiểu bắt buộc khai báo tường minh; dùng
Math.toIntExact()để chống tràn số âm thầm. - Upcasting & Downcasting: Upcast luôn an toàn; downcast bắt buộc bảo vệ bằng
instanceofdạng pattern matching. - String và số: Tuyệt đối không dùng toán tử ép kiểu giữa chuỗi và số nguyên, hãy dùng
Integer.parseInt().
11. Tự kiểm tra kiến thức về ép kiểu trong Java như thế nào?
- Q1Vì sao
int i = 100; float f = i;được chấp nhận tự động trong khifloat f = 100.0f; int i = f;lại báo lỗi biên dịch? - Q2Giá trị của biểu thức
(byte) 130trong Java là bao nhiêu? Giải thích cơ chế bên dưới. - Q3Đoạn mã
Integer val = null; int x = val;sẽ phát sinh lỗi gì khi thực thi? Sửa thế nào? - Q4Phương thức nào trong gói
java.lang.Mathgiúp thu hẹplongvềintkèm cơ chế fail-fast khi tràn số? - Q5Lợi ích chính của Pattern Matching for
instanceof(Java 16+) so với cách kiểm tra truyền thống là gì?
Bài tiếp theo: String, Text Block và StringBuilder — xử lý text hiện đại trong Java
Bài này có giúp bạn hiểu bản chất không?
Bài này đáng gửi cho bạn học cùng?
Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.
Toán tử logic và bitwise — short-circuit, cờ bit và thứ tự ưu tiên
Bài tiếpString, Text Block và StringBuilder trong Java hiện đại
Lộ trình Junior Java Backend Developer
Chặng 2/21 · Lập trình từ số 0 với Java — bài 15/32 của chặng
Thảo luận & góp ý
Hỏi đáp về bài này